RCS_ID_C="$Id: if_sana.c,v 1.30 1993/11/14 21:15:19 jraja Exp $";
/*
 * Copyright (c) 1993 AmiTCP/IP Group, <amitcp-group@hut.fi>,
 *                    Helsinki University of Technology, Finland.
 *                    All rights reserved.
 *
 * if_sana.c --- Generic Interface Routines for Sana II Drivers
 *
 * Created      : Thu Feb 11 13:41:25 1993 ppessi
 * Last modified: Sun Nov 14 23:13:54 1993 jraja
 *
 * HISTORY
 * $Log: if_sana.c,v $
 * Revision 1.30  1993/11/14  21:15:19  jraja
 * Changed IPTOS_LOWDELAY check to bitwise and (was == including other fields).
 *
 * Revision 1.29  1993/11/06  23:39:15  ppessi
 * Automatically puts interface up when device returns to online state.
 * The LOWDELAY IP packets are given higher priority IO requests.
 *
 * Revision 1.28  1993/10/11  20:31:46  jraja
 * Added explicit prototype for the CheckIO(), which is prototyped
 * incorrectly in the <clib/exec_protos.h>.
 *
 * Revision 1.27  1993/08/06  08:41:26  jraja
 * Added a special AbortIO() for the sana2 devices. This allows buggy drivers
 * to be used...
 *
 * Revision 1.26  1993/08/04  23:01:37  ppessi
 * Fixed stupid bug with prefix retry...
 *
 * Revision 1.25  1993/08/03  20:37:10  ppessi
 * Small fixes for the release 2.
 *
 * Revision 1.24  1993/08/01  22:21:21  ppessi
 * Sanitized (a bit) the iface_find(). Too many cooks...
 * Fixed a bug that caused a redundant log message after S2_TRACKTYPE.
 * Added some assert()s.
 *
 * Revision 1.23  1993/07/28  21:22:13  jraja
 * Restructured iface_find(). Now a "networks/" prefix is added to the
 * driver name if it has no path part.
 *
 * Revision 1.22  1993/06/14  22:17:20  jraja
 * Added cd to DEVS:networks before the OpenDevice() call.
 *
 * Revision 1.21  1993/06/04  11:16:15  jraja
 * Fixes for first public release.
 *
 * Revision 1.20  1993/05/16  21:12:38  ppessi
 * Changed RCS version
 *
 * Revision 1.19  1993/05/05  16:10:18  puhuri
 * Fixes for final demo.
 *
 * Revision 1.18  93/05/04  10:53:11  10:53:11  ppessi (Pekka Pessi)
 * Sanitized sana_softc structure
 * 
 * Revision 1.17  93/04/21  03:26:57  03:26:57  ppessi (Pekka Pessi)
 * sana_params.h lurks...
 * 
 * Revision 1.16  93/04/20  23:55:16  23:55:16  ppessi (Pekka Pessi)
 * Fixed ac_hwaddr and S2_CONFIGINTERFACE bugs, some ARP related bugs,
 * bug with m_pkthdr.rcvif member. Works with ARP and IP broadcasts.
 * 
 * Revision 1.14  93/04/19  03:08:37  03:08:37  ppessi (Pekka Pessi)
 * Changed sana_params.? to sana2tags.?
 *
 * Revision 1.14  93/04/19  02:14:35  02:14:35  ppessi (Pekka Pessi)
 * Added some ARP functionality.
 * Uses now sana2perror.
 * sana tag bugs fixed.
 * 
 * Revision 1.13  93/04/11  22:15:10  22:15:10  jraja (Jarno Tapio Rajahalme)
 * Changed first argument of the sana_output back to ifnet *.
 * 
 * Revision 1.12  93/04/06  08:53:35  08:53:35  jraja (Jarno Tapio Rajahalme)
 * Changed bcopy's to aligned_bcopy[_const] when appropriate.
 * 
 * Revision 1.11  93/04/05  17:46:00  17:46:00  jraja (Jarno Tapio Rajahalme)
 * Changed spl storage variables to spl_t.
 * Changed every .c file to use conf.h.
 * 
 * Revision 1.10  93/03/20  07:12:26  07:12:26  ppessi (Pekka Pessi)
 * deinit_sana() fixed.
 * 
 * Revision 1.8  93/03/12  23:55:01  23:55:01  ppessi (Pekka Pessi)
 * Cancelled if_down() kludge.
 * 
 * Revision 1.7  93/03/12  22:49:02  22:49:02  ppessi (Pekka Pessi)
 * Pulled if_down() from if.c;
 * Moved IOSana2Request handling into sana2request.h
 * 
 * Revision 1.6  93/03/09  17:58:58  17:58:58  ppessi (Pekka Pessi)
 * fixed bug with  SIOCSIFDSTADDR ioctl and ioip_alloc_mbuf() error handling
 * 
 * Revision 1.5  93/03/05  19:49:45  19:49:45  jraja (Jarno Tapio Rajahalme)
 * Changed free() to bsd_free().
 * Fixed includes (again).
 * 
 * Revision 1.4  93/03/05  03:12:18  03:12:18  ppessi (Pekka Pessi)
 * Compiles with SASC. Initial test version
 * 
 * Revision 1.3  93/02/28  21:36:09  21:36:09  ppessi (Pekka Pessi)
 * Revised version; final paper test.
 */

#include <conf.h>

#include <sys/param.h>
#include <sys/cdefs.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/syslog.h>

#include <kern/amiga_includes.h>

#include <sys/synch.h>
#include <sys/time.h>

#include <net/if.h>
#include <net/netisr.h>

#define NDEBUG
#include <assert.h>

#if INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#endif

#if NS
#include <netns/ns.h>
#include <netns/ns_if.h>
#endif

#include <net/if_sana.h>
#include <net/sana2arp.h>

#include <net/sana2tags.h>
#include <net/sana2request.h>
#include <net/sana2errno.h>

#if __SASC
#include <proto/dos.h>
#elif __GNUC__
#include <inline/dos.h>
#else
#error Your compiler is not supported in this release.
#endif

/* Correct prototype for the CheckIO.
 * (The one in clib/exec_protos.h has wrong return value type: BOOL (16 bits) 
 * instead of a pointer (32 bits)!)
 */
struct IORequest * CheckIO(struct IORequest *req);

#define ARP_MTU (sizeof(struct s2_arppkt))

int debug_sana = 1;

/* Global port for all SANA-II network interfaces */
struct MsgPort *SanaPort = NULL;

/* queue for sana network interfaces */
struct sana_softc *ssq = NULL;

/* These are wire type dependant parameters of 
 * Sana-II Network Interface
 */
extern struct wiretype_parameters wiretype_table[];

/* 
 * Local prototypes
 */
static int  parse_sana_param_tags(struct sana_softc *, struct TagItem *);
static void sana_run(struct sana_softc *ssc, int requests, struct ifaddr *ifa);
static void sana_unrun(struct sana_softc *ssc);
static void sana_up(struct sana_softc *ssc);
static BOOL sana_down(struct sana_softc *ssc);
static struct mbuf *
sana_read(struct sana_softc *ssc, struct IOIPReq *req, 
	  UWORD  flags, UWORD *sent, char *banner, size_t mtu);
static void sana_ip_read(struct sana_softc *ssc, struct IOIPReq *req);
static void sana_arp_read(struct sana_softc *ssc, struct IOIPReq *req);
static void sana_online(struct sana_softc *ssc, struct IOIPReq *req);
static void free_written_packet(struct sana_softc *ssc, struct IOIPReq *req);

/*
 * Initialize Sana-II interface
 *
 * This routine creates needed message port for Sana-II IO
 * It returns our signal mask, or 0L on an error.
 */
ULONG 
sana_init(void)
{
  assert(!SanaPort);

  SanaPort = CreateMsgPort();	/* V36 function, creates a PA_SIGNAL port */

  if (SanaPort) {
    SanaPort->mp_Node.ln_Name = "sana_if.port";
    loattach();
    return (ULONG) 1 << SanaPort->mp_SigBit;
  }

  return 0L;
}

/*
 * Clean up Sana-II Interfaces
 *
 * Note: main interface queue is SNAFU after deinitializing
 */
void 
sana_deinit(void)
{
  struct sana_softc *ssc; 
  struct IOSana2Req *req;

  assert(SanaPort);

  while (ssq) {
    sana_down(ssq);
    if (ssq->ss_if.if_flags & IFF_RUNNING) {
      sana_unrun(ssq);
    }
    ssc = ssq;
    ssq = ssc->ss_next;
    /* Close device */ 
    req = CreateIOSana2Req(ssc);
    if (req) {
      CloseDevice((struct IORequest*)req);
      DeleteIOSana2Req(req);
    } else {
      log(LOG_ERR, "sana_deinit(): Couldn't close device %s\n",
	  ssc->ss_name);
    }
  }

  if (SanaPort) {
    /* Clear possible pending signals */
    SetSignal(1<<SanaPort->mp_SigBit, 0L);
    DeleteMsgPort(SanaPort);
    SanaPort = NULL;
  }
}

/*
 * sana_poll()
 *  This routine polls SanaPort and processes replied
 *  requests appropriately
 */
BOOL
sana_poll(void)
{
  struct IOIPReq * io;
  spl_t s = splnet();

  while (io = (struct IOIPReq *)GetMsg(SanaPort)) {
    /* touch the network interface */
    get_time(&io->ioip_if->ss_if.if_lastchange);
    if (io->ioip_dispatch) {
      (*io->ioip_dispatch)(io->ioip_if, io);
     } else {
       log(LOG_ERR, "No dispatch function in request for %s\n",
	   io->ioip_if->ss_name);
     }
  }

  net_poll();

  splx(s);

  return FALSE;
}

/*
 * This function strategically plugs into ifunit(), and it is called
 * on a non-existant interface.  We try to look it up, and if successful
 * initialize a descriptor and call if_attach() with it.
 *
 * Name points to the full device name.
 * Device name is a legal DOS file name,
 * appended with a slash and a decimal unit number
 *
 * Some explanation on the device names:
 * There is a DOS wrapper around Exec OpenDevice() function.
 * The device is first searched from the Exec list, if that fails
 * DOS tries to load the segment file with the device name. 
 * If that fails too, the filename is catenated to string "DEVS:" and
 * DOS tries again. 
 *
 * AmiTCP uses internally only the Exec device name (ie. device name
 * without pathpart)
 */
struct ifnet *
iface_find(char *name)
{
  register struct sana_softc *ssc = NULL;
  register struct IOSana2Req *req;
  struct Sana2DeviceQuery devicequery;
  char namebuf[sizeof(NAME_PREFIX) + IFNAMSIZ];

  /* find unit number */
  int namelen;
  LONG unit;
  char *unitp = PathPart(name);
  if (*unitp != '/' || StrToLong(unitp + 1, &unit) < 0) {
    log(LOG_ERR, "iface_find(): Illegal device name: %s\n", name);
    return NULL;
  }

  /* Allocate the request for opening the device */
  if ((req = CreateIOSana2Req(NULL)) == NULL) 
    log(LOG_ERR, "iface_find(): CreateIOSana2Req failed\n");
  else {
    req->ios2_BufferManagement = buffermanagement;

    /* the REALNAMEBUF is pointer to the device name given by user */
#define REALNAMEBUF (namebuf + sizeof(NAME_PREFIX) - 1)

    /* Copy the NAME_PREFIX in the front of the device name */
    strcpy(namebuf, NAME_PREFIX);
    
    /* Calculate the length of device name given by user */
    namelen = unitp - name;
    assert(namelen < IFNAMSIZ - 1);

    /* Copy the device name sans unit number */
    bcopy(name, REALNAMEBUF, namelen);
    REALNAMEBUF[namelen] = '\0';

    if (OpenDevice(REALNAMEBUF, unit, (struct IORequest *)req, 0L) &&
	/* Should we retry with prefix? */
	((char *)FilePart(REALNAMEBUF) != REALNAMEBUF ||
	 /* There was no pathpart in file, so try with NAME_PREFIX */
	 OpenDevice(namebuf, unit, (struct IORequest *)req, 0L)))
      sana2perror("OpenDevice", req);
    else {
      /* Ask for our type, address length, MTU
       * Obl. bitch: nobody tells, WHO is supplying
       * DevQueryFormat and DeviceLevel
       */
      req->ios2_Req.io_Command   = S2_DEVICEQUERY;
      req->ios2_StatData         = &devicequery;
      devicequery.SizeAvailable  = sizeof(devicequery);
      devicequery.DevQueryFormat = 0L;
      
      DoIO((struct IORequest *)req);
      if (req->ios2_Req.io_Error)
	sana2perror("S2_DEVICEQUERY", req);
      else {
	/* Get Our Station address */
	req->ios2_StatData = NULL;
	req->ios2_Req.io_Command = S2_GETSTATIONADDRESS;
	DoIO((struct IORequest *)req);
	
	if (req->ios2_Req.io_Error)
	  sana2perror("S2_GETSTATIONADDRESS", req);
	else {
	  req->ios2_Req.io_Command = 0;
	  
	  /* Allocate the interface structure */
	  ssc = (struct sana_softc *)bsd_malloc(sizeof(*ssc),
						M_IFNET, M_WAITOK);
	  if (!ssc)
	    log(LOG_ERR, "iface_find: out of memory\n");
	  else {
	    aligned_bzero_const(ssc, sizeof(*ssc));
	    
	    /* Save request pointers */
	    ssc->ss_dev     = req->ios2_Req.io_Device;
	    ssc->ss_unit    = req->ios2_Req.io_Unit;
	    ssc->ss_bufmgnt = req->ios2_BufferManagement;
	    
	    /* Save driver name to network interface structure.
	     * Driver name is the last part of the full path
	     * name user have supplied.  Caller has ensured that
	     * it is shorter than IFNAMSIZ.
	     */
	    strcpy(ssc->ss_name, FilePart(namebuf));
	    
	    ssc->ss_if.if_name = ssc->ss_name;
	    ssc->ss_if.if_unit = unit;
	    
	    /* Address must be full bytes */
	    ssc->ss_if.if_addrlen  = (devicequery.AddrFieldSize + 7) >> 3;
	    bcopy(req->ios2_DstAddr, ssc->ss_hwaddr, ssc->ss_if.if_addrlen);
	    ssc->ss_if.if_mtu      = devicequery.MTU;
	    ssc->ss_if.if_baudrate = devicequery.BPS;
	    ssc->ss_hwtype         = devicequery.HardwareType;	
	    
	    /* These might be different on different hwtypes */
	    ssc->ss_if.if_output = sana_output;
	    ssc->ss_if.if_ioctl  = sana_ioctl;
	    {
	      /*
	       * set hardware dependent parameters of SanaII driver
	       */
	      struct wiretype_parameters *wires = wiretype_table;
	      ULONG  type = devicequery.HardwareType;
	      
	      while (wires->wiretype) {
		if (wires->wiretype == type)
		  break;
		else
		  wires++;
	      }
	      ssc->ss_if.if_flags = wires->flags;
	      
	      parse_sana_param_tags(ssc, wires->tags);
	    }
	    
	    
	    NewList((struct List*)&ssc->ss_freereq);

	    if_attach((struct ifnet*)ssc);
	    ifinit();
	    
	    ssc->ss_next = ssq;
	    ssq = ssc;
	  }
	}
      }
      if (!ssc)
	CloseDevice((struct IORequest *)req);
    }    
    DeleteIOSana2Req(req);
  }

  return (struct ifnet *)ssc;
}

/*
 * Parse different SANA-II device driver parameters
 */
static int
parse_sana_param_tags(struct sana_softc *ssc, struct TagItem *tags)
{
  assert(ssc);
  assert(ssc->ss_if.if_type == IFT_SANA);

  while(tags->ti_Tag != TAG_END) {
    switch(tags->ti_Tag) {
    case IFS2_TOTAL:     ssc->ss_reqno    += tags->ti_Data; break;
#if	INET			
    case IFS2_IPNO:      ssc->ss_ip.reqno  += tags->ti_Data; break;
    case IFS2_IPTYPE:    ssc->ss_ip.type   = tags->ti_Data; break;
    case IFS2_ARPNO:     ssc->ss_arp.reqno += tags->ti_Data; break;
    case IFS2_ARPTYPE:   ssc->ss_arp.type  = tags->ti_Data; break;
    case IFS2_ARPHRD:    ssc->ss_arp.hrd   = tags->ti_Data; break;
#endif /* INET */
#if	ISO
    case IFS2_ISONO:     ssc->ss_isoreqno += tags->ti_Data; break;
    case IFS2_ISOTYPE:   ssc->ss_isotype  = tags->ti_Data; break;
#endif /* ISO */
#if	CCITT
    case IFS2_CCITTNO:   ssc->ss_ccittreqno+= tags->ti_Data; break;
    case IFS2_CCITTTYPE: ssc->ss_ccitttype= tags->ti_Data; break;
#endif /* CCITT */
#if	NS
    case IFS2_NSNO:      ssc->ss_nsreqno  += tags->ti_Data; break;
    case IFS2_NSTYPE:    ssc->ss_nstype   = tags->ti_Data; break;
#endif /* NS */
    case IFS2_RAWNO:     ssc->ss_rawreqno += tags->ti_Data; break;
      /* Reset all other tags? */ 
    case IFS2_RESET:     
      if (ssc->ss_if.if_flags & IFF_RUNNING)
	return EISCONN;
      ssc->ss_reqno = 0;
#if	INET			
      ssc->ss_ip.reqno = 0;
      ssc->ss_ip.type = 0;
      ssc->ss_arp.reqno = 0;
      ssc->ss_arp.type  = 0;
#endif /* INET */
#if	ISO
      ssc->ss_isoreqno = 0;
      ssc->ss_isotype  = 0;
#endif /* ISO */
#if	CCITT
      ssc->ss_ccittreqno = 0;
      ssc->ss_ccitttype = 0;
#endif /* CCITT */
#if	NS
      ssc->ss_nsreqno = 0;
      ssc->ss_nstype = 0;
#endif /* NS */
      ssc->ss_rawreqno = 0;
    }
    tags++;
  }
  return FALSE;
}

/*
 * Allocate Sana-II IORequests for TCP/IP process
 */
static void
sana_run(struct sana_softc *ssc, int requests, struct ifaddr *ifa)
{
  int i;
  spl_t s = splimp();
  struct IOIPReq *req, *next = ssc->ss_reqs;
  
  /*
   * Configure the Sana-II device driver
   * (now with factory address)
   */
  if ((ssc->ss_if.if_flags & IFF_RUNNING) == 0) {
    struct IOSana2Req *req;

    if (req = CreateIOSana2Req(ssc)) {
      req->ios2_Req.io_Command = S2_CONFIGINTERFACE;
      bcopy(ssc->ss_hwaddr, req->ios2_SrcAddr, ssc->ss_if.if_addrlen);

      DoIO((struct IORequest*)req);
      if (req->ios2_Req.io_Error)
	sana2perror("S2_CONFIGINTERFACE", req);

      if (req->ios2_Req.io_Error == 0 ||
	  req->ios2_WireError == S2WERR_IS_CONFIGURED) {
	/* Mark us as running */
	ssc->ss_if.if_flags |= IFF_RUNNING;
#ifdef INET
	/* Ask for packet type specific statistics */
	req->ios2_Req.io_Command = S2_TRACKTYPE;
	req->ios2_PacketType = ssc->ss_ip.type;
	DoIO((struct IORequest*)req);
	/* It is *not* safe to turn tracking off */
	if (req->ios2_Req.io_Error && 
	    req->ios2_WireError != S2WERR_ALREADY_TRACKED) 
	  sana2perror("S2_TRACKTYPE for IP", req);
	if (ssc->ss_arp.reqno) {
	  req->ios2_PacketType = ssc->ss_arp.type;
	  DoIO((struct IORequest*)req);
	  if (req->ios2_Req.io_Error  && 
	    req->ios2_WireError != S2WERR_ALREADY_TRACKED) 
	    sana2perror("S2_TRACKTYPE for ARP", req);
	}
#endif	
      }
      DeleteIOSana2Req(req);
    }
  }

  if ((ssc->ss_if.if_flags & IFF_RUNNING)) {
    /* Initialize ioRequests, add them into free queue */
    for (i = 0; i < requests ; i++) {
      if (!(req = CreateIORequest(SanaPort, sizeof(*req))))
	break;
      req->ioip_s2.ios2_Req.io_Device    = ssc->ss_dev;    
      req->ioip_s2.ios2_Req.io_Unit      = ssc->ss_unit;   
      req->ioip_s2.ios2_BufferManagement = ssc->ss_bufmgnt;
      aligned_bcopy(ssc->ss_hwaddr, req->ioip_s2.ios2_SrcAddr, ssc->ss_if.if_addrlen);
      req->ioip_s2.ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
      req->ioip_s2.ios2_Data = req;
      req->ioip_if = ssc;
      req->ioip_next = next;
      AddTail((struct List*)&ssc->ss_freereq, (struct Node*)req);
      next = req;
    }
    ssc->ss_reqs = next;
  }
  splx(s);
}

/*
 * Free Sana-II IO Requests 
 * Note: this is protected by splimp();
 */
static void
sana_unrun(struct sana_softc *ssc)
{
  struct IOIPReq *req, *next;
  
  for ( next = ssc->ss_reqs; req = next ;) {
    next = req -> ioip_next;
    WaitIO((struct IORequest *)req);
    DeleteIORequest(req);
  }
  ssc->ss_reqs = next;
  
  ssc->ss_if.if_flags &= ~IFF_RUNNING;
}

/*
 * Generic SANA-II interface ioctl
 *
 * Interface setup is thru IOCTL.
 */
int 
sana_ioctl(register struct ifnet *ifp, int cmd, caddr_t data)
{
  register struct sana_softc *ssc = (struct sana_softc*)ifp;
  register struct ifaddr *ifa = (struct ifaddr *)data;
  register struct ifreq *ifr = (struct ifreq *)data;
  
  spl_t s = splimp();
  int error = 0;

  switch (cmd) {

  case SIOCSIFFLAGS:
    if ((ifr->ifr_flags & (IFF_UP|IFF_RUNNING)) == (IFF_UP|IFF_RUNNING))
      sana_up(ssc);
    /* Call sana_down() in every case */
    if ((ifr->ifr_flags & IFF_UP) == 0) 
      sana_down(ssc);
    if ((ifr->ifr_flags & IFF_NOARP) == 0)
      alloc_arptable(ssc, 0);
    break;

    /*
     * Set interface address (and mark interface up).
     */
  case SIOCSIFADDR:		/* Set Interface Address */
    if (!(ssc->ss_if.if_flags & IFF_RUNNING)) 
      sana_run(ssc, ssc->ss_reqno, ifa);
    if ((ssc->ss_if.if_flags & IFF_RUNNING) && !(ssc->ss_if.if_flags & IFF_UP))
      sana_up(ssc);
    if ((ifr->ifr_flags & IFF_NOARP) == 0)
      alloc_arptable(ssc, 0);
    
  case SIOCAIFADDR:		/* Alter Interface Address */
    switch (ifa->ifa_addr->sa_family) {
#if INET
    case AF_INET:
      ssc->ss_ipaddr = IA_SIN(ifa)->sin_addr;
      break;
#endif
    }
    break;

  case SIOCSSANATAGS:		/* Special Amiga ioctl */
    error = parse_sana_param_tags(ssc, (struct TagItem *)data);
    break;

  case SIOCSIFDSTADDR:		/* Sets P-P-link destination address */
    break;

  default:
    error = EINVAL;
    break;
  }
  splx(s);
  return (error);
}

/*
 * sana_send_read(): 
 * send read requests with given types, dispatcher & c  
 * MUST be called at splimp()
 */
static inline WORD 
sana_send_read(struct sana_softc *ssc, WORD count, ULONG type, ULONG mtu,
	       void (*dispatch)(struct sana_softc *, struct IOIPReq *),
	       UWORD command, UBYTE flags)
{
  struct IOIPReq *req = NULL;
  WORD i;

  for (i = 0; i < count; i++) {
    if (!(req = (struct IOIPReq*)RemHead((struct List*)&ssc->ss_freereq)))
      return i;
    req->ioip_dispatch = dispatch;
    req->ioip_s2.ios2_PacketType = type;
    req->ioip_Command = command;
    req->ioip_s2.ios2_Req.io_Flags = flags;
    if (!ioip_alloc_mbuf(req, mtu))
      goto no_resources;
    BeginIO((struct IORequest*)req);
  }
  return i;

 no_resources:
  if (req)
    AddHead((struct List*)&ssc->ss_freereq, (struct Node*)req);
  log(LOG_ERR, "sana_up: could not queue enough read requests\n");
  return i;
}

/*
 * sana_up():
 * send read requests
 */
static void
sana_up(struct sana_softc *ssc)
{
  spl_t s = splimp();
  ssc->ss_if.if_flags |= IFF_UP;
  
  /* Send read requests to device driver */
#if	INET
  /* IP */
  ssc->ss_ip.sent += 
    sana_send_read(ssc, ssc->ss_ip.reqno - ssc->ss_ip.sent, ssc->ss_ip.type,
		   ssc->ss_if.if_mtu, sana_ip_read, CMD_READ, 0);

  ssc->ss_arp.sent += 
    sana_send_read(ssc, ssc->ss_arp.reqno - ssc->ss_arp.sent, ssc->ss_arp.type,
		   ARP_MTU, sana_arp_read, CMD_READ, 0);

#endif /* INET */
#if	ISO
#endif /* ISO */
#if	CCITT
#endif /* CCITT */
#if	NS
#endif /* NS */
#if 0
  ssc->ss_rawsent += 
    sana_send_read(ssc, ssc->ss_rawreqno - ssc->ss_rawsent, 0,
		   ssc->ss_if.if_mtu, sana_raw_read, 
		   S2_READORPHAN, SANA2_IOF_RAW);
#endif
  splx(s);
  return;
}

#if __SASC
/*
 * "Fix" for numerous sana2 drivers, which expect to get Unit * in the
 * register A3 when their AbortIO function is called.
 * Note that Exec AbortIO() does NOT put it there.
 */
extern VOID _AbortSanaIO(struct IORequest *, struct Unit *);
#pragma libcall DeviceBase _AbortSanaIO 24 B902

static inline __asm VOID 
AbortSanaIO(register __a1 struct IORequest *ioRequest)
{
#define DeviceBase ioRequest->io_Device
  _AbortSanaIO(ioRequest, ioRequest->io_Unit);
#undef DeviceBase
}
#else /* implement later for other compilers */
#define AbortSanaIO AbortIO
#endif

/*
 * sana_down(): Mark interface as down, abort all pending requests
 */
static BOOL
sana_down(struct sana_softc *ssc)
{
  spl_t s = splimp();
  struct IOIPReq *req = ssc->ss_reqs;

  /* Completed, Remove()'d requests are not aborted */
  while (req) {
    if (!CheckIO((struct IORequest*)req)) {
      AbortSanaIO((struct IORequest*)req);
    }
    req = req->ioip_next;
  }

  splx(s);

  return(TRUE);
}

/*
 * sana_read: deattach a packet from IORequest
 *            resend the IORequest
 */
static struct mbuf *
sana_read(struct sana_softc *ssc, struct IOIPReq *req, 
	  UWORD  flags, UWORD *sent, char *banner, size_t mtu)
{
  register struct mbuf *m = req->ioip_packet;
  register spl_t s = splimp();

  req->ioip_packet = NULL;

  switch (req->ioip_Error) {
  case 0:
    if (req->ioip_s2.ios2_Req.io_Flags & SANA2IOF_BCAST) 
      m->m_flags |= M_BCAST;
    if (req->ioip_s2.ios2_Req.io_Flags & SANA2IOF_MCAST)
      m->m_flags |= M_MCAST;
    break;
  case S2ERR_OUTOFSERVICE:
    /*
     * Somebody put Sana-II driver offline.
     * We put down also the network interface 
     */
    if (ssc->ss_if.if_flags & IFF_UP) {
      /* Show a log message */
      sana2perror(ssc->ss_if.if_name, (struct IOSana2Req *)req);

      /* tell it to protocols */
      if_down((struct ifnet *)ssc); 

      /* Free mbufs allocated for packet */
      m_freem(req->ioip_reserved);
      req->ioip_reserved = NULL;

      /* Order an notify when driver is put back online */
      ssc->ss_eventsent++;
      req->ioip_s2.ios2_Req.io_Command = S2_ONEVENT;
      req->ioip_s2.ios2_WireError = S2EVENT_ONLINE;
      req->ioip_dispatch = sana_online;
      BeginIO(req);
      req = NULL;
    }
    ssc->ss_if.if_flags &= ~IFF_UP;
    m_freem(m);
    m = NULL;
    break;
  default:
    if (debug_sana && req->ioip_Error != IOERR_ABORTED) 
      sana2perror(banner, (struct IOSana2Req *)req);
    m_freem(m);
    m = NULL;
  }

  if (ssc->ss_if.if_flags & IFF_UP) {
    /* Return request to the Sana-II driver */
    if (ioip_alloc_mbuf(req, mtu)) {
      req->ioip_s2.ios2_Req.io_Flags = flags;
      BeginIO((struct IORequest*)req); 
      splx(s);
      return m;
    }
    log(LOG_ERR, "sana_read (%s): not enough mbufs\n", ssc->ss_name);
  } 

  /* do not resend, free used resources */
  (*sent)--;
  if (req) {
    m_freem(req->ioip_reserved);
    req->ioip_reserved = NULL;
    req->ioip_dispatch = NULL;
    AddHead((struct List*)&ssc->ss_freereq, (struct Node*)req);
  }

  if (m) {
    m_freem(m);
  }

  splx(s);
  return NULL;
}

/*
 * sana_ip_read(): feed a packet to the IP queue
 * (This routine is called from sana_poll)
 */
static void
sana_ip_read(struct sana_softc *ssc, struct IOIPReq *req)
{
  struct mbuf *m = sana_read(ssc, req, 0, &ssc->ss_ip.sent, "sana_ip_read",
			     ssc->ss_if.if_mtu);
  spl_t s;

  if (m) {
    s = splimp();
    if (IF_QFULL(&ipintrq)) {
      IF_DROP(&ipintrq);
      m_freem(m);
      /* m = NULL; */
    } else {
      /* Set interface pointer (needed for broadcasts) */
      m->m_pkthdr.rcvif = (struct ifnet *)ssc; 
      IF_ENQUEUE(&ipintrq, m);
      /* A signal might be needed if we use PA_EXCEPTION port */
      schednetisr_nosignal(NETISR_IP);
      /* m = NULL; */
    }
    splx(s);
  }
}

/*
 * sana_arp_read(): process an ARP packet
 * (This routine is called from sana_poll)
 */
static void
sana_arp_read(struct sana_softc *ssc, struct IOIPReq *req)
{
  struct mbuf *m; 
  UBYTE hwaddr[MAXADDRSANA];

  bcopy(req->ioip_s2.ios2_SrcAddr, hwaddr, ssc->ss_if.if_addrlen);

  m = sana_read(ssc, req, 0, &ssc->ss_arp.sent, "sana_arp_read", ARP_MTU);

  if (m)
    arpinput(ssc, m, hwaddr);
}

/*
 * sana_online(): process an ONLINE event
 */
static void
sana_online(struct sana_softc *ssc, struct IOIPReq *req)
{
  LONG events = req->ioip_s2.ios2_WireError;

  if (req->ioip_s2.ios2_Req.io_Error == 0 &&
      events & S2EVENT_ONLINE) {
    ssc->ss_eventsent--;
    req->ioip_dispatch = NULL;
    AddHead((struct List*)&ssc->ss_freereq, (struct Node*)req);
    log(LOG_NOTICE, "%s is online again.", ssc->ss_name);
    sana_up(ssc);
    return;
  }

  /* An error? */
  if (debug_sana && req->ioip_Error != IOERR_ABORTED) { 
    sana2perror("sana_online", (struct IOSana2Req *)req);
    req->ioip_s2.ios2_Req.io_Command = S2_ONEVENT;
    req->ioip_s2.ios2_WireError = S2EVENT_ONLINE;
    BeginIO(req);
  } else {
    /* Aborted -- probably because "ifconfig xxx/0 down" */
    ssc->ss_eventsent--;
    req->ioip_dispatch = NULL;
    AddHead((struct List*)&ssc->ss_freereq, (struct Node*)req);
  }
}

/*
 * sana_output: send a packet to Sana-II driver
 */
int
sana_output(struct ifnet *ifp, struct mbuf *m0, 
	    struct sockaddr *dst, struct rtentry *rt)
{
  register struct sana_softc *ssc = (struct sana_softc *)ifp;
  ULONG type;
  int error = 0;
  struct in_addr idst;

  /* If a broadcast, send a copy to ourself too */
  struct mbuf *mcopy = (struct mbuf *)NULL;
  struct IOIPReq *req = NULL;
  register struct mbuf *m = m0;

  int len = m->m_pkthdr.len;
  spl_t s = splimp();

  ifp->if_opackets++;		/* stats */

  /* Check if we are up and running... */
  if ((ssc->ss_if.if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
    error = ENETDOWN;
    goto bad;
  }

  get_time(&ssc->ss_if.if_lastchange);

  /* Get a free Sana-II IO request */
  if (!(req = (struct IOIPReq*)RemHead((struct List*)&ssc->ss_freereq))) {
    error = ENOBUFS;
    goto bad;
  }

  req->ioip_s2.ios2_Req.io_Flags = 0;

  switch (dst->sa_family) {
#if INET
  case AF_INET:
    idst = ((struct sockaddr_in *)dst)->sin_addr;

    /* If the address is not resolved, arpresolve
     * stores the packet to its private queue for
     * later transmit and broadcasts the resolve
     * request packet to the (ether)net.
     * (Now ARP works only with IP and ethernet.)
     */
    if ((ssc->ss_if.if_flags & IFF_NOARP) != IFF_NOARP &&
	/* ssc = network interface 
	 * m = Packet to send 
	 * idst = destination IP address 
	 * ios2_DestAddr = destination hw address 
	 * error = error return
	 */
	!arpresolve(ssc, m, &idst, req->ioip_s2.ios2_DstAddr, &error)) {
      AddHead((struct List*)&ssc->ss_freereq, (struct Node*)req);
      splx(s);
      return (0);
    }
    type = ssc->ss_ip.type;

    /* Send to loopback if we do not hear our broadcasts */
    if ((ssc->ss_if.if_flags & IFF_SIMPLEX) && (m->m_flags & M_BCAST)) {
      mcopy = m_copy(m, 0, (int)M_COPYALL);
      (void) looutput(&ssc->ss_if, mcopy, dst, rt);
    }
    /* Set the message priority */
    req->ioip_s2.ios2_Req.io_Message.mn_Node.ln_Pri =
      (IPTOS_LOWDELAY & mtod(m, struct ip *)->ip_tos) ?
	1 : 0;
    break;
#endif
#if NS
#error NS unimplemented!!!
  case AF_NS:
    type = ssc->ss_nstype;
    /* There is hardware address straight in socket */
    /* Dunno how this works, if we have a P-to-P device */
    bcopy((caddr_t)&(((struct sockaddr_ns *)dst)->sns_addr.x_host),
	  (caddr_t)req->ioip_s2.ios2_DestAddr, ssc->ss_if.if_addrlen);
    /* Local send */
    if (!bcmp((caddr_t)req->ioip_s2.ios2_DestAddr,
	      (caddr_t)&ns_thishost, ssc->ss_if.if_addrlen)) {
      AddHead(&ssc->ss_freereq, req);
      return (looutput(ifp, m, dst, rt));
    }
    req->ioip_s2.ios2_Req.io_Message.mn_Node.ln_Pri = 0;
    break;
#endif
  case AF_UNSPEC:
    /* Raw packets. Sana-II address (a tuple of type and host)
     * specifies the destination 
     */
    if (type = ((struct sockaddr_sana2*)dst)->ss2_type) {
      bcopy(((struct sockaddr_sana2*)dst)->ss2_host, 
	    req->ioip_s2.ios2_DstAddr, 
	    ssc->ss_if.if_addrlen);
    } else {
      req->ioip_s2.ios2_Req.io_Flags = SANA2IOF_RAW;
      type = 0L;
    }
    req->ioip_s2.ios2_Req.io_Message.mn_Node.ln_Pri = 0;
    break;

#if	ISO
#endif /* ISO */
#if RMP
  case AF_RMP:
#endif

  default: 
    log(LOG_ERR, "%s%ld: can't handle af%ld\n",
	ssc->ss_if.if_name, ssc->ss_if.if_unit, dst->sa_family);
    error = EAFNOSUPPORT; 
    goto bad; 
  }

  /*
   * Queue packet to Sana-II driver
   */
  req->ioip_Command = (m->m_flags & M_BCAST) ? S2_BROADCAST : CMD_WRITE;
  req->ioip_dispatch = free_written_packet;
  req->ioip_packet = m;
  req->ioip_s2.ios2_PacketType = type;
  req->ioip_s2.ios2_DataLength = len;

  BeginIO((struct IORequest*)req);

  /* These statistics are somewhat redundant */
  ifp->if_obytes += len;
  if (m->m_flags & M_BCAST)
    ifp->if_omcasts++;

  splx(s);
  return 0;

 bad:
  if (req)
    AddHead((struct List*)&ssc->ss_freereq, (struct Node*)req);
  ifp->if_oerrors++;

  splx(s);
  if (m)
    m_freem(m);
  return error;
}

/*
 * free_written_packet(): free mbufs of written packet,
 *                        queue IOrequest for reuse
 * (This routine is called from sana_poll)
 */
static void
free_written_packet(struct sana_softc *ssc, struct IOIPReq *req)
{
  spl_t s = splimp();

  if (req->ioip_packet) {
    m_freem(req->ioip_packet);
    req->ioip_packet = NULL;
  }
  req->ioip_dispatch = NULL;
  if (debug_sana && req->ioip_Error)
    sana2perror("sana_output", (struct IOSana2Req *)req);
  AddHead((struct List*)&ssc->ss_freereq, (struct Node*)req);
  splx(s);
}

