RCS_ID_C="$Id: device.c,v 2.3 1993/10/20 15:17:52 jraja Exp $";
/*
 * device.c --- SANA-II test device, device functions
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * Copyright (c) 1993 AmiTCP/IP Group,
 *                    Helsinki University of Technology, Finland.
 *                    All rights reserved.
 *
 * Created      : Thu Jan 21 17:32:55 1993 ppessi
 * Last modified: Wed Oct 20 16:49:15 1993 jraja
 *
 * $Log: device.c,v $
 * Revision 2.3  1993/10/20  15:17:52  jraja
 * Added correct prototype for the CheckIO().
 *
 * Revision 2.2  1993/10/14  00:09:43  ppessi
 * Changed RCS Id format
 *
 * Revision 2.1  93/05/14  16:48:21  ppessi
 * Release version
 * 
 */

#include <string.h>

#include <dos/dostags.h>
#include <dos/rdargs.h>
#include <intuition/intuition.h>
#include <rexx/storage.h>
#include <rexx/rxslib.h>

#include <clib/alib_stdio_protos.h>

#ifdef __SASC
#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>
#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>
#include <clib/utility_protos.h>
#include <pragmas/utility_pragmas.h>
#include <clib/timer_protos.h>
#include <pragmas/timer_pragmas.h>
#endif

#ifdef __GNUC__
#include <inline/exec.h>
#include <inline/dos.h>
#include <inline/utility.h>
#include <inline/timer.h>
#endif

#include "agnet.h"
#include "agnet_protos.h"
#include "agnet_rev.h"
#include "bases.h"

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

/* Local prototypes */
static ULONG AbortReq(struct MinList *minlist, struct IOSana2Req *ios2);
static VOID CloseUnit(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID ExpungeUnit(struct AgnetDevUnit *adu);
static VOID TermIO(struct IOSana2Req *ios2);
static VOID GetSpecialStats(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID GetGlobalStats(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID GetTypeStats(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID TrackType(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID UnTrackType(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID PacketReceived(struct AgnetDevUnit *adu, ULONG length, ULONG type);
static VOID PacketSent(struct AgnetDevUnit *adu, ULONG length, ULONG type);
static VOID PacketOverrun(struct AgnetDevUnit *adu);
static VOID ReceivedGarbage(struct AgnetDevUnit *adu);
static VOID PacketDropped(struct AgnetDevUnit *adu, ULONG type);
static VOID ReceivedOrphan(struct AgnetDevUnit *adu);
static VOID ConfigInterface(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID GetStationAddress(struct AgnetDevUnit *, struct IOSana2Req *);
static VOID DeviceQuery(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID Online(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID Offline(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID OnEvent(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID DoEvent(struct AgnetDevUnit *adu, ULONG event);
static VOID WritePacket(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID ReadPacket(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID ReadOrphan(struct AgnetDevUnit *adu, struct IOSana2Req *ios2);
static VOID SendPacket(struct DelayRequest *delayed);
static VOID ReceivePacket(struct DelayRequest *delayed);
static VOID DoReceive(struct AgnetDevUnit *, struct DelayRequest *delayed );
static VOID CopyBack(struct AgnetDevUnit *, struct IOSana2Req *, 
		     struct DelayRequest *);

/*
 * Device Open vector
 *
 * a1 - SANA2 IO Request
 * a6 - Pointer to our device base
 * d0 - Unit number
 * d1 - Flags
 */
ULONG ASM 
DevOpen(REG(a1) struct IOSana2Req *ios2,
	REG(d0) ULONG s2unit,
	REG(d1) ULONG s2flags)
{
  struct AgnetDevice *AgnetDev = AgnetDeviceBase;
  struct AgnetDevUnit *adu;
  struct TagItem *bufftag;
  struct Library *UtilityBase;
  struct BufferManagement *bm;
  BYTE status = IOERR_UNITBUSY;

  /* Make sure our open remains single-threaded.
     We may Wait() when starting up. If somebody
     decides to DoExpunge() before we get the
     semaphore, system is probably blowing up anyways.
     */
  ObtainSemaphore(&AgnetDev->ad_Lock);

  /* So we won't expunge ourselves... */
  AgnetDev->ad_Dev_OpenCnt++;

  if (s2unit < AD_MAXUNITS)       /* Legal Unit */
    if (adu = InitUnit(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(*bm), 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 *)&adu->adu_BuffMgmt, (struct Node *)bm);

	  /* Everything went okay. */
	  status = 0;
	  AgnetDev->ad_Dev_OpenCnt++;
	  AgnetDev->ad_Dev_Flags &= ~LIBF_DELEXP;
	  adu->adu_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 = adu;
	  ios2->ios2_Req.io_Device = AgnetDev;
	}
	CloseLibrary(UtilityBase);
      }

  /* See if something went wrong. */
  if (status) {
    ios2->ios2_Req.io_Error = status;
    ios2->ios2_Req.io_Unit = (struct Unit *) -1;
    ios2->ios2_Req.io_Device = (struct Device *) -1;
  }
  AgnetDev->ad_Dev_OpenCnt--;
  ReleaseSemaphore(&AgnetDev->ad_Lock);

  return status;
}

/*
 * Device Close vector.
 *
 * a1 - IOReq
 * a6 - Device Pointer
 */
BPTR ASM 
DevClose(REG(a1) struct IOSana2Req *ios2)
{
  struct AgnetDevice *AgnetDev = AgnetDeviceBase;
  struct AgnetDevUnit *adu;
  BPTR seglist = 0L;

  ObtainSemaphore(&AgnetDev->ad_Lock);

  adu = (struct AgnetDevUnit *)ios2->ios2_Req.io_Unit;
  CloseUnit(adu, ios2);

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

  AgnetDev->ad_Dev_OpenCnt--;

  /* Check to see if we've been asked to expunge. */
  if (AgnetDev->ad_Dev_OpenCnt == 0 && AgnetDev->ad_Dev_Flags & LIBF_DELEXP) {
    Signal(AgnetDev->ad_Task, SIGBREAKF_CTRL_F);
  }
  ReleaseSemaphore(&AgnetDev->ad_Lock);

  return seglist;
}

/*
 * Device Expunge vector
 *
 * a6 - Device base
 *
 * Note: You may NEVER EVER Wait() in expunge. Period.
 *	 Don't even *think* about it.
 */
BPTR ASM 
DevExpunge(VOID)
{
  struct AgnetDevice *AgnetDev = AgnetDeviceBase;

  AgnetDev->ad_Dev_Flags |= LIBF_DELEXP;
  Signal(AgnetDev->ad_Task, SIGBREAKF_CTRL_F);

  /* We can not expunge  */
  return (BPTR)0L;
}

/*
 * Device Reserved vector (returns 0L)
 */
ULONG ASM 
DevReserved(VOID)
{
  return 0L;
}

/*
 * BeginIO --- dispatch incoming requests
 *
 * a1 - The IO request
 * a6 - The device base
 */
#define SLIP_IMMEDIATES 0L	/* No QUICK IO */

VOID ASM 
DevBeginIO(REG(a1) struct IOSana2Req *ios2)
{
  register struct AgnetDevice *ad;
  ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE;

  if (ios2->ios2_Req.io_Command < S2_END) {
    if ((ios2->ios2_Req.io_Flags & IOF_QUICK) &&
	((1L << ios2->ios2_Req.io_Command) & SLIP_IMMEDIATES)) {
      PerformIO(ios2);
    } else {
      ios2->ios2_Req.io_Flags &= ~IOF_QUICK;
      /* Send this to DEVICE Message Port */
      ad = (struct AgnetDevice*)ios2->ios2_Req.io_Device;
      PutMsg(&ad->ad_MsgPort, (struct Message *)ios2);
    }
  } else {
    ios2->ios2_Req.io_Error = IOERR_NOCMD;
    TermIO(ios2);
  }
}

/*
 * The device AbortIO() entry point.
 * 
 * A1 - The IO request to be aborted.
 * A6 - The device base.
 */
ULONG ASM 
DevAbortIO(REG(a1) struct IOSana2Req *ios2)
{
  register struct AgnetDevUnit *adu = 
    (struct AgnetDevUnit *)ios2->ios2_Req.io_Unit;
  register ULONG result = 0L;

  
  LockUnit(adu);
  if (ios2->ios2_Req.io_Message.mn_Node.ln_Type != NT_REPLYMSG) {
    switch(ios2->ios2_Req.io_Command) {
    case CMD_READ:	result = AbortReq(&adu->adu_Rx,ios2);
      break;

    case CMD_WRITE:     result = AbortReq(&adu->adu_Tx,ios2);
      break;

    case S2_READORPHAN:	result = AbortReq(&adu->adu_RxOrph,ios2);
      break;

    case S2_ONEVENT:	result = AbortReq(&adu->adu_Events,ios2);
      break;

    default:		result = IOERR_NOCMD;
      break;
    }
  }
  UnlockUnit(adu);
  return result;
}

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

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

  while (!TAILP(node)) {
    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;
}

/*
 * Real expunge
 */
BOOL 
DoExpunge(struct AgnetDevice *adb)
{
  ULONG i;

  /* Well, if there is somebody trying to open,
     they stuck with this */
  ObtainSemaphore(&adb->ad_Lock);
  if (adb->ad_Device.lib_OpenCnt != 0) {
    ReleaseSemaphore(&adb->ad_Lock);
    return FALSE;
  }
  Forbid();
  Remove((struct Node *)adb);
  Permit();

  /* Free up Units */
  for (i=0; i < AD_MAXUNITS; i++) {
    if (adb->ad_Units[i])
      ExpungeUnit((struct AgnetDevUnit *)adb->ad_Units[i]);
    adb->ad_Units[i] = NULL;
  }

  return TRUE;
}

/*
 * InitUnit
 *
 * Initialize (if needed) a new agnet.device Unit
 */
struct AgnetDevUnit *
InitUnit(ULONG s2unit)
{
  struct AgnetDevUnit *adu;
  struct AgnetDevice *adb = AgnetDeviceBase;

  /* 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 (adu = (struct AgnetDevUnit *)adb->ad_Units[s2unit])
    return adu;

  /* Allocate a new Unit structure */
  adu = AllocMem(sizeof(*adu), MEMF_CLEAR|MEMF_PUBLIC);
  if (!adu)
    return NULL;

  /* Do some initialization on the Unit structure */
#if 0
  NewList(&adu->adu_Unit_MsgPort.mp_MsgList);
  adu->adu_Unit_MsgPort.mp_Node.ln_Type = NT_MSGPORT;
  adu->adu_Unit_MsgPort.mp_Flags = PA_IGNORE;
  adu->adu_Unit_MsgPort.mp_Node.ln_Name = AGNETDEVNAME;
#endif
  adu->adu_UnitNum = s2unit;
  adu->adu_Device = (struct Device *) adb;

  /* Try to read in our configuration file */
  if (!ReadConfig(adu)) {
    FreeMem(adu, sizeof(*adu));
    return NULL;
  }

  /* Initialize our list semaphore */
  InitSemaphore(&adu->adu_Lock);

  /* Initialize our linked lists. */
  NewList((struct List *)&adu->adu_FreeToTx);
  NewList((struct List *)&adu->adu_Rx);
  NewList((struct List *)&adu->adu_RxOrph);
  NewList((struct List *)&adu->adu_Tx);
  NewList((struct List *)&adu->adu_Events);
  NewList((struct List *)&adu->adu_Track);
  NewList((struct List *)&adu->adu_BuffMgmt);

  /* Allocate memory buffers */
  DoOnline(adu);

  /* Set up the Unit structure pointer in the device base */
  adb->ad_Units[s2unit] = adu;

  return adu;
}

/*
 * CloseUnit
 *
 * This function closes unit and frees resources
 * allocated for each opener.
 */
static VOID 
CloseUnit(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  register struct BufferManagement *bm = ios2->ios2_BufferManagement;

  LockUnit(adu);

  if (bm) {
    ios2->ios2_BufferManagement = NULL;
    Remove((struct Node *)bm);
    FreeMem(bm, sizeof(*bm));
  }
  adu->adu_Unit.unit_OpenCnt--;

  UnlockUnit(adu);
}

/*
 *  ExpungeUnit
 *
 *  This function is called from the DoExpunge routine.
 *  The unit structure and all subsequent allocations are freed.
 */
static VOID 
ExpungeUnit(struct AgnetDevUnit *adu)
{
  struct IOSana2Req *ios2;
  struct SuperS2PTStats *sstats;
  int i;
  struct List *io_queues[5];

  /* Eliminate every queues */
  LockUnit(adu);

  io_queues[0] = (struct List *)&adu->adu_Rx;
  io_queues[1] = (struct List *)&adu->adu_Tx;
  io_queues[2] = (struct List *)&adu->adu_RxOrph;
  io_queues[3] = (struct List *)&adu->adu_Events;
  io_queues[4] = NULL;

  for (i = 0; io_queues[i]; i++) {
    while(ios2 = (struct IOSana2Req *)RemHead(io_queues[i])) {
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      TermIO(ios2);
     }
  }

  /* Tracking data was allocated by us */
  while(sstats = (struct SuperS2PTStats*)
	RemHead((struct List *)&adu->adu_Track)) {
    FreeMem(sstats, sizeof(*sstats));
  }

  DoOffline(adu);

  UnlockUnit(adu);

  FreeMem(adu, sizeof(*adu));
}

/*
 * This routine is used to dispatch an IO request either from BeginIO
 * or from the Unit process.
 */
VOID 
PerformIO(struct IOSana2Req *ios2)
{
  struct AgnetDevUnit *adu = (struct AgnetDevUnit *)ios2->ios2_Req.io_Unit;

  if (ios2->ios2_Req.io_Device == 
      AgnetDeviceBase->ad_Timer.tr_node.io_Device) {
    ReceivePacket(ios2);
    return;
  }

  ios2->ios2_Req.io_Error = 0;

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

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

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

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

  case S2_CONFIGINTERFACE:    ConfigInterface(adu,ios2);
    break;
#if 0
    /* Non-existing commands */
  case S2_ADDSTATIONALIAS:
  case S2_DELSTATIONALIAS:
#endif
  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_BROADCAST:	    WritePacket(adu,ios2);
    break;

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

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

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

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

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

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

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

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

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

  default:
    ios2->ios2_Req.io_Error = IOERR_NOCMD;
    TermIO(ios2);
    break;
  }
}

/*
 * This function is used to return an IO request
 * back to the sender.
 */
static VOID 
TermIO(struct IOSana2Req *ios2)
{
  if (!(ios2->ios2_Req.io_Flags & IOF_QUICK))
    ReplyMsg((struct Message *)ios2);
}

/*
 * This function returns any device specific statistics that
 * we may have.
 *
 * Currently, there is none.
 *
 * Ethernet may require some
 */
static VOID 
GetSpecialStats(struct AgnetDevUnit *adu, 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
 * slip device.
 */
static VOID 
GetGlobalStats(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  struct Sana2DeviceStats *stats;

  if (stats = (struct Sana2DeviceStats *)ios2->ios2_StatData) {
    stats->PacketsReceived	= adu->adu_Stats.PacketsReceived;
    stats->PacketsSent		= adu->adu_Stats.PacketsSent;
    stats->BadData		= adu->adu_Stats.BadData;
    stats->Overruns		= adu->adu_Stats.Overruns;
    stats->UnknownTypesReceived = adu->adu_Stats.UnknownTypesReceived;
    stats->Reconfigurations	= adu->adu_Stats.Reconfigurations;
    stats->LastStart.tv_secs	= adu->adu_Stats.LastStart.tv_secs;
    stats->LastStart.tv_micro	= adu->adu_Stats.LastStart.tv_secs;
  } 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.
 *
 * Just to be thourough, I have arbitrarily picked
 * the packet type for SLIP IP packets to be 2048, the
 * same as that used for Ethernet.  This will at least
 * allow you to track IP packets.
 */
static VOID
GetTypeStats(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  struct Sana2PacketTypeStats *stats;
  struct SuperS2PTStats *sstats;

  if (stats = (struct Sana2PacketTypeStats *)ios2->ios2_StatData) {
    LockUnit(adu);

    sstats = (struct SuperS2PTStats *)adu->adu_Track.mlh_Head;

    /* Find asked packet type from tracking list */
    while (sstats->ss_Node.mln_Succ) {
      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;
    }
    UnlockUnit(adu);
    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.
 */
static VOID 
TrackType(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  struct SuperS2PTStats *stats;
  ULONG type = ios2->ios2_PacketType;

  LockUnit(adu);

  stats = (struct SuperS2PTStats *)adu->adu_Track.mlh_Head;

  while (stats->ss_Node.mln_Succ) {
    if (type == stats->ss_PType) {
      ios2->ios2_Req.io_Error = S2ERR_BAD_STATE;
      ios2->ios2_WireError    = S2WERR_ALREADY_TRACKED;
    }
    stats = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
  }
  if (!stats->ss_Node.mln_Succ) {
    if(stats = AllocMem(sizeof(*stats), MEMF_CLEAR|MEMF_PUBLIC)) {
      stats->ss_PType = type;
      AddTail((struct List *)&adu->adu_Track, (struct Node *)stats);
    }
  }
  UnlockUnit(adu);

  TermIO(ios2);
}

/*
 * This function removes a packet type from the
 * list of those that are being tracked.
 */
static VOID 
UnTrackType(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  struct SuperS2PTStats *stats;

  LockUnit(adu);

  stats = (struct SuperS2PTStats *)adu->adu_Track.mlh_Head;

  while (stats->ss_Node.mln_Succ) {
    if (ios2->ios2_PacketType == stats->ss_PType) {
      Remove((struct Node *)stats);
      FreeMem(stats, sizeof(*stats));
      stats = NULL;
      break;
    }
    stats = (struct SuperS2PTStats *)stats->ss_Node.mln_Succ;
  }
  if(stats) {
    ios2->ios2_Req.io_Error = S2ERR_BAD_STATE;
    ios2->ios2_WireError    = S2WERR_NOT_TRACKED;
  }
  UnlockUnit(adu);

  TermIO(ios2);
}

/*
 * This function is called whenever a packet is received
 */
static VOID 
PacketReceived(struct AgnetDevUnit *adu, ULONG length, ULONG type)
{
  struct SuperS2PTStats *stats;

  adu->adu_Stats.PacketsReceived++;

  LockUnit(adu);

  stats = (struct SuperS2PTStats *)adu->adu_Track.mlh_Head;

  while (stats->ss_Node.mln_Succ) {
    if (stats -> ss_PType == type) {
      stats -> ss_Stats.PacketsReceived++;
      stats -> ss_Stats.BytesReceived += length;
      break;
    }
    stats = (struct SuperS2PTStats *)stats -> ss_Node.mln_Succ;
  }

  UnlockUnit(adu);
}

/*
 * This function is called whenever a packet is sent
 */
static VOID 
PacketSent(struct AgnetDevUnit *adu, ULONG length, ULONG type)
{
  struct SuperS2PTStats *stats;

  adu->adu_Stats.PacketsSent++;

  LockUnit(adu);

  stats = (struct SuperS2PTStats *)adu->adu_Track.mlh_Head;

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

  UnlockUnit(adu);
}

/*
 * This function is called a packet overruns
 */
static VOID 
PacketOverrun(struct AgnetDevUnit *adu)
{
  adu->adu_Stats.Overruns++;
  DoEvent(adu, S2EVENT_HARDWARE | S2EVENT_RX | S2EVENT_ERROR);
}

/*
 * This function is called whenever a packet with
 * garbage data is encountered.
 */
static VOID 
ReceivedGarbage(struct AgnetDevUnit *adu)
{
  adu->adu_Stats.BadData++;
  DoEvent(adu, S2EVENT_HARDWARE | S2EVENT_RX | S2EVENT_ERROR);
}

/*
 * This function is called whenever a packet
 * is dropped by the SLIP driver.
 */
static VOID 
PacketDropped(struct AgnetDevUnit *adu, ULONG type)
{
  struct SuperS2PTStats *stats;

  LockUnit(adu);

  stats = (struct SuperS2PTStats *)adu->adu_Track.mlh_Head;

  while (stats->ss_Node.mln_Succ) {
    if (stats -> ss_PType == type) {
      stats -> ss_Stats.PacketsDropped++;
      break;
    }
    stats = (struct SuperS2PTStats *)stats -> ss_Node.mln_Succ;
  }

  UnlockUnit(adu);
}

/*
 * This function is called whenever an orphan packet
 * is received
 */
static VOID 
ReceivedOrphan(struct AgnetDevUnit *adu)
{
  adu->adu_Stats.UnknownTypesReceived++;
}

/*
 * This function handles S2_CONFIGINTERFACE commands.
 */
static VOID 
ConfigInterface(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  /* Note: we may only be configured once. */
  if (!(adu->adu_State & AGUF_CONFIG)) {
    switch (adu->adu_HardwareType) {
    case S2WireType_PPP:
    case S2WireType_SLIP:
    case S2WireType_CSLIP:
    case S2WireType_Ethernet:
    case S2WireType_IEEE802:
    case S2WireType_Arcnet:
    case S2WireType_LocalTalk:
    case S2WireType_AmokNet:
      /* Copy given address as our Station Address */
      memcpy(&adu->adu_Addr, &ios2->ios2_SrcAddr, MAX_ADDR_BYTES);
      /*FALLTHROUGH*/
    default:
      adu->adu_State |= AGUF_CONFIG;
    }
  } 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.
 */
static VOID 
GetStationAddress(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  memcpy(ios2->ios2_DstAddr, adu->adu_Addr, MAX_ADDR_BYTES);
  if (adu->adu_State & AGUF_CONFIG)
    memcpy(ios2->ios2_SrcAddr, adu->adu_Addr, MAX_ADDR_BYTES);
  else 
    memset(ios2->ios2_SrcAddr, 0xff, MAX_ADDR_BYTES);
  TermIO(ios2);
}

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

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

  sdq->DeviceLevel   = 0L;
  sdq->AddrFieldSize = adu->adu_AddrFieldSize;
  sdq->MTU           = adu->adu_MaxTU;
  sdq->BPS           = adu->adu_BPS;
  sdq->HardwareType  = adu->adu_HardwareType;

  sdq->SizeSupplied = sizeof(*sdq);
  TermIO(ios2);
}

/*
 * This routime handles CMD_ONLINE commands.
 */
static VOID 
Online(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  if (!(adu->adu_State & AGUF_ONLINE)) {
    /* We're offline. Go online. */
    LockUnit(adu);
    if (DoOnline(adu)) {
      /* Couldn't get online */
      ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
      ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
    } 
    UnlockUnit(adu);
  }
  TermIO(ios2);
}

/* 
 * Actual Online, return 0 on success
 *
 * Caller should have a lock on the unit
 */
LONG
DoOnline(struct AgnetDevUnit *adu)
{
  struct AgnetDevice *adb = (struct AgnetDevice *)adu->adu_Device;
  ULONG mtu; LONG i; BOOL error = TRUE;
  struct DelayRequest *txed;

  /* Allocate transfer buffer */
  mtu = adu->adu_MaxTU + 64;
  for (i = 0; i < MAX_TXED; i++) {
    if (adu->adu_TxEd[i]) {
      error = FALSE;
      continue;
    }
    if (!(txed = CreateIORequest(&adb->ad_MsgPort, 
				 sizeof(struct DelayRequest) + mtu))) 
      break;
    error = FALSE;
    adu->adu_TxEd[i] = txed;
    txed->tr_node.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
    txed->tr_node.io_Device = adb->ad_Timer.tr_node.io_Device;
    txed->tr_node.io_Unit = adb->ad_Timer.tr_node.io_Unit;
    txed->tr_node.io_Command = TR_ADDREQUEST;
    txed->dr_Unit = adu;
    txed->dr_Len = mtu;
    AddHead(&adu->adu_FreeToTx, txed);
  }

  if (!error) {
    adu->adu_State |= AGUF_ONLINE;
    GetSysTime(&adu->adu_Stats.LastStart);
    /* In case someone wants to know...*/
    DoEvent(adu, S2EVENT_ONLINE);
  }
  return error;
}

/*
 * This routine handles CMD_OFFLINE commands.
 * Any pending read or write request will be sent to 
 * their owners
 */
static VOID 
Offline(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  struct AgnetDevice *adb = (struct AgnetDevice *)adu->adu_Device;

  if (adu->adu_State & AGUF_ONLINE) {
    /* We're online, so shut everything down. */
    LockUnit(adu);
    DoOffline(adu);
    UnlockUnit(adu);
  }

  TermIO(ios2);
}

/*
 * Put the unit offline 
 *
 * Caller should have a lock on the unit
 */
VOID
DoOffline(struct AgnetDevUnit *adu)
{
  struct AgnetDevice *adb = (struct AgnetDevice *)adu->adu_Device;
  struct IOSana2Req *ios2;
  int i;
  struct DelayRequest *txed;

  adu->adu_State &= ~AGUF_ONLINE;

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

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

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

  /* 
   * Abort still pending delayrequests, 
   * free all delayrequests
   */
  for (i = MAX_TXED - 1; i >= 0; i--) {
    if (txed = adu->adu_TxEd[i]) {
      if (!CheckIO(txed))
	AbortIO(txed);
      WaitIO(txed);		/* also removes from free queue */
      DeleteIORequest(txed);
    }
    adu->adu_TxEd[i] = NULL;
  }
  
  DoEvent(adu, S2EVENT_OFFLINE);
}
/* 
 * This routine handles S2_ONEVENT commands 
 */
static VOID 
OnEvent(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  /* Two special cases.
   *  If we are (on/off)line, and ask (on/off)line event,
   *  we return immediately 
   */
  if (ios2->ios2_WireError == S2EVENT_ONLINE &&
      adu->adu_State & AGUF_ONLINE ||
      ios2->ios2_WireError == S2EVENT_OFFLINE &&
      !(adu->adu_State & AGUF_ONLINE)) {
    TermIO(ios2);
  } else {
    LockUnit(adu);
    AddTail((struct List *)&adu->adu_Events, (struct Node *)ios2);
    UnlockUnit(adu);
  }
}

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

  LockUnit(adu);

  ios2 = (struct IOSana2Req *)adu->adu_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);
      ios2->ios2_WireError = event;
      TermIO(ios2);
    }
    ios2 = ios2_next;
  }

  UnlockUnit(adu);
}

/*
 * This function is used for handling CMD_WRITE and S2_BROADCAST
 * commands.
 */
static VOID 
WritePacket(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  /* Make sure that we are online. */
  if (!(adu->adu_State & AGUF_ONLINE)) {
    /* Sorry, we're offline */
    ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
    ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
    TermIO(ios2);
    return;
  }

  /* Is the data length legal? */
  if (ios2->ios2_DataLength < adu->adu_MinTU ||
      ios2->ios2_DataLength > adu->adu_MaxTU) {
    /* Sorry, the packet is too long or too small! */
    ios2->ios2_Req.io_Error = S2ERR_MTU_EXCEEDED;
    ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
    TermIO(ios2);
    DoEvent(adu, S2EVENT_SOFTWARE | S2EVENT_TX | S2EVENT_ERROR);
    return;
  }

  /* We call the SendPacket if no delay */
  ios2->ios2_Req.io_Flags &= ~IOF_QUICK;

  {
    struct DelayRequest *delayed;
    LockUnit(adu);
    AddTail((struct List *)&adu->adu_Tx,(struct Node *)ios2);
    /* 
     * If there is free delay requests, 
     * try to send immediately
     */
    delayed = (struct DelayRequest *)
      RemHead((struct List *)&adu->adu_FreeToTx);
    UnlockUnit(adu);

    if (delayed)
      SendPacket(delayed);
  }
  return;
}

/*
 * This routine handles CMD_READ commands.  We
 * always queue these unless we're offline.
 */
static VOID 
ReadPacket(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  if(adu->adu_State & AGUF_ONLINE) {
    /* Queue it... */
    LockUnit(adu);
    AddTail((struct List *)&adu->adu_Rx, (struct Node *)ios2);
    UnlockUnit(adu);
    return;
  }

  /* 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.
 */
static VOID
ReadOrphan(struct AgnetDevUnit *adu, struct IOSana2Req *ios2)
{
  if(adu->adu_State & AGUF_ONLINE) {
    /* Queue it... */
    LockUnit(adu);
    AddTail((struct List *)&adu->adu_RxOrph, (struct Node *)ios2);
    UnlockUnit(adu);
    return;
  }

  /* Sorry, we're offline */
  ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
  ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
  TermIO(ios2);
}

/*
 * "Send" next queued packet 
 */
static VOID
SendPacket(struct DelayRequest *delayed)
{
  struct AgnetDevUnit *adu = delayed->dr_Unit;
  struct BufferManagement *bm;
  struct IOSana2Req *ios2 = NULL;
  LONG length; ULONG delay;

  LockUnit(adu);

  while (!ios2) {
    ios2 = (struct IOSana2Req *)RemHead((struct List *)&adu->adu_Tx);
    /* There is nothing to send */
    if (!ios2) {
      AddHead((struct List *)&adu->adu_FreeToTx,(struct Node *)delayed);
      UnlockUnit(adu);
      return;
    }

    length = ios2->ios2_DataLength;

    /* Update statistics */
    PacketSent(adu, length, ios2->ios2_PacketType);

    /* Should we lose the packet? */
    if (adu->adu_Loss && adu->adu_Loss > LRandom() % LOSS_MAX) {
      TermIO(ios2);
      ios2 = NULL;
    }
  }
  UnlockUnit(adu);

  if (length > delayed->dr_Len)
    length = delayed->dr_Len;

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

  /* Copy the data out of the packet into timer request buffer. */
  if (bm->bm_CopyFromBuffer &&		  
      (*bm->bm_CopyFromBuffer)	/* we should have this tag, really */
      (delayed->dr_Data, ios2->ios2_Data, length)) {
    delayed->dr_DataLen = length;
    /* Copy addresses */
    if ((delayed->dr_PPUnit = adu->adu_PPUnit) < 0) {
      WORD adrlen = adu->adu_AddrFieldSize + 7 >> 3;
      memcpy(delayed->dr_DstAddr, ios2->ios2_DstAddr, adrlen);
      memcpy(delayed->dr_SrcAddr, adu->adu_Addr, adrlen);
    }
    /* Set hwtype */
    delayed->dr_HardwareType = adu->adu_HardwareType;
    delayed->dr_PacketType = ios2->ios2_PacketType;
    /* We should recognize broadcasts or multicasts */
    delayed->dr_Cmd = ios2->ios2_Req.io_Command;
    /*  Make errors */
    if (adu->adu_Errors) {
      LONG total = adu->adu_Errors * length * 8 / (LRandom() % ERRORS_MAX);
      while (total-- > 0) {
      /* Make a bit error.. */
	ULONG position = LRandom() & 0x7fffffffL;
	/* flip a bit */
	delayed->dr_Data[(position / 8) % length] ^= 1 << (position % 8);
      }
    }

    /* calculate delay */
    delay = adu->adu_Delay;
    if (delay && adu->adu_Deviation) 
      delay = RandomDev(delay, adu->adu_Deviation);

    if (delay) {
      delayed->tr_node.io_Command = TR_ADDREQUEST;
      delayed->tr_time.tv_secs = delay / 1000;
      delayed->tr_time.tv_micro = (delay % 1000) * 1000;
      SendIO(delayed);
    } else {
      /* Fallthrough */
      ReceivePacket(delayed);
    }
  } else {
    /* Something went wrong...*/
    ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
    ios2->ios2_WireError = S2WERR_BUFF_ERROR;
    DoEvent(adu, S2EVENT_SOFTWARE | S2EVENT_TX | S2EVENT_BUFF | S2EVENT_ERROR);
  }

  TermIO(ios2);
}

/*
 * This routine is called whenever a packet is received.
 * It return all appropriate read requests
 */
static VOID
ReceivePacket(struct DelayRequest *delayed)
{
  struct AgnetDevUnit *adu;
  int i; 

  if (delayed->dr_PPUnit >= 0 && delayed->dr_PPUnit < AD_MAXUNITS) {
    adu = (struct AgnetDevUnit *)AgnetDeviceBase->ad_Units[delayed->dr_PPUnit];
    if (adu)
      DoReceive(adu, delayed);
  } else {
    for (i = 0; i < AD_MAXUNITS; i++) {
      if (!(adu = (struct AgnetDevUnit *)AgnetDeviceBase->ad_Units[i]))
	continue;			/* No unit */
      if (delayed->dr_HardwareType != adu->adu_HardwareType) 
	continue;			/* Different hardware */
      if (delayed->dr_Cmd != S2_BROADCAST &&
	  memcmp(delayed->dr_DstAddr, adu->adu_Addr, 
		 adu->adu_AddrFieldSize + 7 >> 3)) 
	continue;			/* Address mismatched */
      DoReceive(adu, delayed);
    }
  } 

  SendPacket(delayed);
}

/* 
 * DoReceive
 */
static VOID 
DoReceive(struct AgnetDevUnit *adu, struct DelayRequest *delayed)
{
  ULONG length = delayed->dr_DataLen;
  ULONG type   = delayed->dr_PacketType;
  struct IOSana2Req *rxr;

  /* Update statistics */
  PacketReceived(adu, length, type);
  if (adu->adu_MaxTU < length) {
    PacketOverrun(adu);
    return;
  }
  if (adu->adu_MinTU > length) {
    ReceivedGarbage(adu);
    return;
  }

  /* Find an appropriate request wanting this packet type */
  LockUnit(adu);

  for (rxr = (struct IOSana2Req *)adu->adu_Rx.mlh_Head;
       rxr->ios2_Req.io_Message.mn_Node.ln_Succ; 
       rxr = (struct IOSana2Req *)rxr->ios2_Req.io_Message.mn_Node.ln_Succ) {
    if (rxr->ios2_PacketType == type) {
      Remove((struct Node *)rxr);
      CopyBack(adu, rxr, delayed);
      rxr = NULL;
      break;
    }
  }

  if (rxr) {
    /* Nobody wants this packet type? So, it's orphan */
    ReceivedOrphan(adu);
    if (rxr = (struct IOSana2Req *)
	RemHead((struct List *)&adu->adu_RxOrph)) {
      rxr -> ios2_PacketType = type;
      CopyBack(adu, rxr, delayed);
    } else {
      /* Nobody is interested in this packet, drop it */
      PacketDropped(adu, type);
    }
  }
  
  UnlockUnit(adu);
}

/*
 * Copy received packet into a request,
 * return the request to the caller
 */
static VOID
CopyBack(struct AgnetDevUnit *adu, struct IOSana2Req *ios2, 
	 struct DelayRequest *delayed)
{
  struct BufferManagement * bm =
    (struct BufferManagement *)ios2->ios2_BufferManagement;
  void *data = delayed->dr_Data;
  ULONG length = delayed->dr_DataLen;
  WORD  adrlen = (adu->adu_AddrFieldSize + 7) >> 3;

  /* Copy the data into the protocol stack's buffer using its
     supplied callback routine. */
  if (bm->bm_CopyToBuffer &&
      (*bm->bm_CopyToBuffer)(ios2->ios2_Data, data, length)) {
    ios2->ios2_DataLength =	length;

    if (delayed->dr_Cmd == S2_BROADCAST) {
      ios2->ios2_Req.io_Flags |= SANA2IOB_BCAST;
      memset(ios2->ios2_DstAddr, 0xff, adrlen);
    } else {
      memcpy(ios2->ios2_DstAddr, adu->adu_Addr, adrlen);
    }
    memcpy(ios2->ios2_SrcAddr, delayed->dr_SrcAddr, adrlen); 

    return TermIO(ios2);
  } else {
    ios2->ios2_DataLength   = 0;
    ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
    ios2->ios2_WireError    = S2WERR_BUFF_ERROR;
    DoEvent(adu, S2EVENT_SOFTWARE | S2EVENT_RX | S2EVENT_BUFF | S2EVENT_ERROR);
    return TermIO(ios2);
  }
}


