/*
** $Source: dh1:network/parnet/Sana2/Sources/sana2_funcs.c,v $
** $State: Exp $
** $Revision: 37.2 $
** $Date: 93/12/17 22:07:45 $
** $Author: S.A.Pechler $
**
** Amiga SANA-II Example PARnet device driver.
**
** SPAR Sana-II functions.
**
** Based on the Amiga SANA-II Example SLIP device driver code by bj, 
** which is (C) Copyright 1992 Commodore-Amiga, Inc.
** the rhslip.device by Olaf Seibert <rhialto@mbfys.kun.nl>, and on
** the agnet.device code by ppessi <Pekka.Pessi@hut.fi>, which is
** Copyright (c) 1993 AmiTCP/IP Group,
**                    Helsinki University of Technology, Finland.
**                    All rights reserved.
**
*/

#include "device_protos.h"

/*
**
** PerformIO
**
** PerformIO actually dispatches an io request.  It might be called from
** the Unit process, or directly from BeginIO (thus on the caller's schedule)
*/
VOID PerformIO(struct IOSana2Req *ios2)
{
    struct SPARDevUnit *sdu;

    sdu = (struct SPARDevUnit *)ios2->ios2_Req.io_Unit; /* get the Unit pointer */

    ios2->ios2_Req.io_Error = NULL;  /* no error so far */

    switch(ios2->ios2_Req.io_Command)
    {
        case CMD_READ:              ReadPacket(sdu,ios2);
                                    break;

        case CMD_WRITE:             debug(("PerformIO: call to CMD_WRITE\n"))
                                    WritePacket(sdu,ios2);
                                    break;

        case S2_DEVICEQUERY:        debug(("PerformIO: call to S2_DEVICEQUERY\n"))
                                    DeviceQuery(sdu,ios2);
                                    break;

        case S2_GETSTATIONADDRESS:  debug(("PerformIO: call to S2_GETSTATIONADDRESS\n"))
                                    GetStationAddress(sdu,ios2);
                                    break;

        case S2_CONFIGINTERFACE:    debug(("PerformIO: call to S2_CONFIGINTERFACE\n"))
                                    ConfigInterface(sdu,ios2);
                                    break;

        case S2_BROADCAST:          debug(("PerformIO: call to S2_BROADCAST\n"))
                                    BroadcastPacket(sdu,ios2);
                                    break;
        case S2_ADDMULTICASTADDRESS:
        case S2_DELMULTICASTADDRESS:
        case S2_MULTICAST:          ios2->ios2_Req.io_Error = S2ERR_NOT_SUPPORTED;
                                    ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
                                    debug(("PerformIO: call to not supported function\n"))

                                    TermIO(ios2);
                                    break;

        case S2_TRACKTYPE:          debug(("PerformIO: call to S2_TRACKTYPE\n"))
                                    TrackType(sdu,ios2);
                                    break;

        case S2_UNTRACKTYPE:        debug(("PerformIO: call to S2_UNTRACKTYPE\n"))
                                    UnTrackType(sdu,ios2);
                                    break;

        case S2_GETTYPESTATS:       debug(("PerformIO: call to S2_GETTYPESTATS\n"))
                                    GetTypeStats(sdu,ios2);
                                    break;

        case S2_GETSPECIALSTATS:    debug(("PerformIO: call to S2_GETSPECIALSTATS\n"))
                                    GetSpecialStats(sdu,ios2);
                                    break;

        case S2_GETGLOBALSTATS:     debug(("PerformIO: call to S2_GETGLOBALSTATS\n"))
                                    GetGlobalStats(sdu,ios2);
                                    break;

        case S2_ONEVENT:            debug(("PerformIO: call to S2_ONEVENT\n"))
                                    OnEvent(sdu,ios2);
                                    break;

        case S2_READORPHAN:         debug(("PerformIO: call to S2_READORPHAN\n"))
                                    ReadOrphan(sdu,ios2);
                                    break;

        case S2_ONLINE:             debug(("PerformIO: call to S2_ONLINE\n"))
                                    Online(sdu,ios2);
                                    break;

        case S2_OFFLINE:            debug(("PerformIO: call to S2_OFFLINE\n"))
                                    Offline(sdu,ios2);
                                    break;

        default:                    debug(("PerformIO: call to unknown function\n"))
                                    ios2->ios2_Req.io_Error = IOERR_NOCMD;
                                    TermIO(ios2);
                                    break;
    }
}

/*
** This function returns any device specific statistics that
** we may have.  Unfortunately, we don't have any SPAR specific
** statistics.
*/
VOID GetSpecialStats(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    struct Sana2SpecialStatHeader *stats;

    stats = (struct Sana2SpecialStatHeader *)ios2->ios2_StatData;

    stats->RecordCountSupplied = 0;
    TermIO(ios2);
}

/*
** This function returns the global statistics for the
** spar device.
*/
VOID GetGlobalStats(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
  struct Sana2DeviceStats *stats;

  if (stats = (struct Sana2DeviceStats *)ios2->ios2_StatData)
  {
    stats->PacketsReceived      = sdu->sdu_Stats.PacketsReceived;
    stats->PacketsSent          = sdu->sdu_Stats.PacketsSent;
    stats->BadData              = sdu->sdu_Stats.BadData;
    stats->Overruns             = sdu->sdu_Stats.Overruns;
    stats->UnknownTypesReceived = sdu->sdu_Stats.UnknownTypesReceived;
    stats->Reconfigurations     = sdu->sdu_Stats.Reconfigurations;
    stats->LastStart.tv_secs    = sdu->sdu_Stats.LastStart.tv_secs;
    stats->LastStart.tv_micro   = sdu->sdu_Stats.LastStart.tv_micro; /* Rhialto */

    ios2->ios2_Req.io_Error = 0;
    ios2->ios2_WireError = 0;
  }
  else
  {
    ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT;
    ios2->ios2_WireError = S2WERR_NULL_POINTER;
  }
  TermIO(ios2);
}

/*
** This function returns statistics for a specific
** type of packet that is being tracked.
**
*/
VOID GetTypeStats(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
  struct Sana2PacketTypeStats *stats;
  struct SuperS2PTStats *sstats;

  if (stats = (struct Sana2PacketTypeStats *)ios2->ios2_StatData)
  {
    ObtainSemaphore(&sdu->sdu_ListLock);

    sstats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;

    while(sstats->ss_Node.mln_Succ)
    {
      /* Is this the desired packet type? */
      if(ios2->ios2_PacketType == sstats->ss_PType)
      {
          stats->PacketsSent     = sstats->ss_Stats.PacketsSent;
          stats->PacketsReceived = sstats->ss_Stats.PacketsReceived;
          stats->BytesSent       = sstats->ss_Stats.BytesSent;
          stats->BytesReceived   = sstats->ss_Stats.BytesReceived;
          stats->PacketsDropped  = sstats->ss_Stats.PacketsDropped;
          break;
      }
      sstats = (struct SuperS2PTStats *)sstats->ss_Node.mln_Succ;
    }
    ReleaseSemaphore(&sdu->sdu_ListLock);
    if(!sstats->ss_Node.mln_Succ)
    {
      ios2->ios2_Req.io_Error = S2ERR_BAD_STATE;
      ios2->ios2_WireError = S2WERR_NOT_TRACKED;
    }
  }
  else
  {
    ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT;
    ios2->ios2_WireError = S2WERR_NULL_POINTER;
  }
  TermIO(ios2);
}

/*
** This function adds a packet type to the list
** of those that are being tracked.
*/
VOID TrackType(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    BOOL TypeFound=FALSE;
    struct SuperS2PTStats *stats;

    ObtainSemaphore(&sdu->sdu_ListLock);

    stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;

    /* Check in the list is this packet is already been tracked */
    while(stats->ss_Node.mln_Succ)
    {
      if(ios2->ios2_PacketType == stats->ss_PType)
      {
        ios2->ios2_Req.io_Error = S2ERR_BAD_STATE;
        ios2->ios2_WireError = S2WERR_ALREADY_TRACKED;
        TypeFound=TRUE;
        break;
      }
      stats = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
    }


    if(!TypeFound) /* Type not in list? */
      if(stats = AllocMem(sizeof(struct SuperS2PTStats),MEMF_CLEAR|MEMF_PUBLIC))
      {   /* Add packet type to the list */
          sdu->sdu_TrackP = TRUE;   
          stats->ss_PType = ios2->ios2_PacketType;

          debug(("TrackType: Added Type: %04lx.\n",ios2->ios2_PacketType))

          AddTail((struct List *)&sdu->sdu_Track,(struct Node *)stats);
      }
    ReleaseSemaphore(&sdu->sdu_ListLock);

    TermIO(ios2);
}

/*
** This function removes a packet type from the
** list of those that are being tracked.
*/
VOID UnTrackType(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    struct SuperS2PTStats *stats;
    struct SuperS2PTStats *stats_next;
    BOOL TypeFound = FALSE;

    ObtainSemaphore(&sdu->sdu_ListLock);

    stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;

    while(stats->ss_Node.mln_Succ)
    {
      stats_next = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
      if(ios2->ios2_PacketType == stats->ss_PType)
      {
          Remove((struct Node *)stats);
          FreeMem(stats,sizeof(struct SuperS2PTStats));
          TypeFound = TRUE;
          break;
      }
      stats = stats_next;
    }
    if(!TypeFound) /* Type not found in list */
    {
      ios2->ios2_Req.io_Error  = S2ERR_BAD_STATE;
      ios2->ios2_WireError = S2WERR_NOT_TRACKED;
    }
    if(!stats->ss_Node.mln_Succ) /* list is empty    */
        sdu->sdu_TrackP=FALSE;   /* reset track flag  */
    ReleaseSemaphore(&sdu->sdu_ListLock);

    TermIO(ios2);
}

/*
** This function is called whenever we receive a packet
** from the PARnet device driver.
**
*/
VOID PacketReceived(struct SPARDevUnit *sdu, ULONG length)
{
    struct SuperS2PTStats *stats;

    sdu->sdu_Stats.PacketsReceived++;

    /* Packet tracking enabled ? */
    if(sdu->sdu_TrackP)
    {
      stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;

      /* Check if this packet type is on the tracking list */
      while(stats->ss_Node.mln_Succ)
      {
        if( *(UWORD *)(sdu->sdu_RxBuff+SADDR_LEN*2) == stats->ss_PType)
        {
          /* Found entry for this packet type, updating stats */
          stats->ss_Stats.PacketsReceived++;
          stats->ss_Stats.BytesReceived+=length;
          break;
        }
        stats = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
      }
    }
}

/*
** This function is called whenever a packet is
** sent to the PARnet device driver.
*/
VOID PacketSent(struct SPARDevUnit *sdu, ULONG length)
{
    struct SuperS2PTStats *stats;

    sdu->sdu_Stats.PacketsSent++;

    /* Packet tracking enabled ? */
    if(sdu->sdu_TrackP)
    {
      stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;

      /* Check if this packet type is on the tracking list */
      while(stats->ss_Node.mln_Succ)
      {

        if( *(UWORD *)(sdu->sdu_TxBuff+SADDR_LEN*2) == (UWORD)stats->ss_PType)
        {
          /* Found entry for this packet type, updating stats */

          debug(("PacketSent: Tracked packet type: %04lx.\n",stats->ss_PType))

          stats->ss_Stats.PacketsSent++;
          stats->ss_Stats.BytesSent+=length;
          break;
        }
        stats = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
      }
    }
}

/*
** This function is called whenever a packet that
** is too large is received.
*/
VOID PacketOverrun(struct SPARDevUnit *sdu)
{
    sdu->sdu_Stats.Overruns++;
    DoEvent(sdu, S2EVENT_RX);
}

/*
** This function is called whenever a packet with
** garbage data is encountered.
*/
VOID ReceivedGarbage(struct SPARDevUnit *sdu)
{
    sdu->sdu_Stats.BadData++;
    DoEvent(sdu, S2EVENT_RX);
}

/*
** This function is called whenever a packet
** is dropped by the SPAR driver.
*/
VOID PacketDropped(struct SPARDevUnit *sdu)
{
    struct SuperS2PTStats *stats;

    /* Packet tracking enabled ? */
    if(sdu->sdu_TrackP)
    {
      stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;

      /* Check if this packet type is on the tracking list */
      while(stats->ss_Node.mln_Succ)
      {

        if( *(UWORD *)(sdu->sdu_TxBuff+SADDR_LEN*2) == (UWORD)stats->ss_PType)
        {
          /* Found entry for this packet type, updating stats */
          stats->ss_Stats.PacketsDropped++;
          break;
        }
        stats = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
      }
    }
}

/*
** This function is called whenever an orphan packet
** is received (Routine from the agnet device).
*/
VOID 
ReceivedOrphan(struct SPARDevUnit *sdu)
{
  sdu->sdu_Stats.UnknownTypesReceived++;
}

/*
** This function is used to locate an IO request in a linked
** list and abort it if found.
*/
LONG AbortReq(struct MinList *minlist, struct IOSana2Req *ios2)
{
    struct Node *node, *next;
    LONG result=IOERR_NOCMD;

    node = (struct Node *)minlist->mlh_Head;

    while(node->ln_Succ)
    {
      next = node->ln_Succ;

      if(node == (struct Node *)ios2)
      {
          Remove((struct Node *)ios2);
          ios2->ios2_Req.io_Error = IOERR_ABORTED;
          TermIO(ios2);
          result = 0;
      }
      node = next;
    }
    return(result);
}

/*
** This function handles S2_CONFIGINTERFACE commands.
*/
VOID ConfigInterface(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    UBYTE tempaddr;               /* unvalidated hardware address */

    /* Note: we may only be configured once. */
    if(!(sdu->sdu_State & SPARUF_CONFIG))
    {
        /* A PARnet network can only handle addresses of 1 octet, so
         * only the last octet of the given ethernet address is used (an
         * ethernetaddress occupies the first 6 bytes in ios2_SrcAddr) */
        tempaddr = ios2->ios2_SrcAddr[0];

        debug(("ConfigInterface: Hw.addr. set to :%d (%08lx%04x).\n",(LONG)tempaddr,*(LONG *)ios2->ios2_SrcAddr,*(UWORD *)(ios2->ios2_SrcAddr+4)))

        /* check the address before using, take over when it's ok. */
        if ((tempaddr!=0) && (tempaddr!=255))
                  sdu->sdu_StAddr = tempaddr;
        
        sdu->sdu_State |= SPARUF_CONFIG;

        if(!(OpenPARnet(sdu)))
        { /* failed to open */
          sdu->sdu_State &= ~SPARUF_CONFIG;
          ios2->ios2_Req.io_Error = IOERR_OPENFAIL;
          ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
        }
    }
    else
    {
        /* Sorry, we're already configured. */
        ios2->ios2_Req.io_Error = S2ERR_BAD_STATE;
        ios2->ios2_WireError = S2WERR_IS_CONFIGURED;
    }
    TermIO(ios2);
}


/*
** This function handles S2_BROADCAST commands.
** It is very limited, because PARnet does not support packet broadcast.
** Only ARP broadcasts will be accepted and processed internally.
** 
*/
VOID BroadcastPacket(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
   struct BufferManagement *bm;
   struct ARPframePacket *ARPreqPacket;
   /* ARP response packet */
   struct ARPframePacket ARPrespPacket = {0,0,ETHERTYPE_ARP,ARPHRD_ETHER,ETHERTYPE_IP,6,4,ARPOP_REPLY,0,0,0,0};

   if (sdu->sdu_DestAddr)
   { 
     memset(ios2->ios2_DstAddr, 0, SANA2_MAX_ADDR_BYTES); /* Clear dest.addr. */
     ios2->ios2_DstAddr[0]=sdu->sdu_DestAddr; /* fill in destination address */
     WritePacket(sdu,ios2); /* Just write it to the other end */
   }
   else
   {
    bm =(struct BufferManagement *) ios2->ios2_BufferManagement;

    /* Copy the data out of the packet into our temporary buffer. */
    if((*bm->bm_CopyFromBuffer)(sdu->sdu_RxBuff+SHDR_LEN,ios2->ios2_Data,ios2->ios2_DataLength))
    {
      ARPreqPacket=(struct ARPframePacket *)sdu->sdu_RxBuff;  /* Pointer to ARP frame  */

      if ((UWORD)ios2->ios2_PacketType == ETHERTYPE_ARP)       /* ARP broadcast? */
      {
        debug(("ARP Req (%ld): %08lx%08lx%08lx%08lx%08lx%08lx",ios2->ios2_DataLength,*(LONG *)sdu->sdu_RxBuff,*(LONG *)(sdu->sdu_RxBuff+4),*(LONG *)(sdu->sdu_RxBuff+8),*(LONG *)(sdu->sdu_RxBuff+12),*(LONG *)(sdu->sdu_RxBuff+16),*(LONG *)(sdu->sdu_RxBuff+20)))

        ARPrespPacket.ar_dstaddr=sdu->sdu_StAddr; /* frame target h/w address (myself) */
                                                  /* frame type already filled in
                                                   * with ETHERTYPE_ARP as default */

        /* copy sender's addresses to target address fields */
        ARPrespPacket.ar_tpa=ARPreqPacket->ar_spa;
        memcpy(&ARPrespPacket.ar_tha,ARPreqPacket->ar_sha,EADDR_LEN);

        /* copy target IP address to sender's IP address field */

        ARPrespPacket.ar_spa=ARPreqPacket->ar_tpa;

        /* calculate the target hardware address from the last octet of the
         * target IP address */

        ARPrespPacket.ar_sha[0]=

        /* Fill in also hardware source address */
        ARPrespPacket.ar_srcaddr= (UBYTE)ARPrespPacket.ar_spa;

        /* copy packet to receiver buffer */
        memcpy(sdu->sdu_RxBuff,&ARPrespPacket,sizeof(ARPrespPacket));

        debug(("Tx ARP resp (%ld): %08lx%08lx%08lx%08lx%08lx%08lx",sizeof(ARPrespPacket),*(LONG *)sdu->sdu_RxBuff,*(LONG *)(sdu->sdu_RxBuff+4),*(LONG *)(sdu->sdu_RxBuff+8),*(LONG *)(sdu->sdu_RxBuff+12),*(LONG *)(sdu->sdu_RxBuff+16),*(LONG *)(sdu->sdu_RxBuff+20)))

        GotPacket(sdu,sizeof(ARPrespPacket));
      }
      else                  /* non-ARP broadcast packet */
      {
        debug(("Unsupported broadcast packet type\n"))
        PacketDropped(sdu); /* Can't handle address 255 */    
      }
    }
    else
    {
       /* Something went wrong...*/
       ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
       ios2->ios2_WireError = S2WERR_BUFF_ERROR;
       DoEvent(sdu,S2EVENT_BUFF);
    }
    TermIO(ios2);
  }
}

/*
** This function handles S2_GETSTATIONADDRESS commands.
**
** PARnet uses only 1 octet, so only the 1th byte in ios2_SrcAddr will be
** filled.
**
** Rhialto: What we want is the config file address as hardware
**	    address, and the S2_CONFIGINTERFACE address as current address.
**
*/

VOID GetStationAddress(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    /* Clear address space */
    memset(ios2->ios2_DstAddr, 0, SANA2_MAX_ADDR_BYTES);
    memset(ios2->ios2_SrcAddr, 0, SANA2_MAX_ADDR_BYTES);

    /* Place our current address in first position of Src_Addr.
     * And our hardware address (given in spar#.config) in Dst_Addr.
     */
    ios2->ios2_SrcAddr[0]=sdu->sdu_StAddr;
    ios2->ios2_DstAddr[0]=sdu->sdu_HwAddr;

    debug(("GetStationAddr: %08lx%04lx.\n",*(LONG *)ios2->ios2_SrcAddr,*(UWORD *)(ios2->ios2_SrcAddr+4)))

    TermIO(ios2);
}

/*
** This function handles S2_DEVICEQUERY comands.
*/
VOID DeviceQuery(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    struct Sana2DeviceQuery *sdq;

    sdq = (struct Sana2DeviceQuery *)ios2->ios2_StatData;

    sdq->AddrFieldSize = 48;                      /* six bytes (48 bits) hardware address */
                                                  /* this is for Ethernet compatibility, */
                                                  /* Spar uses 1 byte h/w addresses. */
    sdq->MTU = SPAR_MTU;                          /* max. trans. unit */
    sdq->BPS = SPAR_SPEED;                        /* PARnet has fixed speed */
    sdq->HardwareType = S2WireType_Ethernet;      /* fake! */

    sdq->DevQueryFormat = NULL;
    sdq->DeviceLevel    = NULL;
    sdq->SizeSupplied   = sizeof(struct Sana2DeviceQuery);
    TermIO(ios2);
}

/*
** This function is used for handling CMD_WRITE
** commands.
*/
VOID WritePacket(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{

    /* Make sure that we are online. */
    if(sdu->sdu_State & SPARUF_ONLINE)
    {
      /* Make sure it's a legal length. */
      if(ios2->ios2_DataLength <= SPAR_MTU)
      {
          /* See if our PARnet CMD_WRITE command is busy.  If it's not, send
           * the IO request to SendPacket. */
          if(CheckIO((struct IORequest *)sdu->sdu_ParTx))
          {
            WaitIO((struct IORequest *)sdu->sdu_ParTx);
            SendPacket(sdu, ios2);
          }
          else
          {
            /* We'll have to queue the packet for later...*/
            ios2->ios2_Req.io_Flags &= ~IOF_QUICK;
            ObtainSemaphore(&sdu->sdu_ListLock);
            AddTail((struct List *)&sdu->sdu_Tx,(struct Node *)ios2);
            ReleaseSemaphore(&sdu->sdu_ListLock);
          }
      }
      else
      {
          /* Sorry, the packet is too long! */
          ios2->ios2_Req.io_Error = S2ERR_MTU_EXCEEDED;
          ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
          TermIO(ios2);
          DoEvent(sdu,S2EVENT_TX);
      }
    }
    else
    {
      /* Sorry, we're offline */
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      TermIO(ios2);
    }
}

/*
** This routine handles CMD_READ commands.  We
** always queue these unless we're offline.
*/
VOID ReadPacket(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{

    if(sdu->sdu_State & SPARUF_ONLINE)
    {
      /* Queue it... */
      ObtainSemaphore(&sdu->sdu_ListLock);
      AddTail((struct List *)&sdu->sdu_Rx,(struct Node *)ios2);
      ReleaseSemaphore(&sdu->sdu_ListLock);
    }
    else
    {
      /* Sorry, we're offline */
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      TermIO(ios2);
    }
}

/*
** This routine handles CMD_READORPHAN commands.  We
** always queue these unless we're offline.
**
*/
VOID ReadOrphan(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{

    if(sdu->sdu_State & SPARUF_ONLINE)
    {
      /* Queue it...*/
      ObtainSemaphore(&sdu->sdu_ListLock);
      AddTail((struct List *)&sdu->sdu_RxOrph,(struct Node *)ios2);
      ReleaseSemaphore(&sdu->sdu_ListLock);
    }
    else
    {
      /* Sorry, we're offline */
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      TermIO(ios2);
    }
}

/*
** This routine handles S2_ONEVENT commands.
*/
VOID OnEvent(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{

    switch(ios2->ios2_WireError)
    {
      /* Special case.  We may already be online, in which
       * case the IO request should return immediately. Otherwise
       * we queue it for later. */

      case S2EVENT_ONLINE:
            if(sdu->sdu_State & SPARUF_ONLINE)
                TermIO(ios2);
            else
            {
                ObtainSemaphore(&sdu->sdu_ListLock);
                AddTail((struct List *)&sdu->sdu_Events,(struct Node *)ios2);
                ReleaseSemaphore(&sdu->sdu_ListLock);
            }
            break;

      /* Same as with S2EVENT_ONLINE, but the opposite
       * happens. */
      case S2EVENT_OFFLINE:
        if(sdu->sdu_State & SPARUF_ONLINE)
        {
            ObtainSemaphore(&sdu->sdu_ListLock);
            AddTail((struct List *)&sdu->sdu_Events,(struct Node *)ios2);
            ReleaseSemaphore(&sdu->sdu_ListLock);
        }
        else
            TermIO(ios2);
        break;

      /* Just queue everything else. */
      default:
            ObtainSemaphore(&sdu->sdu_ListLock);
            AddTail((struct List *)&sdu->sdu_Events,(struct Node *)ios2);
            ReleaseSemaphore(&sdu->sdu_ListLock);
            break;
    }
}

/*
** This routine gets the current system time and stores
** it in our global statistics structure.
*/

VOID MarkTimeOnline(struct SPARDevUnit *sdu)
{
    register struct Library *TimerBase;
    struct timerequest *treq;

    if(treq = (struct timerequest *)AllocMem(sizeof(struct timerequest),MEMF_PUBLIC|MEMF_CLEAR))
    {
      if(!OpenDevice("timer.device",UNIT_MICROHZ,(struct IORequest *)treq,NULL))
      {
          TimerBase = (struct Library *)treq->tr_node.io_Device;
          GetSysTime(&sdu->sdu_Stats.LastStart);
          CloseDevice((struct IORequest *)treq);
      }
      FreeMem(treq,sizeof(struct timerequest));
    }
}

/*
** This routine handles CMD_ONLINE commands.
*/
VOID Online(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{

    if(!(sdu->sdu_State & SPARUF_ONLINE))
    {
      /* We're offline. Try to go online. */
      if(OpenPARnet(sdu))
      {
          if(sdu->sdu_State & SPARUF_ONLINE)
          {
            /* In case someone wants to know...*/
            DoEvent(sdu, S2EVENT_ONLINE);
          }
          else
          {
            /* Sorry, the attempt to go online failed. */
            ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
            ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
          }
        }
        else
        {
            /* A general problem occured. */
            ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
            ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
        }
    }
    TermIO(ios2);
}

/*
** This routine handles CMD_OFFLINE commands.
*/
VOID Offline(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    TermIO(ios2);

    if(sdu->sdu_State & SPARUF_ONLINE)
    {
      /* We're online, so shut everything down. */
      ClosePARnet(sdu);
      DoOffline(sdu);
      DoEvent(sdu,S2EVENT_OFFLINE);
    }
}

/*
** This routine is called whenever an "important"
** SANA-II event occurs.
*/
VOID DoEvent(struct SPARDevUnit *sdu, ULONG event)
{
    struct IOSana2Req *ios2;
    struct IOSana2Req *ios2_next;

    ObtainSemaphore(&sdu->sdu_ListLock);

    ios2 = (struct IOSana2Req *)sdu->sdu_Events.mlh_Head;

    while(ios2->ios2_Req.io_Message.mn_Node.ln_Succ)
    {
      ios2_next = (struct IOSana2Req *)ios2->ios2_Req.io_Message.mn_Node.ln_Succ;

      /* Is this the event they are looking for? */
      if(ios2->ios2_WireError == event)
      {
          Remove((struct Node *)ios2);
          TermIO(ios2);
      }
      ios2 = ios2_next;
    }
    ReleaseSemaphore(&sdu->sdu_ListLock);
}

/*
** This routine is called whenever the device needs to
** be taken offline.  We return any pending CMD_READ's
** or CMD_WRITE's to their senders.
*/
VOID DoOffline(struct SPARDevUnit *sdu)
{
    struct IOSana2Req *ios2;

    ObtainSemaphore(&sdu->sdu_ListLock);

    while(ios2 = (struct IOSana2Req *)RemHead((struct List *)&sdu->sdu_Rx))
    {
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      TermIO(ios2);
    }

    while(ios2 = (struct IOSana2Req *)RemHead((struct List *)&sdu->sdu_Tx))
    {
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      TermIO(ios2);
    }
    ReleaseSemaphore(&sdu->sdu_ListLock);
}
