/*
** $Source: dh1:network/parnet/Sana2/device_funcs.c,v $
** $State: Exp $
** $Revision: 37.1 $
** $Date: 93/08/24 20:40:39 $
** $Author: sap  $
**
** Amiga SANA-II Example PARnet device driver.
**
** Based on the Amiga SANA-II Example SLIP device driver code by bj.
**
** Portions (C) Copyright 1992 Commodore-Amiga, Inc.
*/

#include "spar_device.h"

#include <dos/dostags.h>
#include <dos/rdargs.h>
#include <intuition/intuition.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/utility_protos.h>
#include <clib/timer_protos.h>
#include <clib/alib_stdio_protos.h>
#include <clib/intuition_protos.h>

#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/timer_pragmas.h>
#include <pragmas/intuition_pragmas.h>

#include <string.h>
#include "device_protos.h"

/*
** External variables and functions
**
*/

/* the __far keyword is only needed for data structures which are
** in a different segment. Otherwise the linker will complain.
*/
__far extern struct Library *ExtDeviceBase;
__far extern VOID (*DevProcEntry)(VOID);
extern ULONG IPToNum(STRPTR ipstr);
extern VOID kprintf(STRPTR fmt, ...);

/*
** Device Open vector
**
** a1 - SANA2 IO Request
** a6 - Pointer to our device base
** d0 - Unit number
** d1 - Flags
**
*/

LONG ASM DevOpen(REG(a1) struct IOSana2Req *ios2,
                 REG(d0) ULONG s2unit,
                 REG(d1) ULONG s2flags)
{
    struct SPARDevice *SPARDevice = SPARBase; /* device base in register a6 */
    struct SPARDevUnit *sdu;     /* our unit structure (it could already have been
                                  * initialized on a previous DevOpen() call) */
    struct TagItem *bufftag;     /* Tags for the SANA-II buffer management functions */
    struct Library *UtilityBase; /* utility.library is used for the Tag functions */
    struct BufferManagement *bm;
    LONG returncode;
    BOOL status = FALSE;

    /* Make sure our open remains single-threaded. */
    ObtainSemaphore(&SPARDevice->sd_Lock); /* Enforce single threading since we may need to
                                            * Wait() when starting up the Unit process */

    SPARDevice->sd_Device.lib_OpenCnt++;   /* So we won't expunge ourselves... */

    if(s2unit < SD_MAXUNITS)               /* Legal Unit number? */
    {
      if(sdu = InitSPARUnit(s2unit))       /* Initialize the Unit */
      {
        if(UtilityBase = OpenLibrary("utility.library",37L)) /* For Tag functions */
        {
        /* Allocate a structure to store the pointers to the callback routines. */

        if(bm = AllocMem(sizeof(struct BufferManagement),MEMF_CLEAR|MEMF_PUBLIC))
        {
            /* Note: I don't complain if I can't find pointers to the callback routines.
             * This is because there are some programs that may need to open me, but
             * will never use any device commands that require the callbacks. */

            if(bufftag = FindTagItem(S2_CopyToBuff, (struct TagItem *)ios2->ios2_BufferManagement))
                bm->bm_CopyToBuffer = (SANA2_CTB) bufftag->ti_Data;

            if(bufftag = FindTagItem(S2_CopyFromBuff, (struct TagItem *)ios2->ios2_BufferManagement))
                bm->bm_CopyFromBuffer = (SANA2_CFB) bufftag->ti_Data;

            AddTail((struct List *)&sdu->sdu_BuffMgmt,(struct Node *)bm);

            /* Everything went okay. */
            status = TRUE;
            returncode = 0;
            SPARDevice->sd_Device.lib_OpenCnt++;
            SPARDevice->sd_Device.lib_Flags &=~LIBF_DELEXP;
            sdu->sdu_Unit.unit_OpenCnt++;

            /* Fix up the initial io request */
            ios2->ios2_BufferManagement = (VOID *)bm;
            ios2->ios2_Req.io_Error = 0;
            ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
            ios2->ios2_Req.io_Unit = (struct Unit *)sdu;
            ios2->ios2_Req.io_Device = (struct Device *)SPARDevice;
        }
        CloseLibrary(UtilityBase);
      }
     }
    }

    /* See if something went wrong. */
    if(!status)
    {
      ios2->ios2_Req.io_Error = IOERR_OPENFAIL;
      ios2->ios2_Req.io_Unit = (struct Unit *) -1;
      ios2->ios2_Req.io_Device = (struct Device *) -1;
      returncode = IOERR_OPENFAIL;
    }
    SPARDevice->sd_Device.lib_OpenCnt--;
    ReleaseSemaphore(&SPARDevice->sd_Lock);

    return(returncode);
}

/*
** Device Close vector.
**
** a1 - IOReq
** a6 - Device Pointer
**
** There are two different things that might be returned from the Close
** routine.  If the device wishes to be unloaded, the Close should return
** the segment list (as given to DevInit).  Otherwise close MUST return NULL.
*/

BPTR ASM DevClose(REG(a1) struct IOSana2Req *ios2)
{
    struct SPARDevice *SPARDevice = SPARBase;
    struct SPARDevUnit *sdu;
    BPTR seglist = 0L;

    ObtainSemaphore(&SPARDevice->sd_Lock);

    sdu = (struct SPARDevUnit *)ios2->ios2_Req.io_Unit;

    /* I always shut the unit process down if the open count drops to zero.
     * That way, if I need to expunge, I never have to Wait(). */

    sdu->sdu_Unit.unit_OpenCnt--;
    if(!sdu->sdu_Unit.unit_OpenCnt)
      ExpungeUnit(sdu);

    /* Trash the io_Device and io_Unit fields so that any attempt to use this
     * request will die immediatly. */

    ios2->ios2_Req.io_Device = (struct Device *) -1;
    ios2->ios2_Req.io_Unit = (struct Unit *) -1;

    SPARDevice->sd_Device.lib_OpenCnt--;

    ReleaseSemaphore(&SPARDevice->sd_Lock);

    /* Check to see if we've been asked to expunge. */
    if(SPARDevice->sd_Device.lib_Flags & LIBF_DELEXP)
      seglist = DevExpunge();

    return(seglist);
}


/*
** Device Expunge vector
**
** a6 - Device base
**
** There are two different things that might be returned from the Expunge
** routine.  If the device is no longer open then Expunge should return the
** segment list (as given to DevInit). Otherwise Expunge should set the
** delayed expunge flag (LIBF_DELEXP) and return NULL.
**
** One other important note: You may NEVER EVER Wait() in expunge. Period.
** This is because Expunge is called from the memory allocator, A Wait()
** call would take too long time to complete.
*/

BPTR ASM DevExpunge(VOID)
{
    struct SPARDevice *SPARDevice = SPARBase;
    BPTR seglist;
    ULONG devbase;
    LONG devbasesize;

    if(SPARDevice->sd_Device.lib_OpenCnt)
    {
      /* Sorry, we're busy.  We'll expunge later on if we can. */
      SPARDevice->sd_Device.lib_Flags |= LIBF_DELEXP;
      seglist = (BPTR)0L;
    }
    else
    {
      /* Free up our library base and function table after
       * removing ourselves from the library list. */
      Remove((struct Node *)SPARDevice);
      seglist = SPARDevice->sd_SegList;

      /* Calculate the amount of memory to be FreeMem'ed */
      devbase = (ULONG) SPARDevice;

      devbasesize = (ULONG)SPARDevice->sd_Device.lib_NegSize;
      devbase = devbase - devbasesize;

      devbasesize += (ULONG)SPARDevice->sd_Device.lib_PosSize;

      FreeMem((APTR)devbase,devbasesize);
    }
    return(seglist);
}

/*
** InitSPARUnit
**
** Initialize (if needed) a new SPAR device Unit and process.
**
*/

struct SPARDevUnit *InitSPARUnit(ULONG s2unit)
{
    struct SPARDevice *SPARDevice = SPARBase;
    struct SPARDevUnit *sdu;
    struct TagItem NPTags[]={NP_Entry, 0, NP_Name, 0, NP_Priority, SPAR_PRI, TAG_DONE, 0};
    struct MsgPort *replyport;

    /* Check to see if the Unit is already up and running.  If
     * it is, just drop through.  If not, try to start it up. */

    if(!SPARDevice->sd_Units[s2unit])
    {
      /* Open up dos.library */
      if(SPARDevice->sd_DOSBase = OpenLibrary("dos.library",37L))
      {
            /* Allocate a new Unit structure */
            if(sdu = AllocMem(sizeof(struct SPARDevUnit), MEMF_CLEAR|MEMF_PUBLIC))
            {
                /* Do some initialization on the Unit structure.
                 * We set the Unit's message port to PA_IGNORE until the
                 * new process has a change to set it up.
                 */
                NewList(&sdu->sdu_Unit.unit_MsgPort.mp_MsgList);
                sdu->sdu_Unit.unit_MsgPort.mp_Node.ln_Type = NT_MSGPORT;
                sdu->sdu_Unit.unit_MsgPort.mp_Flags = PA_IGNORE;
                sdu->sdu_Unit.unit_MsgPort.mp_Node.ln_Name = "spar.device";

                sdu->sdu_UnitNum = s2unit;
                sdu->sdu_Device = (struct Device *) SPARDevice;
                sdu->sdu_State = 0;                      /* status: nothing */

                /* Try to read in our configuration file */
                if(ReadConfig(sdu))
                {
                    /* Start up the unit process */
                    if(replyport = CreateMsgPort())
                    {
                        SPARDevice->sd_Startup.Msg.mn_ReplyPort = replyport;
                        SPARDevice->sd_Startup.Device = (struct Device *) SPARDevice;
                        SPARDevice->sd_Startup.Unit = (struct Unit *)sdu;

                        NPTags[0].ti_Data = (ULONG) &DevProcEntry;  /* Assembly entry point for the unit process. */
                        NPTags[1].ti_Data = (ULONG) "spar.device";  /* Process name */

                        ExtDeviceBase = (struct Library *)SPARDevice;

                        if(sdu->sdu_Proc = CreateNewProc(NPTags))
                        {
                            /* Wait for process setup completion (see DevProcCEntry) */
                            PutMsg(&sdu->sdu_Proc->pr_MsgPort,(struct Message *)&SPARDevice->sd_Startup);
                            WaitPort(replyport);
                            GetMsg(replyport);
                        }
                        DeleteMsgPort(replyport);
                    }
                }

                if(!sdu->sdu_Proc)
                    /* The Unit process couldn't start for some reason, so free the Unit structure. */
                    FreeMem(sdu,sizeof(struct SPARDevUnit));
                else
                    /* Set up the Unit structure pointer in the device base */
                    SPARDevice->sd_Units[s2unit] = (struct Unit *)sdu;
            }
            CloseLibrary(SPARDevice->sd_DOSBase);
        }
    }
    return((struct SPARDevUnit *)SPARDevice->sd_Units[s2unit]);
}

/*
**
** ExpungeUnit
**
** Tells a unit process to go away...
**
** This function is called from the DevClose routine when the open count for a
** unit reaches zero.  This routine signals the unit process to exit and then
** waits for the unit process to acknowledge.  The unit structure is then
** freed.
*/

VOID ExpungeUnit(struct SPARDevUnit *sdu)
{
    struct SPARDevice *SPARDevice = SPARBase;
    struct Task *unittask;

    unittask = (struct Task *)sdu->sdu_Proc;

    sdu->sdu_Proc = (struct Process *)FindTask(0L);

    Signal(unittask,SIGBREAKF_CTRL_F);
    Wait(SIGBREAKF_CTRL_F);

    SPARDevice->sd_Units[sdu->sdu_UnitNum] = NULL;

    FreeMem(sdu, sizeof(struct SPARDevUnit));
}


/*
**
** ReadConfig
**
** Attempt to read in and parse the driver's configuration file.
**
** The files are named by ENV:SANA2/sparX.config where X is the decimal
** representation of the device's unit number.
**
*/

BOOL ReadConfig(struct SPARDevUnit *sdu)
{
    UBYTE *linebuff,buff[40]; /* temporary linebuffer & filename */
    STRPTR termchar;          /* line terminator */
    struct RDArgs *rdargs;
    BPTR ConfigFile;          /* DOS file desciptor */
    LONG args[3];             /* arguments to read from the file */
    BOOL status = FALSE;
    ULONG linenum=0;
    UWORD i;

    struct Library *IntuitionBase;
    struct EasyStruct es;

    /* initialize structure for a possible error message */
    es.es_StructSize=sizeof(struct EasyStruct);
    es.es_Flags=0;
    es.es_Title="spar.device";
    es.es_GadgetFormat="Okay";

    /* Create the name of our config file.. */
    sprintf(buff,"ENV:SANA2/spar%ld.config",(ULONG)sdu->sdu_UnitNum);

    /* ...and open it. */
    if(ConfigFile = Open(buff,MODE_OLDFILE))
    {
      /* Here, I use ReadArgs() to do the file parsing for me. */

      if(linebuff = AllocMem(256,MEMF_CLEAR|MEMF_PUBLIC))
      {
          if(rdargs = AllocDosObject(DOS_RDARGS, NULL))
          {
            while(FGets(ConfigFile, linebuff, 255))
            {
                linenum++;

                if(linebuff[0] == '#') /* Skip comment lines */
                    continue;

                rdargs->RDA_Source.CS_Buffer = linebuff;
                rdargs->RDA_Source.CS_Length = 256;
                rdargs->RDA_Source.CS_CurChr = 0;
                rdargs->RDA_DAList = NULL;  /* MUST be initalized to NULL */

                /* ReadArgs() requires that the line be null-terminated
                 * or funny things happen. */

                termchar = (STRPTR) linebuff + strlen(linebuff);
                *termchar = '\n';
                termchar++;
                *termchar = 0;

                for(i = 0; i< 3; i++) args[i]=0; /* clear argument list */

                /* Parse the line...*/

                if(ReadArgs("PARNAME/A,PARUNIT/A/N,PARADDR/A/N",args,rdargs))
                {
                  strcpy(sdu->sdu_ParDevName,(STRPTR)args[0]);
                  sdu->sdu_ParUnitNum = *((ULONG *)args[1]);
                  sdu->sdu_StAddr = (UWORD) *((ULONG *)args[2]);

                  /* check hardware address */
                  if ((sdu->sdu_StAddr!=0) && (sdu->sdu_StAddr!=255))
                    status = TRUE;
                  else
                    /* send a notification to the user */
                    if(IntuitionBase = OpenLibrary("intuition.library",37L))
                    {
                      es.es_TextFormat="Invalid hardware address (%d) given.";
                      EasyRequestArgs(NULL, &es, 0, &sdu->sdu_StAddr);
                      CloseLibrary(IntuitionBase);
                    }
                  FreeArgs(rdargs);
                  break;
                }
                else
                {
                   /* Produce an error message in a requester */
                   if(IntuitionBase = OpenLibrary("intuition.library",37L))
                   {
                     es.es_TextFormat="Error in configuration file on line %ld.";
                     EasyRequestArgs(NULL, &es, 0, &linenum);
                     CloseLibrary(IntuitionBase);
                   }
                   break;
                }
            }
            FreeDosObject(DOS_RDARGS,rdargs);
        }
        FreeMem(linebuff, 256);
      }
      Close(ConfigFile);
    }
  return(status);
}                 

/*
**
** BeginIO
**
** This is the dispatch point for the driver's incoming IORequests.
**
** BeginIO starts all incoming IO.  The IO is either queued up for the
** unit task or processed immediately.
**
** IMPORTANT:
** The exec WaitIO() function uses the IORequest node type (LN_TYPE)
** as a flag.  If set to NT_MESSAGE, it assumes the request is
** still pending and will wait.  If set to NT_REPLYMSG, it assumes the
** request is finished.  It's the responsibility of the device driver
** to set the node type to NT_MESSAGE before returning to the user.
*/

/* This define is used to tell which commands should be handled
 * immediately (on the caller's schedule).
 * In this case, no immediate commands are defined.
 */
#define SPAR_IMMEDIATES 0L

VOID ASM DevBeginIO(REG(a1) struct IOSana2Req *ios2)
{
    /* set node type: request pending */
    ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE;

    /* Check for valid command */
    if(ios2->ios2_Req.io_Command < S2_END)

      /* check if this command can be handled immediately */
      if((1L << ios2->ios2_Req.io_Command) & SPAR_IMMEDIATES)
          PerformIO(ios2); /* Process immediately */
      else
      {
          /* Queue up to the unit task */
          ios2->ios2_Req.io_Flags &= ~IOF_QUICK; /* we did NOT complete this quickly */
          PutMsg((struct MsgPort *)ios2->ios2_Req.io_Unit,(struct Message *)ios2);
      }
    else
    {   /* Unknown device command */
        ios2->ios2_Req.io_Error = IOERR_NOCMD;
        TermIO(ios2);
    }
}

/*
**
** 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 = 0;  /* no error so far */

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

        case CMD_WRITE:             WritePacket(sdu,ios2);
                                    break;

        case S2_DEVICEQUERY:        DeviceQuery(sdu,ios2);
                                    break;

        case S2_GETSTATIONADDRESS:  GetStationAddress(sdu,ios2);
                                    break;

        case S2_CONFIGINTERFACE:    ConfigInterface(sdu,ios2);
                                    break;

        case S2_BROADCAST: /*       WritePacket(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;
                                    TermIO(ios2);
                                    break;

        case S2_TRACKTYPE:          TrackType(sdu,ios2);
                                    break;

        case S2_UNTRACKTYPE:        UnTrackType(sdu,ios2);
                                    break;

        case S2_GETTYPESTATS:       GetTypeStats(sdu,ios2);
                                    break;

        case S2_GETSPECIALSTATS:    GetSpecialStats(sdu,ios2);
                                    break;

        case S2_GETGLOBALSTATS:     GetGlobalStats(sdu,ios2);
                                    break;

        case S2_ONEVENT:            OnEvent(sdu,ios2);
                                    break;

        case S2_READORPHAN:         ReadOrphan(sdu,ios2);
                                    break;

        case S2_ONLINE:             Online(sdu,ios2);
                                    break;

        case S2_OFFLINE:            Offline(sdu,ios2);
                                    break;

        default:                    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;

    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_secs;

    ios2->ios2_Req.io_Error = 0;
    ios2->ios2_WireError = 0;
    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;

    ObtainSemaphore(&sdu->sdu_ListLock);

    stats = (struct Sana2PacketTypeStats *)ios2->ios2_StatData;
    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;
    }
    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;
#ifdef DEBUG
    struct Library *IntuitionBase;
    struct EasyStruct es;
#endif

    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;
#ifdef DEBUG
          if(IntuitionBase = OpenLibrary("intuition.library",37L))
          {
            es.es_StructSize=sizeof(struct EasyStruct);
            es.es_Flags=0;
            es.es_Title="spar.device";
            es.es_TextFormat="TrackType: Added Type: %04lx.";
            es.es_GadgetFormat="Okay";
            EasyRequestArgs(NULL, &es, 0, &ios2->ios2_PacketType );
            CloseLibrary(IntuitionBase);
          }
#endif
          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++;

    if(sdu->sdu_TrackP)
    {
      stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;
      while(stats->ss_Node.mln_Succ)
      {
        if( *(UWORD *)(sdu->sdu_RxBuff+EADDR_LEN*2) == stats->ss_PType)
        {
          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;
#ifdef DEBUG
  struct Library *IntuitionBase;
  struct EasyStruct es;
#endif

    sdu->sdu_Stats.PacketsSent++;

    if(sdu->sdu_TrackP)
    {
      stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;
      while(stats->ss_Node.mln_Succ)
      {

        if( *(UWORD *)(sdu->sdu_TxBuff+EADDR_LEN*2) == (UWORD)stats->ss_PType)
        {
#ifdef DEBUG
          if(IntuitionBase = OpenLibrary("intuition.library",37L))
          {
            es.es_StructSize=sizeof(struct EasyStruct);
            es.es_Flags=0;
            es.es_Title="spar.device";
            es.es_TextFormat="PacketSent: Tracked packet type: %04lx.";
            es.es_GadgetFormat="Okay";
            EasyRequestArgs(NULL, &es, 0, &stats->ss_PType );
            CloseLibrary(IntuitionBase);
          }
#endif
          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;

    if(sdu->sdu_TrackP)
    {
      stats = (struct SuperS2PTStats *)sdu->sdu_Track.mlh_Head;
      while(stats->ss_Node.mln_Succ)
      {

        if( *(UWORD *)(sdu->sdu_TxBuff+EADDR_LEN*2) == (UWORD)stats->ss_PType)
        {
          stats->ss_Stats.PacketsDropped++;
          break;
        }
        stats = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
      }
    }
}

/*
** TermIO sends the IO request back to the user.  It knows not to mark
** the device as inactive if this was an immediate request or if the
** request was started from the server task.
*/
VOID TermIO(struct IOSana2Req *ios2)
{
#ifdef ISTASK
   /* check if this was an immediaty request */
   if(!((1L << ios2->ios2_Req.io_Command) & SPAR_IMMEDIATES))
   {
      struct SPARDevUnit *sdu;
      sdu = (struct SPARDevUnit *)ios2->ios2_Req.io_Unit; /* get the Unit pointer */

      /* We may need to turn the active bit off, when IO came not from the
       * task */
      if (!(sdu->sdu_Unit.unit_flags & UNITB_INTASK))
      {
        /* The task does not have more work to do */
        sdu->sdu_Unit.unit_flags &= ~UNITB_ACTIVE;
      }
   }
#endif
   /* If the quick bit is still set then we don't need to reply
    * msg, just return to the user. */

   if(!(ios2->ios2_Req.io_Flags & IOF_QUICK))
      ReplyMsg((struct Message *)ios2);
}

/*
** The device AbortIO() entry point.
**
** A1 - The IO request to be aborted.
** A3 - The unit pointer
** A6 - The device base.
*/
LONG ASM DevAbortIO(REG(a1) struct IOSana2Req *ios2, REG(a3) struct SPARDevUnit *sdu)
{
    LONG result = 0L;

    ObtainSemaphore(&sdu->sdu_ListLock);
    if(ios2->ios2_Req.io_Message.mn_Node.ln_Type != NT_REPLYMSG)
      switch(ios2->ios2_Req.io_Command)
      {
        case CMD_READ:      result=AbortReq(&sdu->sdu_Rx,ios2);
                            break;

        case CMD_WRITE:     result=AbortReq(&sdu->sdu_Tx,ios2);
                            break;

        case S2_READORPHAN: result=AbortReq(&sdu->sdu_RxOrph,ios2);
                            break;

        case S2_ONEVENT:    result=AbortReq(&sdu->sdu_Events,ios2);
                            break;

        default:            result=IOERR_NOCMD;
                            break;
      }
    ReleaseSemaphore(&sdu->sdu_ListLock);
    return(result);
}

/*
** This funcion 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)
{
    UWORD tempaddr;               /* unvalidated hardware address */
#ifdef DEBUG
    struct Library *IntuitionBase;
    struct EasyStruct es;
    ULONG arglong[2];
#endif

    /* 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 = (UWORD)ios2->ios2_SrcAddr[5];
        tempaddr &= 255; /* mask out MSB */

#ifdef DEBUG
        arglong[0]=(ULONG)tempaddr;
        arglong[1]=*(ULONG *)(ios2->ios2_SrcAddr+2);
        if(IntuitionBase = OpenLibrary("intuition.library",37L))
        {
           es.es_StructSize=sizeof(struct EasyStruct);
           es.es_Flags=0;
           es.es_Title="spar.device";
           es.es_TextFormat="ConfigInterface: Hw.addr. set to :%d (%08lx).";
           es.es_GadgetFormat="Okay";
           EasyRequestArgs(NULL, &es, 0, arglong);
           CloseLibrary(IntuitionBase);
        }
#endif
        /* 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_GETSTATIONADDRESS commands.
**
** PARnet uses only 1 octet, it will be translated to a 6-octet ethernet
** address by filling only the 6th byte in ios2_SrcAddr.
*/

VOID GetStationAddress(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    UBYTE i;
#ifdef DEBUG
    struct Library *IntuitionBase;
    struct EasyStruct es;
#endif

    for(i=0; i< SANA2_MAX_ADDR_BYTES; i++)
      ios2->ios2_SrcAddr[i]=0;

    ios2->ios2_SrcAddr[5]=(UBYTE)sdu->sdu_StAddr;

#ifdef DEBUG
    if(IntuitionBase = OpenLibrary("intuition.library",37L))
    {
       es.es_StructSize=sizeof(struct EasyStruct);
       es.es_Flags=0;
       es.es_Title="spar.device";
       es.es_TextFormat="GetStationAddr: %08lx.";
       es.es_GadgetFormat="Okay";
       EasyRequestArgs(NULL, &es, 0, (ULONG *)(ios2->ios2_SrcAddr+2));
       CloseLibrary(IntuitionBase);
    }
#endif

    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 = 32;      /* 32-bit IP address */
    sdq->MTU = SPAR_MTU;          /* max. trans. un. */
    sdq->BPS = SPAR_SPEED;        /* PARnet has fixed speed */
    sdq->HardwareType = S2WireType_Ethernet; /* fake! */

    sdq->SizeSupplied = 14;
    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);
    }
}

VOID SendPacket(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    struct IOParReq *iopar;
    struct BufferManagement *bm;
#ifdef DEBUG
    struct Library *IntuitionBase;
    struct EasyStruct es;
    ULONG buflong[2];
#endif

    bm =(struct BufferManagement *) ios2->ios2_BufferManagement;

    /* Copy the data out of the packet into our temporary buffer. */

    if ((ios2->ios2_DstAddr[5]==0) || (ios2->ios2_DstAddr[5]==255))
    {
        PacketDropped(sdu); /* Can't handle addresses 0 and 255 */
#ifdef DEBUG
        if(IntuitionBase = OpenLibrary("intuition.library",37L))
        {
          es.es_StructSize=sizeof(struct EasyStruct);
          es.es_Flags=0;
          es.es_Title="spar.device";
          es.es_TextFormat="SendPacket: Bad destinatio addr:%lx (packet dropped).";
          es.es_GadgetFormat="Okay";
          EasyRequestArgs(NULL, &es, 0, (ULONG *)(ios2->ios2_DstAddr+2));
          CloseLibrary(IntuitionBase);
        }
#endif
    }
    else
    {
      if((*bm->bm_CopyFromBuffer)(sdu->sdu_TxBuff+EHDR_LEN,ios2->ios2_Data,ios2->ios2_DataLength))
      {  CopyMem(ios2->ios2_DstAddr,sdu->sdu_TxBuff,EADDR_LEN);
         if ((ios2->ios2_SrcAddr[5]==0) || (ios2->ios2_SrcAddr[5]==255)) /* Valid source address ? */
           sdu->sdu_TxBuff[EADDR_LEN+5]=(UBYTE)sdu->sdu_StAddr; /* Use internal h/w addr. */
         else
           CopyMem(ios2->ios2_SrcAddr,sdu->sdu_TxBuff+EADDR_LEN,EADDR_LEN);
         *(UWORD *)(sdu->sdu_TxBuff+2*EADDR_LEN)=(UWORD)ios2->ios2_PacketType;

         /* Destination address ok, send it */
         PacketSent(sdu,ios2->ios2_DataLength);

         iopar = sdu->sdu_ParTx;
         iopar->io_Data = sdu->sdu_TxBuff;
         iopar->io_Length = ios2->ios2_DataLength+EHDR_LEN;
         iopar->io_Data2 = 0L;             /* must be 0 if you do not */
         iopar->io_Length2 = 0L;           /* use these fields.       */
         iopar->io_Command = CMD_WRITE;
         iopar->io_Error = 0;              /* is this needed?? */
         iopar->io_Message.mn_Node.ln_Type = 0;
         iopar->io_Port = SPAR_PORT;
         iopar->io_Addr = (UWORD) ios2->ios2_DstAddr[5];
         iopar->io_Flags = PRO_DGRAM; 
         /* Send the packet to the PARnet device driver */
         SendIO((struct IORequest *)iopar);

#ifdef DEBUG
         buflong[1]=(ULONG)ios2->ios2_DataLength;
         buflong[2]=(ULONG)iopar->io_Addr;

         if(IntuitionBase = OpenLibrary("intuition.library",37L))
         {
            es.es_StructSize=sizeof(struct EasyStruct);
            es.es_Flags=0;
            es.es_Title="spar.device";
            es.es_TextFormat="SendPacket: Sent packet size %ld to addr: %ld.";
            es.es_GadgetFormat="Okay";
            EasyRequestArgs(NULL, &es, 0, buflong);
            CloseLibrary(IntuitionBase);
         }
#endif
      }
      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 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 initializes our IO requests and buffers for PARnet i/o .
*/
BOOL InitPARnet(struct SPARDevUnit *sdu)
{
    ULONG *clr;
    BOOL status = FALSE;

    /* This is a dirty way of clearing some pointers in the struct SPARDevUnit */
    for(clr = (ULONG *) &sdu->sdu_ParRx; clr <= (ULONG *) &sdu->sdu_TxBuff; clr++)
      *clr = 0L;

    if(sdu->sdu_TxPort = CreateMsgPort()) /* PARnet CMD_WRITE IORequest reply port */
      if(sdu->sdu_ParTx = CreateIORequest(sdu->sdu_TxPort,sizeof(struct IOParReq)))
          if(sdu->sdu_RxPort = CreateMsgPort()) /* PARnet CMD_READ IORequest reply port */
            if(sdu->sdu_ParRx = CreateIORequest(sdu->sdu_RxPort,sizeof(struct IOParReq)))
                /* Allocate some buffer memory */
                if(sdu->sdu_TxBuff = AllocMem(SPAR_MTU * 2,MEMF_CLEAR|MEMF_PUBLIC))
                {
                  sdu->sdu_RxBuff = sdu->sdu_TxBuff + SPAR_MTU;
                  status = TRUE;  /* flag: initialisation was successful */
                }

    if(!status)
      DeinitPARnet(sdu); /* Something went wrong, release all */
    return(status);
}

/*
** This routine cleans up our PARnet i/o requests
** and misc. buffers.
*/
VOID DeinitPARnet(struct SPARDevUnit *sdu)
{
    if(sdu->sdu_ParTx)
      DeleteIORequest(sdu->sdu_ParTx);

    if(sdu->sdu_TxPort)
      DeleteMsgPort(sdu->sdu_TxPort);

    if(sdu->sdu_ParRx)
      DeleteIORequest(sdu->sdu_ParRx);

    if(sdu->sdu_RxPort)
      DeleteMsgPort(sdu->sdu_RxPort);

    if(sdu->sdu_TxBuff)
      FreeMem(sdu->sdu_TxBuff,(SPAR_MTU * 2)); /* release both TxBuff & RxBuff */

}

/*
** 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 opens the PARnet device driver and attempts to bring
** the device online.
*/
BOOL OpenPARnet(struct SPARDevUnit *sdu)
{
  BOOL status = FALSE;
  ULONG odflags = 0;
  struct IOParReq *ioTpar,*ioRpar; /* IORequest structures */

  /* next variables are used for displaying the error message */
  struct Library *IntuitionBase;
  struct EasyStruct es;
  ULONG args[2];

  ioRpar = sdu->sdu_ParRx;
  ioTpar = sdu->sdu_ParTx;
  ioRpar->io_Device = NULL;

  if (sdu->sdu_TxPort) /* Is this needed?? */
  {
    ioTpar->io_Message.mn_ReplyPort=sdu->sdu_TxPort;
    ioTpar->io_Port  = 0;
    ioTpar->io_Flags = PRO_CONTROL;

    if(!OpenDevice(sdu->sdu_ParDevName,sdu->sdu_ParUnitNum,(struct IORequest *)ioTpar,odflags))
    {
      /* Set up our PARnet RX parameters */
      ioRpar->io_Device = ioTpar->io_Device;
      ioRpar->io_Unit   = ioTpar->io_Unit;

      /* set up IO request for hardware address setting */
      ioTpar->io_Command = PPD_SETADDR;
      ioTpar->io_Addr    = sdu->sdu_StAddr;

      if(!DoIO((struct IORequest *)ioTpar)) /* Set my hardware address */
      {
            /* Close the device again to reset the PRO_CONTROL mode */
            CloseDevice((struct IORequest *)ioTpar);

            /* Open the device in Data Gram mode */
            ioTpar->io_Port  = SPAR_PORT;
            ioTpar->io_Flags = PRO_DGRAM;
            if(!OpenDevice(sdu->sdu_ParDevName,sdu->sdu_ParUnitNum,(struct IORequest *)ioTpar,odflags))
            {
                /* Assume we're now online */
                sdu->sdu_State |= SPARUF_ONLINE;

                /* check first if the reply port has already been initalized */
                if (sdu->sdu_RxPort)  /* Is this really needed?? */
                {
                     /* Queue up the initial CMD_READ command for the PARnet driver. */
                     ioRpar->io_Message.mn_ReplyPort=sdu->sdu_RxPort;
                     ioRpar->io_Port    = SPAR_PORT;  /* PARnet port to listen on */
                     ioRpar->io_Flags   = PRO_DGRAM; /* only DataGram mode supported */
                     ioRpar->io_Command = CMD_READ;
                     ioRpar->io_Length  = SPAR_MTU;
                     if (sdu->sdu_RxBuff)            /* Is this needed?? */
                     {
                          ioRpar->io_Data = sdu->sdu_RxBuff;
                          SendIO((struct IORequest *)ioRpar);
                          status = TRUE; /* All ok! */
                     }
                }
             }
             if(!status)
                 CloseDevice((struct IORequest *)ioTpar);
      }
      else /* Can't set PARnet hardware address */
        CloseDevice((struct IORequest *)ioTpar);
    }
    else /* Can't open device */
    {
        args[0]=(ULONG)sdu->sdu_ParDevName;
        args[1]=(ULONG)sdu->sdu_ParUnitNum;
        if(IntuitionBase = OpenLibrary("intuition.library",37L))
        {
            es.es_StructSize=sizeof(struct EasyStruct);
            es.es_Flags=0;
            es.es_Title="spar.device";
            es.es_TextFormat="Couldn't open %s unit %ld.";
            es.es_GadgetFormat="Okay";
            EasyRequestArgs(NULL, &es, 0, (APTR)args);
            CloseLibrary(IntuitionBase);
        }
    }
    if(sdu->sdu_State & SPARUF_ONLINE)
      MarkTimeOnline(sdu);
  }
  return(status);
}

/*
** 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,0L))
      {
          TimerBase = (struct Library *)treq->tr_node.io_Device;
          GetSysTime(&sdu->sdu_Stats.LastStart);
          CloseDevice((struct IORequest *)treq);
      }
      FreeMem(treq,sizeof(struct timerequest));
    }
}

/*
** This routine aborts any pending activity with the PARnet
** device driver and then brings the spar driver offline.
*/
VOID ClosePARnet(struct SPARDevUnit *sdu)
{
    AbortIO((struct IORequest *)sdu->sdu_ParRx);
    WaitIO((struct IORequest *)sdu->sdu_ParRx);

    while(GetMsg(sdu->sdu_RxPort));

    AbortIO((struct IORequest *)sdu->sdu_ParTx);
    WaitIO((struct IORequest *)sdu->sdu_ParTx);

    while(GetMsg(sdu->sdu_TxPort));

    CloseDevice((struct IORequest *)sdu->sdu_ParRx);

    sdu->sdu_State &= ~SPARUF_ONLINE;

}

/*
** 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);
}

/*
** This routine is called whenever a CMD_WRITE request
** has returned from the PARnet driver.
*/
VOID ServiceTxPort(struct SPARDevUnit *sdu, struct IOParReq *IOPar)
{
    struct IOSana2Req *ios2;

    if(IOPar->io_Error) /* a write timeout occured */
        PacketDropped(sdu);

    if(sdu->sdu_State & SPARUF_ONLINE)
    {
        /* Get next CMD_WRITE request (if present) */
      ObtainSemaphore(&sdu->sdu_ListLock);
      ios2 = (struct IOSana2Req *)RemHead((struct List *)&sdu->sdu_Tx);
      ReleaseSemaphore(&sdu->sdu_ListLock);

      if(ios2)       /* Is a CMD_WRITE request pending? */
      {
          SendPacket(sdu, ios2);
          sdu->sdu_NoMore = TRUE;  /* There might be more */
      }
    }
    else
      sdu->sdu_NoMore = TRUE;
}

/*
** This routine is called whenever a CMD_READ request
** returns from the PARnet driver.
*/
VOID DoPARnet(struct SPARDevUnit *sdu, struct IOParReq *IOPar)
{
    if (IOPar->io_Actual < 0) ReceivedGarbage(sdu);
    else
    {
       IOPar->io_Length=IOPar->io_Actual;

       if(IOPar->io_Length > SPAR_MTU) PacketOverrun(sdu);
       else GotPacket(sdu,IOPar->io_Length);
    }
    /* Queue up another CMD_READ request...*/
    QueueParRequest(sdu);
}

/*
** This routine is called whenever we think we've got
** a complete packet to satisfy a CMD_READ request.
*/
VOID GotPacket(struct SPARDevUnit *sdu, ULONG length)
{
    struct IOSana2Req *ios2;
    struct BufferManagement *bm;
    ULONG Offset;
#ifdef DEBUG
    struct Library *IntuitionBase;
    struct EasyStruct es;
    ULONG buflongs[3];
#endif

    if(length)
    {
      PacketReceived(sdu,length);
      ObtainSemaphore(&sdu->sdu_ListLock);

      ios2 = (struct IOSana2Req *)RemHead((struct List *)&sdu->sdu_Rx);

      if(!ios2)
        ios2 = (struct IOSana2Req *)RemHead((struct List *)&sdu->sdu_RxOrph);

      ReleaseSemaphore(&sdu->sdu_ListLock);

      if(ios2)
      {
        bm = (struct BufferManagement *)ios2->ios2_BufferManagement;

        /* Don't strip ethernet header when RAW data is requested */
        if (ios2->ios2_Req.io_Flags==SANA2IOB_RAW) Offset=0;
         else Offset=EHDR_LEN;

        /* Copy the data into the protocol stack's buffer using its
         * supplied callback routine. */
        if((*bm->bm_CopyToBuffer)(ios2->ios2_Data,sdu->sdu_RxBuff+Offset,length-Offset))
        {
           CopyMem(sdu->sdu_RxBuff,          ios2->ios2_DstAddr,EADDR_LEN);
           CopyMem(sdu->sdu_RxBuff+EADDR_LEN,ios2->ios2_SrcAddr,EADDR_LEN);
           ios2->ios2_DataLength = length-Offset;
           ios2->ios2_PacketType = (ULONG) *(UWORD *)(sdu->sdu_RxBuff+2*EADDR_LEN);

#ifdef DEBUG
           if(IntuitionBase = OpenLibrary("intuition.library",37L))
           {
             buflongs[0]=(ULONG)ios2->ios2_DataLength;
             buflongs[1]=(ULONG)ios2->ios2_PacketType;
             buflongs[2]=*(ULONG *)(ios2->ios2_SrcAddr+2);

             es.es_StructSize=sizeof(struct EasyStruct);
             es.es_Flags=0;
             es.es_Title="spar.device";
             es.es_TextFormat="GotPacket: Length:%08lx Type:%08lx Source:%08lx.";
             es.es_GadgetFormat="Okay";
             EasyRequestArgs(NULL, &es, 0, (APTR)buflongs );
             CloseLibrary(IntuitionBase);
           }
#endif
        }
        else
        {
           ios2->ios2_DataLength   = 0;
           ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
           ios2->ios2_WireError    = S2WERR_BUFF_ERROR;
           DoEvent(sdu,S2EVENT_BUFF);
        }
        TermIO(ios2);
      }
      else
        PacketDropped(sdu);
    } /* Length */
}

/*
** This routine is called whenever we need to
** get more data from the PARnet port.
*/
VOID QueueParRequest(struct SPARDevUnit *sdu)
{
    sdu->sdu_ParRx->io_Command = CMD_READ;
    sdu->sdu_ParRx->io_Data = sdu->sdu_RxBuff;
    sdu->sdu_ParRx->io_Length = SPAR_MTU;

    SendIO((struct IORequest *)sdu->sdu_ParRx);
}

/*
** This is the C entry point for the Unit process.
** A6 has been set up by the assembly stub in spar_device.asm.
*/

VOID ASM DevProcCEntry(VOID)
{
    struct Process *proc;
    struct SPARDevUnit *sdu;
    struct IOParReq *iopar;
    struct StartupMessage *sm;
    struct BufferManagement *bm;
    struct IOSana2Req *ios2;
    ULONG waitmask,signals;
    UBYTE signalbit;

    /* Find our Process pointer and wait for our startup
       message to arrive. */

    proc = (struct Process *)FindTask(0L);

    WaitPort(&proc->pr_MsgPort);

    /* Pull the startup message off of our process messageport. */
    sm = (struct StartupMessage *)GetMsg(&proc->pr_MsgPort);

    /* Grab our Unit pointer. */
    sdu = (struct SPARDevUnit *)sm->Unit;

    /* Attempt to allocate a signal bit for our Unit MsgPort. */
    signalbit = AllocSignal(-1L);
    if(signalbit != -1)
    {
      /* Set up our Unit's MsgPort. */
      sdu->sdu_Unit.unit_MsgPort.mp_SigBit = signalbit;
      sdu->sdu_Unit.unit_MsgPort.mp_SigTask = (struct Task *)proc;
      sdu->sdu_Unit.unit_MsgPort.mp_Flags = PA_SIGNAL;  /* make it "live" */

      /* Initialize our list semaphore */
      InitSemaphore(&sdu->sdu_ListLock);

      /* Initialize our linked lists. */
      NewList((struct List *)&sdu->sdu_Rx);
      NewList((struct List *)&sdu->sdu_RxOrph);
      NewList((struct List *)&sdu->sdu_Tx);
      NewList((struct List *)&sdu->sdu_Events);
      NewList((struct List *)&sdu->sdu_BuffMgmt);
      NewList((struct List *)&sdu->sdu_Track);

      /* Initialize the PARnet stuff.  If all goes okay,
       * set sdu->sdu_Proc to pointer to our unit process.
       * This will let the Unit init code know that were
       * are okay. */

      if(InitPARnet(sdu))
            sdu->sdu_Proc = proc;
    }
    /* Initialization done. Reply to our startup message (see InitSparUnit),
     * so they can continue */

    ReplyMsg((struct Message *)sm);

    /* Check sdu->sdu_Proc to see if everything went okay up
     * above. */
    if(sdu->sdu_Proc)
    {
      waitmask = (1L<<signalbit) | (1L<<sdu->sdu_RxPort->mp_SigBit) |
           (1L<<sdu->sdu_TxPort->mp_SigBit) | SIGBREAKF_CTRL_F;

      /* Loop...*/

      while(TRUE)
      {
         signals = Wait(waitmask);

         /* Have we been signaled to shut down? */
         if(signals & SIGBREAKF_CTRL_F)
         break;

         /* I use this flag to make sure I don't sit idle
          * with a message sitting on one of my message ports. */

            sdu->sdu_NoMore = TRUE;  /* Make sure we run at least once. */

            while(sdu->sdu_NoMore)
            {
                sdu->sdu_NoMore = FALSE;

                /* Handle messages from the user (were queued in DevBeginIO) */
                if(ios2 = (struct IOSana2Req *)GetMsg((struct MsgPort *)sdu))
                {
                    sdu->sdu_NoMore = TRUE; /* there might be more */
                    PerformIO(ios2);        /* process an io request */
                }

                /* Handle message from the receiver */
                if(iopar = (struct IOParReq *)GetMsg(sdu->sdu_RxPort))
                    if(sdu->sdu_State & SPARUF_ONLINE)
                        DoPARnet(sdu, iopar);

                /* Handle message from the transmitter */
                if(iopar = (struct IOParReq *)GetMsg(sdu->sdu_TxPort))
                    ServiceTxPort(sdu, iopar);
            }
        }
        /* No more messages on my port */
        /* If we're online, we need to shut everything down. */
        if(sdu->sdu_State & SPARUF_ONLINE)
        {
            ClosePARnet(sdu);
            FreeSignal((long)signalbit);
            while(bm = (struct BufferManagement *)RemHead((struct List *)&sdu->sdu_BuffMgmt))
            FreeMem(bm,sizeof(struct BufferManagement));
        }
        DeinitPARnet(sdu);

        /* Signal the other side we're exiting.  Make sure that
           we exit before they wake up by using the same trick
           when replying to Workbench startup messages. */

        Forbid();
        Signal((struct Task *)sdu->sdu_Proc,SIGBREAKF_CTRL_F);
    }
    /* Something went wrong in the init code.  Drop out. */
    else
    {
      if(signalbit)
          FreeSignal((long)signalbit);
    }
}

/* List init routine. */
VOID NewList(struct List *list)
{
    list->lh_Head = (struct Node *)&list->lh_Tail;
    list->lh_Tail = NULL;
    list->lh_TailPred = (struct Node *)list;
}
