/*
 *  cnet.c - cnet.device c rewrite, for MorphOS - v2.0
 *
 *  Copyright © 2000-2001 by Harry "Piru" Sintonen.
 *  Distribution rights granted to the MorphOS team.
 *
 * notes:
 * - building requires the updated card.h include file!
 *
 * bugs:
 * - crash at card insert when DEBUG is enabled
 *
 * todo:
 * - test card.resource System V4 ABI PPC callback system
 * - RAW read & write tests (ShapeShifter)
 * - S2_DMACopyToBuff32 support
 * - MorphOS testing
 *
 * history:
 * 18012001 - source cleanup, made it use declgate.h.
 *          - reordered static const structures so romtag is in the
 *            beginning, looks better.
 * 01022001 - fixed a broadcast detection bug from readioreq.
 *          - fixed a SANA2IOF_RAW bug from readioreq. ShapeShifter
 *            works again.
 * 05022001 - added StackSwap() to EasyReq().
 */

#ifdef __MORPHOS__
#  define EMUL_NOQUICKMODE    1
#else
#  define RTF_PPC             0
#endif /* __MORPHOS__ */

#include "cnet.h"
#include "myregargs.h"
#include "declgate.h"

#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/cardres.h>
#include <proto/timer.h>
#include <proto/intuition.h>

#if defined(__SASC) || defined(__MORPHOS__)
#include <clib/alib_protos.h>
#endif

#define DB_LEVEL 1

#ifdef DEBUG
#  define stcode
#  ifndef __MORPHOS__
#    include <stdarg.h>
#  endif /* !__MORPHOS__ */
   void dprintf(const char *, ...);
#  ifdef __GNUC__
#    define KPRINTF(l, x) do { if ((l) >= DB_LEVEL) \
     { dprintf("%s:%s/%lu: ", __FILE__, __FUNCTION__, __LINE__); dprintf x;} } while (0)
#  else
#    define KPRINTF(l, x) do { if ((l) >= DB_LEVEL) \
     { dprintf("%s/%lu: ", __FILE__, __LINE__); dprintf x;} } while (0)
#  endif /* __GNUC__ */
#  define DB(x) x
   void dumpmem(APTR mem, ULONG len);
#else /* !DEBUG */
#  define stcode static
#  define KPRINTF(l, x) ((void) 0)
#  define DB(x)
#endif /* DEBUG */

#define stdata static
#define inline __inline

#define ciaa (*(volatile struct CIA *) 0xbfe001)
/*#define custom (*(volatile struct Custom *) 0xdff000)
*/

#define ATTRMEM_BASE base->dd_attrmem


#define deverror(ioerr, wireerr) (((wireerr) << 8) | ((ioerr) & 0xff))

#define initlistwalk(type, name, list) (name) = (type) list.mlh_Head;
#define listwalk(type, name)                            \
  (((struct MinNode *)((name) = (type)                  \
  (((struct MinNode *) (name))->mln_Succ)))->mln_Succ)

#define mcmp(a,b) ((((ULONG *) (a))[0] == ((ULONG *) (b))[0]) &&  \
                   (((UWORD *) (a))[2] == ((UWORD *) (b))[2]))


#define PUDDLESIZE 2048
#define THRESHSIZE 512
#define TX_RETRIES 3

#define DEVNAME             "cnet.device"
#define VERSION             2
#define REVISION            0
#define VERSION_STR         "2"
#define REVISION_STR        "0"
#define DATE                "5.2.2001"

#if 0
#  define delay() do { UBYTE d; (void) (d = ciaa.ciapra); } while (0)
#else
#  define delay() ((void) 0)
#endif


/* this is a device, not executable
*/
static int fakemain(void)
{
  return -1;
}

#ifdef __MORPHOS__
/* important!
*/
const ULONG __amigappc__ = 1;
#endif /* __MORPHOS__ */


#ifdef __MORPHOS__

stcode struct MyDevice *devInit(struct MyDevice *base,
                                BPTR seglist,
                                struct ExecBase *SysBase);
#else

stcode struct MyDevice *NATDECLFUNC_3(devInit,
                                      d0, struct MyDevice *, base,
                                      a0, BPTR, seglist,
                                      a6, struct ExecBase *, SysBase);
#endif /* __MORPHOS__ */


stdata const char devname[];
stdata const char devidstring[];
stdata const struct initstruct deviceinitstruct;

stdata const struct Resident ROMTag =
{
  RTC_MATCHWORD,
  (APTR) &ROMTag,
  (APTR) (&ROMTag + 1),
  RTF_PPC | RTF_AUTOINIT,
  VERSION,
  NT_DEVICE,
  0,
  (char *) devname,
  (char *) devidstring,
  (APTR) &deviceinitstruct
};


/* base function prototypes
*/

stcode struct MyDevice *NATDECLFUNC_4(devOpen,
                                      a1, struct IOSana2Req *, ioreq,
                                      d0, LONG, unit,
                                      d1, ULONG, flags,
                                      a6, struct MyDevice *, base);

stcode BPTR NATDECLFUNC_2(devClose,
                          a1, struct IOSana2Req *, ioreq,
                          a6, struct MyDevice *, base);

stcode BPTR NATDECLFUNC_1(devExpunge,
                          a6, struct MyDevice *, base);

stcode ULONG devReserved(void);

stcode void NATDECLFUNC_2(devBeginIO,
                          a1, struct IOSana2Req *, ioreq,
                          a6, struct MyDevice *, base);

stcode void NATDECLFUNC_2(devAbortIO,
                          a1, struct IOSana2Req *, ioreq,
                          a6, struct MyDevice *, base);


/* static data
*/

stdata const ULONG FuncTable[];

stdata const struct initstruct deviceinitstruct =
{
  sizeof(struct MyDevice),
  FuncTable,
  NULL,
  (void (*)(void)) devInit
};


stdata const ULONG FuncTable[] =
{
#ifdef __MORPHOS__
  FUNCARRAY_32BIT_NATIVE,
#endif /* __MORPHOS__ */
  (ULONG) devOpen,
  (ULONG) devClose,
  (ULONG) devExpunge,
  (ULONG) devReserved,
  (ULONG) devBeginIO,
  (ULONG) devAbortIO,
  ~0
};

stdata const char devname[] = DEVNAME;
stdata const char devidstring[] = DEVNAME " " VERSION_STR "." REVISION_STR " (" DATE ")\r\n";
stdata const char vers[] = "$VER: " DEVNAME " " VERSION_STR "." REVISION_STR " (" DATE ")";

stdata const struct Sana2DeviceQuery DeviceQueryBlock[] =
{
  {
  sizeof(struct Sana2DeviceQuery), sizeof(struct Sana2DeviceQuery),
  0, 0,
  ETHER_ADDR_SIZE * 8,
  ETHERPKT_SIZE,
  10000000,
  S2WireType_Ethernet
  }
};

stdata const UBYTE default_address[ETHER_ADDR_SIZE] =
{
  0x00, 0x00, 0x12, 0x34, 0x56, 0x78
};



/* protos
*/

stcode void DECLFUNC_1(removed,
                       a1, struct MyDevice *, base);
stcode void DECLFUNC_1(inserted,
                       a1, struct MyDevice *, base);
stcode UBYTE DECLFUNC_2(status,
                       a1, struct MyDevice *, base,
                       d0, UBYTE, intmask);
stcode void DECLFUNC_1(tx,
                       a1, struct MyDevice *, base);
stcode void DECLFUNC_1(rx,
                       a1, struct MyDevice *, base);

DECLGATE(stcode const, removed, LIBNR)
DECLGATE(stcode const, inserted, LIBNR)
DECLGATE(stcode const, status, LIB)
DECLGATE(stcode const, tx, LIBNR)
DECLGATE(stcode const, rx, LIBNR)


stcode BPTR idevExpunge(struct MyDevice *base);

stcode void iremoved(struct MyDevice *base);
stcode void iinserted(struct MyDevice *base);
stcode UBYTE istatus(struct MyDevice *base,
                     UBYTE intmask);

stcode void AbortList(struct MinList *list,
                      short error,
                      struct MyDevice *base);
stcode void AbortRW(short error,
                    struct MyDevice *base);


stcode inline unsigned long int ether_crc(int length,
                                          unsigned char *data);
stcode void nic_update_multicasts(struct MyDevice *base);
stcode short addmcastrange(struct IOSana2Req *ioreq,
                           UBYTE *lower,
                           UBYTE *upper,
                           struct MyDevice *base);

stcode short delmcastrange(struct IOSana2Req *ioreq,
                           UBYTE *lower,
                           UBYTE *upper,
                           struct MyDevice *base);

stcode void refresh_typestats(struct MyDevice *base);
stcode struct Sana2PacketTypeStats *findpackettypestats(UWORD packettype,
                                                        struct MyDevice *base);

stcode short cmdRead(struct IOSana2Req *ioreq,
                     struct MyDevice *base);
stcode short cmdWrite(struct IOSana2Req *ioreq,
                      struct MyDevice *base);
stcode short cmdFlush(struct IOSana2Req *ioreq,
                      struct MyDevice *base);
stcode short cmdDeviceQuery(struct IOSana2Req *ioreq,
                            struct MyDevice *base);
stcode short cmdGetStationAddress(struct IOSana2Req *ioreq,
                                  struct MyDevice *base);
stcode short cmdConfigInterface(struct IOSana2Req *ioreq,
                                struct MyDevice *base);
stcode short cmdAddMulticastAddress(struct IOSana2Req *ioreq,
                                    struct MyDevice *base);
stcode short cmdDelMulticastAddress(struct IOSana2Req *ioreq,
                                    struct MyDevice *base);
stcode short cmdMulticast(struct IOSana2Req *ioreq,
                          struct MyDevice *base);
stcode short cmdBroadcast(struct IOSana2Req *ioreq,
                          struct MyDevice *base);
stcode short cmdTrackType(struct IOSana2Req *ioreq,
                          struct MyDevice *base);
stcode short cmdUntrackType(struct IOSana2Req *ioreq,
                            struct MyDevice *base);
stcode short cmdGetTypeStats(struct IOSana2Req *ioreq,
                             struct MyDevice *base);
stcode short cmdGetSpecialStats(struct IOSana2Req *ioreq,
                                struct MyDevice *base);
stcode short cmdGetGlobalStats(struct IOSana2Req *ioreq,
                               struct MyDevice *base);
stcode short cmdOnEvent(struct IOSana2Req *ioreq,
                        struct MyDevice *base);
stcode short cmdReadOrphan(struct IOSana2Req *ioreq,
                           struct MyDevice *base);
stcode short cmdOnline(struct IOSana2Req *ioreq,
                       struct MyDevice *base);
stcode short cmdOffline(struct IOSana2Req *ioreq,
                        struct MyDevice *base);
stcode short cmdNSDeviceQuery(struct IOStdReq *ioreq,
                              struct MyDevice *base);
stcode short cmdAddMulticastAddresses(struct IOSana2Req *ioreq,
                                      struct MyDevice *base);
stcode short cmdDelMulticastAddresses(struct IOSana2Req *ioreq,
                                      struct MyDevice *base);

stcode void *callcopy(void *routine,
                      void *from,
                      void *to,
                      ULONG len);
stcode BOOL callfilter(void *hook,
                       void *ios2,
                       void *data);
stcode void writepacket(struct IOSana2Req *ioreq,
                        struct MyDevice *base);

stcode void readioreq(struct prhdr *prhdr,
                      UWORD datasize,
                      struct IOSana2Req *ioreq,
                      UBYTE *flags,
                      struct MyDevice *base);
stcode void readpacket(struct prhdr *prhdr,
                       UWORD pageoff,
                       struct MyDevice *base);

stcode void devclosedown(struct MyDevice *base);
stcode void nic_kill(struct MyDevice *base);
stcode void nic_reset(struct MyDevice *base);
stcode void nic_put_multi(struct MyDevice *base);

stcode struct Unit *Open_Unit(struct IOSana2Req *ioreq,
                              LONG unit,
                              struct MyDevice *base);
stcode void Close_Unit(struct MyDevice *base,
                       struct IOSana2Req *ioreq);


stcode short init_card(UWORD flags,
                       struct MyDevice *base);
stcode void nic_setstationaddress(struct MyDevice *base);
stcode short nic_init(UWORD flags,
                      struct MyDevice *base);

stcode void RemoteRead(UWORD *buffer,
                       UWORD nicbuffer,
                       UWORD len,
                       struct MyDevice *base);

stcode short RemoteWrite(UWORD *etherhdr,
                         UWORD *packetdata,
                         UWORD writelen,
                         struct MyDevice *base);


stcode void DoEvent(ULONG events,
                    struct MyDevice *base);
stcode int AbortReq(struct MinList *list,
                    struct IOSana2Req *ioreq,
                    struct MyDevice *base);
stcode void TermIO(struct IOSana2Req *ioreq,
                   struct MyDevice *base);


stcode void *alloc(ULONG memSize,
                   struct MyDevice *base);
stcode void free(void *memory,
                 ULONG memSize,
                 struct MyDevice *base);

stcode void EasyReq(const UBYTE *bodymsg,
                    LONG *array,
                    struct MyDevice *base);



#ifdef __MORPHOS__

stcode struct MyDevice *devInit(struct MyDevice *base,
                                BPTR seglist,
                                struct ExecBase *SysBase)
{

#else

stcode struct MyDevice *NATDECLFUNC_3(devInit,
                                      d0, struct MyDevice *, base,
                                      a0, BPTR, seglist,
                                      a6, struct ExecBase *, SysBase)
{
  DECLARG_3(d0, struct potgoresbase *, base,
            a0, BPTR, dummyseglist,
            a6, struct ExecBase *, SysBase)

#endif /* __MORPHOS__ */

  KPRINTF(10, ("devInit base: 0x%08lx seglist: 0x%08lx SysBase: 0x%08lx\n",
               base, seglist, SysBase));

  if ((base->dd_cardres = OpenResource(CARDRESNAME)))
  {
    struct Library *CardResource = base->dd_cardres;

    if (CardInterface() == CARD_INTERFACE_AMIGA_0)
    {

      if ((base->dd_utilitybase =
        (struct UtilityBase *) OpenLibrary("utility.library", 40)))
      {

        base->dd_timereq.tr_node.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
        base->dd_timereq.tr_node.io_Message.mn_ReplyPort = NULL;
        base->dd_timereq.tr_node.io_Message.mn_Length = sizeof(struct timerequest);

        if (!OpenDevice("timer.device", UNIT_VBLANK, &base->dd_timereq.tr_node, 0))
        {
          base->dd_timerbase = base->dd_timereq.tr_node.io_Device;

          if ((base->dd_mempool = CreatePool(MEMF_PUBLIC | MEMF_CLEAR,
                                             PUDDLESIZE, THRESHSIZE)))
          {

            /* query the information about the card.resource memory map
            */
            base->dd_cmm = GetCardMap();
            base->dd_attrmem = (nic_t *) base->dd_cmm->cmm_AttributeMemory;

            /* init memory pool semaphore
            */
            InitSemaphore(&base->dd_mempoolsema);

            /* initialize device node & library struct
            */
            base->dd_library.lib_Node.ln_Type = NT_DEVICE;
            base->dd_library.lib_Node.ln_Name = (char *) devname;
            base->dd_library.lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
            base->dd_library.lib_Version = VERSION;
            base->dd_library.lib_Revision = REVISION;
            base->dd_library.lib_IdString = (char *) devidstring;

            /* initialize lists
            */
            NewList((struct List *) &base->dd_orphanlist);
            NewList((struct List *) &base->dd_writelist);
            NewList((struct List *) &base->dd_eventlist);
            NewList((struct List *) &base->dd_bufmanlist);
            NewList((struct List *) &base->dd_tracklist);
            NewList((struct List *) &base->dd_multicasts);

            InitSemaphore(&base->dd_multicastssema);

            /* init receive interrupt
            */
            base->dd_rxint.is_Node.ln_Type = NT_INTERRUPT;
            base->dd_rxint.is_Node.ln_Pri = 16;
            base->dd_rxint.is_Node.ln_Name = "cnet rx softint";
            base->dd_rxint.is_Data = (APTR) base;
            base->dd_rxint.is_Code = (void (*)(void)) &rx;

            /* init transmit interrupt
            */
            base->dd_txint.is_Node.ln_Type = NT_INTERRUPT;
            base->dd_txint.is_Node.ln_Pri = 0;
            base->dd_txint.is_Node.ln_Name = "cnet tx softint";
            base->dd_txint.is_Data = (APTR) base;
            base->dd_txint.is_Code = (void (*)(void)) &tx;

            /* init cardhandle ints
            */
            base->dd_cardremoved.is_Data = (APTR) base;
            base->dd_cardremoved.is_Code = (void (*)(void)) &removed;

            base->dd_cardinserted.is_Data = (APTR) base;
            base->dd_cardinserted.is_Code = (void (*)(void)) &inserted;

            base->dd_cardstatus.is_Data = (APTR) base;
            base->dd_cardstatus.is_Code = (void (*)(void)) &status;

#ifdef __MORPHOS__
            base->dd_ppccardremoved.is_Data = (APTR) base;
            base->dd_ppccardremoved.is_Code = (void (*)(void)) &iremoved;

            base->dd_ppccardinserted.is_Data = (APTR) base;
            base->dd_ppccardinserted.is_Code = (void (*)(void)) &iinserted;

            base->dd_ppccardstatus.is_Data = (APTR) base;
            base->dd_ppccardstatus.is_Code = (void (*)(void)) &istatus;
#endif /* __MORPHOS__ */

            base->dd_typestats2048 = NULL;
            base->dd_typestats2054 = NULL;

            /* store sysbase & segment
            */
            base->dd_SysBase = SysBase;
            base->dd_seglist = seglist;

            KPRINTF(10, ("devInit: Ok\n"));

          }
          else
          {
            KPRINTF(10, ("devInit: CreatePool() failed!\n"));
            CloseDevice(&base->dd_timereq.tr_node);
            CloseLibrary((struct Library *) base->dd_utilitybase);
            base = NULL;
          }
        }
        else
        {
          KPRINTF(10, ("devInit: OpenDevice(\"timer.device\",...) failed!\n"));
          CloseLibrary((struct Library *) base->dd_utilitybase);
          base = NULL;
        }
      }
      else
      {
        KPRINTF(10, ("devInit: OpenLibrary(\"utility.library\", 40) failed!\n"));
        base = NULL;
      }
    }
    else
    {
      KPRINTF(10, ("devInit: Unknown card.resource interface type\n"));
      base = NULL;
    }
  }
  else
  {
    KPRINTF(10, ("devInit: OpenResource(\"card.resource\") failed!\n"));
    base = NULL;
  }

  KPRINTF(10, ("devInit: openCnt = %ld\n", base->dd_library.lib_OpenCnt));

  return base;
}


stcode struct MyDevice *NATDECLFUNC_4(devOpen,
                                      a1, struct IOSana2Req *, ioreq,
                                      d0, LONG, unit,
                                      d1, ULONG, flags,
                                      a6, struct MyDevice *, base)
{
  DECLARG_4(a1, struct IOSana2Req *, ioreq,
            d0, LONG, unit,
            d1, ULONG, flags,
            a6, struct MyDevice *, base)

  /*struct ExecBase *SysBase = base->dd_SysBase;
  */
  UWORD prevmask = 0;

  KPRINTF(10, ("devOpen ioreq: 0x%08lx unit: %ld flags: 0x%08lx base: 0x%08lx\n",
               ioreq, unit, flags, base));

  ++base->dd_library.lib_OpenCnt;
  base->dd_library.lib_Flags &= ~LIBF_DELEXP;

  KPRINTF(10, ("devOpen: openCnt = %ld\n", base->dd_library.lib_OpenCnt));

  if (ioreq->ios2_Req.io_Message.mn_Length < sizeof(struct IOSana2Req))
  {
    KPRINTF(20, ("devOpen: invalid MN_LENGTH!\n"));

    ioreq->ios2_Req.io_Error = IOERR_BADLENGTH;
    goto openfail;
  }

  /* default to open failure.
  */
  ioreq->ios2_Req.io_Error = IOERR_OPENFAIL;

  /* are we exclusively open?
  */
  if (base->dd_flags & DDF_MINE)
  {
    KPRINTF(20, ("devOpen: device already in use exclusively!\n"));
    goto openfail;
  }

  /* opener want exclusive mode?
  */
  if (flags & SANA2OPF_MINE)
  {
    if (base->dd_library.lib_OpenCnt != 1)
    {
      KPRINTF(20, ("devOpen: device already in use non-exclusively!\n"));
      goto openfail;
    }

    prevmask = (base->dd_flags & (DDF_MINE | DDF_PROM)) ^ (DDF_MINE | DDF_PROM);

    /* set exclusive mode
    */
    base->dd_flags |= DDF_MINE;

    if (flags & SANA2OPF_PROM)
    {
      /* set promiscuous mode
      */
      base->dd_flags |= DDF_PROM;
    }
  }


  /* open unit
  */
  if ((ioreq->ios2_Req.io_Unit = Open_Unit(ioreq, unit, base)) == NULL)
  {
    KPRINTF(20, ("devOpen: could not open unit!\n"));
    goto openfail;
  }


  /* nic already initialised ?
  */
  if (!(base->dd_flags & DDF_NICUP))
  {
    /* init PCMCIA card & set up nic
    */
    if (init_card(0, base) || nic_init(0, base))
    {
      Close_Unit(base, ioreq);

      KPRINTF(20, ("devOpen: could not init the card!\n"));
      goto openfail2;
    }
  }

  /* automagically go back online?
  */
  if (!(base->dd_flags & DDF_OFFLINE))
  {
    base->dd_flags |= DDF_ONLINE;
  }

  /* opended ok!
  */
  ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
  ioreq->ios2_Req.io_Error = 0;
  return base;


  openfail2:
  if (base->dd_flags & DDF_CARDHANDLE)
  {
    struct Library *CardResource = base->dd_cardres;

    /* now it's offline
    */
    base->dd_flags &= ~(DDF_ONLINE | DDF_CARDHANDLE);

    /* stop controller
    */
    nic_kill(base);

    KPRINTF(10, ("devOpen: cleanup calling ReleaseCard...\n"));
    ReleaseCard(&base->dd_cardhandle, CARDF_REMOVEHANDLE);
  }

  openfail:
  base->dd_flags &= ~prevmask;

  ioreq->ios2_Req.io_Unit = (APTR) -1;
  ioreq->ios2_Req.io_Device = (APTR) -1;
  base->dd_library.lib_OpenCnt--;
  return NULL;
}


/*
*======================================================================
*                  init_card(flags, device)
*======================================================================
*
*                    Initialise PCMCIA card
*/
stcode short init_card(UWORD flags,
                       struct MyDevice *base)
{
  struct Library *CardResource = base->dd_cardres;
  UWORD r;
  BOOL funcid = FALSE, network = FALSE;

  KPRINTF(10, ("init_card\n"));

  /* have we called OwnCard() yet?
  */
  if (!(base->dd_flags & DDF_CARDHANDLE))
  {
    /* init cardhandle
    */
#ifdef __MORPHOS__
    base->dd_cardhandle.cah_CardNode.ln_Type = CNT_PPCCARDHANDLE;
    base->dd_cardhandle.cah_PPCType = 0;
    base->dd_cardhandle.cah_PPCCardRemoved = &base->dd_ppccardremoved;
    base->dd_cardhandle.cah_PPCCardInserted = &base->dd_ppccardinserted;
    base->dd_cardhandle.cah_PPCCardStatus = &base->dd_ppccardstatus;
#else
    base->dd_cardhandle.cah_CardNode.ln_Type = 0;  /* MUST be 0! */
#endif
    base->dd_cardhandle.cah_CardNode.ln_Pri = 20;
    base->dd_cardhandle.cah_CardNode.ln_Name = (char *) devname;

    base->dd_cardhandle.cah_CardRemoved = &base->dd_cardremoved;
    base->dd_cardhandle.cah_CardInserted = &base->dd_cardinserted;
    base->dd_cardhandle.cah_CardStatus = &base->dd_cardstatus;
    base->dd_cardhandle.cah_CardFlags = CARDF_IFAVAILABLE | CARDF_POSTSTATUS;

    KPRINTF(10, ("init_card: calling OwnCard()...\n"));

    if (OwnCard(&base->dd_cardhandle) == 0)
    {
      KPRINTF(10, ("init_card: OwnCard() succesful, card owned\n"));

      base->dd_flags |= DDF_CARDHANDLE;
    }
    else
    {
      KPRINTF(10, ("init_card: OwnCard() failed!\n"));
      goto out;
    }
  }

  /* We own the card now. Now do a smart probing for CISTPL_FUNC_ID tuple.
  */

  for (r = 0; r < 8; r++)
  {
    KPRINTF(10, ("init_card: CopyTuple(CISTPL_FUNC_ID)\n"));
    if (CopyTuple(&base->dd_cardhandle, base->dd_tuplebuf,
                  (r << 16) | CISTPL_FUNC_ID, 40))
    {
      KPRINTF(10, ("init_card: CopyTuple(CISTPL_FUNC_ID) done\n"));

      funcid = TRUE;
      if (base->dd_tuplebuf[2] == CISTPL_FUNCID_NETWORK)
      {
        /* break out - we found what we were looking for
        */
        network = TRUE;
        break;
      }
    }
    else
    {
      KPRINTF(10, ("init_card: CopyTuple(CISTPL_FUNC_ID) done\n"));
    }
  }

  if (funcid)
  {
    if (network)
    {
      KPRINTF(10, ("init_card: CISTPL_FUNC_ID say CISTPL_FUNCID_NETWORK\n"));
    }
    else
    {
      KPRINTF(10, ("init_card: CISTPL_FUNC_ID tuple say no CISTPL_FUNCID_NETWORK\n"));
      if (!(flags & 1))
      {
        EasyReq("CISTPL_FUNC_ID tuple say\n"
                "no CISTPL_FUNCID_NETWORK", NULL, base);
      }
      goto out;
    }
  }
  else
  {
    KPRINTF(10, ("init_card: no CISTPL_FUNC tuple, assuming network card\n"));
  }

  if (CopyTuple(&base->dd_cardhandle, base->dd_tuplebuf, CISTPL_CONF_MAP, 40))
  {
    ULONG addr = 0;

    r = base->dd_tuplebuf[2] & 0x3;
    do
    {
      addr = (addr << 8) | base->dd_tuplebuf[r + 4];
    } while (r--);

    KPRINTF(10, ("init_card: config address: 0x%08lx\n", addr));

    if (addr < base->dd_cmm->cmm_AttributeMemSize)
    {
      if (CopyTuple(&base->dd_cardhandle, base->dd_tuplebuf, CISTPL_CONFIG, 40))
      {
        CardMiscControl(&base->dd_cardhandle,
                        CARD_DISABLEF_WP | CARD_ENABLEF_DIGAUDIO);
        
        /* extract configuration ID value,
           put ID into card config register
        */
        KPRINTF(10, ("init_card: id: %lu card config register: 0x%08lx\n",
                     base->dd_tuplebuf[2] & 0x3f, ((UBYTE *) base->dd_attrmem) + addr));

        *((volatile UBYTE *) (((UBYTE *) base->dd_attrmem) + addr)) =
          (base->dd_tuplebuf[2] & 0x3f); delay();

        KPRINTF(10, ("init_card: Ok\n"));
        return 0;
      }
      else
      {
        KPRINTF(10, ("init_card: CISTPL_CONFIG tuple not found!\n"));
        if (!(flags & 1))
        {
          EasyReq("CISTPL_CONFIG tuple not found", NULL, base);
        }
      }
    }
    else
    {
      KPRINTF(10, ("init_card: CISTPL_CONF_MAP tuple config address insane!\n"));
      if (!(flags & 1))
      {
        EasyReq("CISTPL_CONF_MAP tuple\n"
                "config address insane", NULL, base);
      }
    }
  }
  else
  {
    KPRINTF(10, ("init_card: CISTPL_CONF_MAP tuple not found!\n"));
    if (!(flags & 1))
    {
      EasyReq("CISTPL_CONF_MAP tuple not found", NULL, base);
    }
  }

  out:
  DoEvent(S2EVENT_ERROR | S2EVENT_HARDWARE, base);
  KPRINTF(10, ("init_card failed!\n"));

  return -1;
}



stcode void *alloc(ULONG memSize,
                   struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  void *ret;

  ObtainSemaphore(&base->dd_mempoolsema);
  ret = AllocPooled(base->dd_mempool, memSize);
  ReleaseSemaphore(&base->dd_mempoolsema);

  return ret;
}

stcode void free(void *memory,
                 ULONG memSize,
                 struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;

  ObtainSemaphore(&base->dd_mempoolsema);
  FreePooled(base->dd_mempool, memory, memSize);
  ReleaseSemaphore(&base->dd_mempoolsema);
}


stcode struct Unit *Open_Unit(struct IOSana2Req *ioreq,
                              LONG unit,
                              struct MyDevice *base)
{
  void *taglist;

  KPRINTF(5, ("Open_Unit ioreq: 0x%08lx unit: %ld\n", ioreq, unit));

  /* only unit 0 supported
  */
  if (unit != 0)
  {
    return NULL;
  }

  /* got taglist? (don't fail if not available!)
  */
  if ((taglist = ioreq->ios2_BufferManagement))
  {
    struct ExecBase *SysBase = base->dd_SysBase;
    struct UtilityBase *UtilityBase = base->dd_utilitybase;
    struct bufman *bufman;

    if ((bufman = alloc(sizeof(struct bufman), base)) == NULL)
    {
      return NULL;
    }

    /* get copyfrom routines
    */

    bufman->copyfrombuf = (APTR) GetTagData(S2_CopyFromBuff, NULL, taglist);
    bufman->copytobuf = (APTR) GetTagData(S2_CopyToBuff, NULL, taglist);

    /* this is new SANA-II V2 addition
    */
    bufman->packetfilter = (APTR) GetTagData(S2_PacketFilter, NULL, taglist);

    /* these are new SANA-II V3 addition
    */
    bufman->dmacopyfrombuf32 = (APTR) GetTagData(S2_DMACopyFromBuff32, NULL, taglist);
    bufman->dmacopytobuf32 = (APTR) GetTagData(S2_DMACopyToBuff32, NULL, taglist);

    /* init the list for CMD_READ requests
    */
    NewList((struct List *) &bufman->rxqueue);

    /* add the new bufman to global bufmanlist
    */

    KPRINTF(5, ("Open_Unit: added bufman at 0x%08lx\n", bufman));

    Disable();
    AddHead((struct List *) &base->dd_bufmanlist, (struct Node *) bufman);
    Enable();

    ioreq->ios2_BufferManagement = bufman;

    KPRINTF(5, ("Open_Unit:\n"
                "copyfrombuf: 0x%08lx copytobuf: 0x%08lx packetfilter: 0x%08lx\n"
                "dmacopyfrombuf32: 0x%08lx dmacopytobuf32: 0x%08lx\n",
                bufman->copyfrombuf, bufman->copytobuf, bufman->packetfilter,
                bufman->dmacopyfrombuf32, bufman->dmacopytobuf32));
  }

  return (struct Unit *) 1;
}

stcode void Close_Unit(struct MyDevice *base,
                       struct IOSana2Req *ioreq)
{
  struct bufman *bufman;

  if ((bufman = ioreq->ios2_BufferManagement))
  {
    struct ExecBase *SysBase = base->dd_SysBase;

    KPRINTF(5, ("Close_Unit: remove bufman at 0x%08lx\n", bufman));

    ioreq->ios2_BufferManagement = NULL;

    Disable();
    Remove((struct Node *) bufman);
    Enable();
    free(bufman, sizeof(struct bufman), base);
  }
}


stcode BPTR NATDECLFUNC_2(devClose,
                          a1, struct IOSana2Req *, ioreq,
                          a6, struct MyDevice *, base)
{
  DECLARG_2(a1, struct IOSana2Req *, ioreq,
            a6, struct MyDevice *, base)

  BPTR ret = NULL;

  KPRINTF(10, ("devClose ioreq: 0x%08lx base: 0x%08lx\n", ioreq, base));

  ioreq->ios2_Req.io_Unit = (APTR) -1;
  ioreq->ios2_Req.io_Device = (APTR) -1;

  Close_Unit(base, ioreq);


  if (--base->dd_library.lib_OpenCnt == 0)
  {

    KPRINTF(5, ("devClose: devclosedown\n"));
    devclosedown(base);

    if (base->dd_library.lib_Flags & LIBF_DELEXP)
    {

      KPRINTF(5, ("devClose: calling expunge...\n"));

      ret = idevExpunge(base);
    }
  }

  KPRINTF(5, ("devClose: lib_OpenCnt = %ld\n", base->dd_library.lib_OpenCnt));

  return ret;
}

stcode BPTR idevExpunge(struct MyDevice *base)
{
  BPTR ret = NULL;

  KPRINTF(10, ("devExpunge base: 0x%08lx\n", base));

  if (base->dd_library.lib_OpenCnt == 0)
  {
    struct ExecBase *SysBase = base->dd_SysBase;

    KPRINTF(5, ("devExpunge: Unloading...\n"));

    ret = base->dd_seglist;

    /*
    KPRINTF(5, ("devExpunge: devclosedown\n"));
    devclosedown(base);
    */

    if (base->dd_flags & DDF_CARDHANDLE)
    {
      struct Library *CardResource = base->dd_cardres;
      base->dd_flags &= ~DDF_CARDHANDLE;

      KPRINTF(5, ("devExpunge: releasing cardhandle 0x%08lx\n",
                  &base->dd_cardhandle));
      ReleaseCard(&base->dd_cardhandle, CARDF_REMOVEHANDLE);
    }

    KPRINTF(5, ("devExpunge: freeing memorypool 0x%08lx\n",
                base->dd_mempool));
    DeletePool(base->dd_mempool);

    KPRINTF(5, ("devExpunge: closedevice timer ioreq 0x%08lx\n",
                &base->dd_timereq.tr_node));
    CloseDevice(&base->dd_timereq.tr_node);

    KPRINTF(5, ("devExpunge: closelibrary utilitybase 0x%08lx\n",
                base->dd_utilitybase));
    CloseLibrary((struct Library *) base->dd_utilitybase);

    /* NOTE: There is no CloseResource() call.
    */

    KPRINTF(5, ("devExpunge: removing device node 0x%08lx\n",
                &base->dd_library.lib_Node));
    Remove(&base->dd_library.lib_Node);

    KPRINTF(5, ("devExpunge: FreeMem()...\n"));
    FreeMem((char *) base - base->dd_library.lib_NegSize,
            (ULONG) (base->dd_library.lib_NegSize + base->dd_library.lib_PosSize));

    KPRINTF(5, ("devExpunge: Unloading done! cnet.device expunged!\n\n"));
    return ret;
  }
  else
  {
    KPRINTF(5, ("devExpunge: Could not expunge, LIBF_DELEXP set!\n"));
    base->dd_library.lib_Flags |= LIBF_DELEXP;
  }
  return NULL;
}

stcode BPTR NATDECLFUNC_1(devExpunge,
                          a6, struct MyDevice *, base)
{
  DECLARG_1(a6, struct MyDevice *, base)

  return idevExpunge(base);
}


stcode ULONG devReserved(void)
{
  return 0;
}


/*
*======================================================================
*                  devclosedown(device)
*======================================================================
*
* Put the nic into sleep... also do whatever nececcary hw magic
* before device closedown. mark the device offline and unconfigured.
*/
stcode void devclosedown(struct MyDevice *base)
{
  /* not online, cancel exclusive mode, cancel promiscuous mode
  */
  base->dd_flags &= ~(DDF_ONLINE | DDF_MINE | DDF_PROM);

  /* stop controller
  */
  nic_kill(base);
}

/*
*--------------------------------------------------------
*                     nic_kill(device)
*--------------------------------------------------------
*/
stcode void nic_kill(struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;

  Disable();

  /* nic initialized ?
  */
  if (base->dd_flags & DDF_NICUP)
  {
    nic_t *nic = ATTRMEM_BASE;

    /* mark killed
    */
    base->dd_flags &= ~DDF_NICUP;

    nic_reset(base);

    /* set rx to monitor mode
    */
    nic->nic_rcr = DSRC_MON; delay();

    /* set tx to loopback mode
    */
    nic->nic_tcr = DSTC_LB0; delay();
  }

  Enable();
}


stcode void nic_setstationaddress(struct MyDevice *base)
{
  nic_t *nic = ATTRMEM_BASE;
  UBYTE oldcmd;
  UBYTE *addr = base->dd_stationaddress;

  /* save old command
  */
  oldcmd = nic->nic_cr; delay();

  /* select bank 1
  */
  nic->nic_cr = DSCM_NODMA | DSCM_PG1 | DSCM_STOP; delay();

  /* set station address
  */
  nic->nic_par0 = *addr++; delay();
  nic->nic_par1 = *addr++; delay();
  nic->nic_par2 = *addr++; delay();
  nic->nic_par3 = *addr++; delay();
  nic->nic_par4 = *addr++; delay();
  nic->nic_par5 = *addr;   delay();

  /* restore old command
  */
  nic->nic_cr = oldcmd; delay();
}


/*
*========================================================================
*                           nic_init(flags, device)
*========================================================================
*
* Set up the network card for online operation. We also get the hardware
* station address from the nic's ROM.
*
*/
stcode short nic_init(UWORD flags,
                      struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct Device *TimerBase = base->dd_timerbase;

  /* ignore ints while setting up
  */
  Disable();

  /* already initialised ?
  */
  if (!(base->dd_flags & DDF_NICUP))
  {
    nic_t *nic = ATTRMEM_BASE;

    /* Word Xfer, FIFO, Burst
    */
    base->dd_dcr = DSDC_WTS | DSDC_FT1 | DSDC_BMS;

    /* accept broadcast & multicast
    */
    base->dd_rcr = DSRC_AB | DSRC_AM;

    /* if promiscuous mode?
    */
    if (base->dd_flags & DDF_PROM)
    {
      /* accept all packets
      */
      base->dd_rcr |= (DSRC_SEP | DSRC_PRO | DSRC_AR);
    }

    /* accept useful interrupts
    */
    base->dd_imr = INTMASK;

    /* reset the controller
    */
    nic_reset(base); delay();

    /* get command - correct response ?
    */
    if (nic->nic_cr == (DSCM_NODMA | DSCM_PG0 | DSCM_STOP))
    {
      short r;

      /* set data configuration register
      */
      nic->nic_dcr = base->dd_dcr; delay();

      /* clear remote byte count low & hi
      */
      nic->nic_rbcr0 = 0; delay();
      nic->nic_rbcr1 = 0; delay();

      /* set rx to monitor mode
      */
      nic->nic_rcr = DSRC_MON; delay();

      /* set tx to loopback mode 1
      */
      nic->nic_tcr = DSTC_LB0; delay();

      /* set boundary page
      */
      nic->nic_bnry = (RBUFEND / 256) - 1; delay();

      /* set start & end of rx ring buffer
      */
      nic->nic_pstart = RBUF / 256; delay();
      nic->nic_pstop = RBUFEND / 256; delay();

      /* clear all interrupts
      */
      nic->nic_isr = 0xff; delay();

      /* no interrupts allowed
      */
      nic->nic_imr = 0; delay();

      /* read status registers
      */
      r = nic->nic_rsr; delay();
      r = nic->nic_ncr; delay();
      r = nic->nic_cntr0; delay();
      r = nic->nic_cntr1; delay();
      r = nic->nic_cntr2; delay();


      /* byte count low & high = (words)
      */
      nic->nic_rbcr0 = ETHER_ADDR_SIZE * 2; delay();
      nic->nic_rbcr1 = 0; delay();

      /* remote start addr low & high = 0 (ROM)
      */
      nic->nic_rsar0 = 0; delay();
      nic->nic_rsar1 = 0; delay();

      /* start remote read to get
      */
      nic->nic_cr = DSCM_PG0 | DSCM_RREAD; delay();

      /* get ROM station address
      */
      for (r = 0; r < ETHER_ADDR_SIZE; r++)
      {
        base->dd_romstationaddress[r] = *((volatile UBYTE *) &nic->nic_data); delay();
      }

      KPRINTF(1, ("nic_init: romstationaddress: 0x%08lx%04lx\n",
                  *((ULONG *) base->dd_romstationaddress),
                  *((UWORD *) (base->dd_romstationaddress + 4))));

      /* wait for remote DMA complete
      */
      for (r = 30000; (!(nic->nic_isr & DSIS_RDC)) && (r > 0); r--)
      {
        delay();
      }

      if (r != 0)
      {
        UBYTE *sa = base->dd_romstationaddress;

        /* clear remote DMA complete int
        */
        nic->nic_isr = DSIS_RDC; delay();

        /* good station address ?
        */
        if ((base->dd_romstationaddress[0] & 0x80) ||
            (*((ULONG *) &base->dd_romstationaddress[2]) == 0x00000000) ||
            (*((ULONG *) &base->dd_romstationaddress[2]) == 0xffffffff))
        {
          /* bad station address, fallback to default
          */
          KPRINTF(10, ("nic_init: bad station address, fell back to internal default.\n"));

          sa = (UBYTE *) default_address;
        }

        /* copy address to device data
        */
        for (r = 0; r < ETHER_ADDR_SIZE;
             base->dd_stationaddress[r++] = *sa++);

        KPRINTF(1, ("nic_init: stationaddress: 0x%08lx%04lx\n",
                    *((ULONG *) base->dd_stationaddress),
                    *((UWORD *) (base->dd_stationaddress + 4))));


        /* set station address
        */
        nic_setstationaddress(base);

        /* select bank 1
        */
        nic->nic_cr = DSCM_NODMA | DSCM_PG1 | DSCM_STOP; delay();

        /* set current page for rx
        */
        nic->nic_curr = RBUF / 256; delay();

        /* start controller
        */
        nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_START; delay();

        /* command accepted ok?
        */
        if (nic->nic_cr == (DSCM_NODMA | DSCM_PG0 | DSCM_START))
        {
          /* load multicast registers
          */
          nic_update_multicasts(base);

          /* normal rx mode
          */
          nic->nic_rcr = base->dd_rcr; delay();

          /* loopback mode off
          */
          nic->nic_tcr = 0; delay();

          /* init tx start page
          */
          nic->nic_tpsr = TBUF / 256; delay();

          /* clear all interrupts
          */
          nic->nic_isr = 0xff; delay();

          /* enable nic interrupts
          */
          nic->nic_imr = base->dd_imr; delay();


          /* nic is initialised
          */
          base->dd_flags |= DDF_NICUP;

        }
        else
        {
          /* Command not accepted
          */
          nic_reset(base);
          DoEvent(S2EVENT_ERROR | S2EVENT_HARDWARE, base);
          Enable();
          KPRINTF(10, ("nic_init: failed to start controller, card not compatible?\n"));
          if (!(flags & 1))
          {
            EasyReq("failed to start controller,\n"
                    "card not compatible?", NULL, base);
          }
          return -1;
        }
      }
      else
      {
        /* DMA timeout
        */
        nic_reset(base);
        DoEvent(S2EVENT_ERROR | S2EVENT_HARDWARE, base);
        Enable();
        KPRINTF(10, ("nic_init: DMA timeout getting hardware station address\n"));
        if (!(flags & 1))
        {
          EasyReq("DMA timeout getting\n"
                  "hardware station address", NULL, base);
        }
        return -1;
      }
    }
    else
    {
      /* bad command response
      */
      DoEvent(S2EVENT_ERROR | S2EVENT_HARDWARE, base);
      Enable();
      KPRINTF(10, ("nic_init: failed reset, card not compatible?\n"));
      if (!(flags & 1))
      {
        EasyReq("card reset failed,\n"
                "card not compatible?", NULL, base);
        return -1;
      }
    }
  }
  Enable();

  /* record start time_of_day
  */
  GetSysTime(&base->dd_devicestats.LastStart);

  KPRINTF(1, ("nic_init: Ok\n"));

  return 0;
}

/*
*=========================================================
*                      nic_reset()
*=========================================================
*/
stcode void nic_reset(struct MyDevice *base)
{
  nic_t *nic = ATTRMEM_BASE;
  UBYTE rst;
  short r;

  /* start reset pulse
  */
  rst = nic->nic_rst; delay();

  /* end reset pulse
  */
  nic->nic_rst = rst; delay();

  /* stop controller
  */
  nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_STOP; delay();

  /* wait ~1.5mS
  */
  for (r = 0; r < 1500; r++)
  {
    /* wait 1uS
    */
    (void) (rst = ciaa.ciapra);
  }

  /* clear all nic ints
  */
  nic->nic_isr = 0xff; delay();
}




/*
*==============================================================
*      Put Multicast address bitfield into NIC registers
*==============================================================
*/

stcode void nic_put_multi(struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  nic_t *nic = ATTRMEM_BASE;
  UBYTE oldcmd;

  Disable();

  /* save old command
  */
  delay();
  oldcmd = nic->nic_cr; delay();

  /* select page 1
  */
  nic->nic_cr = DSCM_NODMA | DSCM_PG1 | DSCM_STOP; delay();

  /* put Multicast bits
  */
  nic->nic_mar0 = base->dd_mcastaddr[0]; delay();
  nic->nic_mar1 = base->dd_mcastaddr[1]; delay();
  nic->nic_mar2 = base->dd_mcastaddr[2]; delay();
  nic->nic_mar3 = base->dd_mcastaddr[3]; delay();
  nic->nic_mar4 = base->dd_mcastaddr[4]; delay();
  nic->nic_mar5 = base->dd_mcastaddr[5]; delay();
  nic->nic_mar6 = base->dd_mcastaddr[6]; delay();
  nic->nic_mar7 = base->dd_mcastaddr[7]; delay();

  /* restore old command
  */
  nic->nic_cr = oldcmd; delay();

  Enable();
}




/*
*==========================================================
*             DoEvent(events, device)
*==========================================================
*
* called when an 'important' event occurs
*/
stcode void DoEvent(ULONG events,
                    struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct IOSana2Req *worknode, *nextnode;

  KPRINTF(1, ("DoEvent events: 0x%08lx base: 0x%08lx\n", events, base));
  Disable();

  /* process pending events
  */
  worknode = (struct IOSana2Req *) base->dd_eventlist.mlh_Head;
  while ((nextnode = (struct IOSana2Req *) (((struct MinNode *) worknode)->mln_Succ)))
  {
    if (worknode->ios2_WireError & events)
    {
      Remove(&worknode->ios2_Req.io_Message.mn_Node);
      worknode->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;

      KPRINTF(1, ("DoEvent: returned eventreq 0x%08lx\n", worknode));

      ReplyMsg(&worknode->ios2_Req.io_Message);
    }
    worknode = nextnode;
  }

  Enable();
  /*KPRINTF(1, ("DoEvent: done\n"));
  */
}


/*
*======================================================================
*                  AbortReq(minlist, ioreq, base)
*======================================================================
*
* locate an IO request in a linked list and abort it if found.
*/
stcode int AbortReq(struct MinList *list,
                    struct IOSana2Req *ioreq,
                    struct MyDevice *base)
{
  struct MinNode *node;

  for (node = list->mlh_Head; node->mln_Succ; node = node->mln_Succ)
  {
    if (node == (struct MinNode *) ioreq)
    {
      struct ExecBase *SysBase = base->dd_SysBase;

      /*KPRINTF(1, ("AbortReq: aborted 0x%08lx\n", ioreq));
      */

      Remove(&ioreq->ios2_Req.io_Message.mn_Node);
      ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
      ioreq->ios2_Req.io_Error = IOERR_ABORTED;
      ReplyMsg(&ioreq->ios2_Req.io_Message);
      return S2ERR_NO_ERROR;
    }
  }
  return IOERR_NOCMD;
}




/*
*===========================================================
*                      TermIO(ioreq, base)
*===========================================================
*
* return completed ioreq to sender.
*/
stcode void TermIO(struct IOSana2Req *ioreq,
                   struct MyDevice *base)
{
  ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE;

  /* If not quick I/O, reply the message
  */
  if (!(ioreq->ios2_Req.io_Flags & IOF_QUICK))
  {
    struct ExecBase *SysBase = base->dd_SysBase;

    ReplyMsg(&ioreq->ios2_Req.io_Message);
  }
}



stcode void NATDECLFUNC_2(devAbortIO,
                          a1, struct IOSana2Req *, ioreq,
                          a6, struct MyDevice *, base)
{
  DECLARG_2(a1, struct IOSana2Req *, ioreq,
            a6, struct MyDevice *, base)

  struct ExecBase *SysBase = base->dd_SysBase;
  struct bufman *worknode, *nextnode;

  KPRINTF(1, ("devAbortIO ioreq: 0x%08lx\n", ioreq));

  Disable();

  /* is it pending?
  */
  if (ioreq->ios2_Req.io_Message.mn_Node.ln_Type == NT_MESSAGE)
  {
    switch (ioreq->ios2_Req.io_Command)
    {
      case CMD_READ:

        /* Check all nodes in dd_bufmanlist until we succeed to abort.
        */
        worknode = (struct bufman *) base->dd_bufmanlist.mlh_Head;
        while ((nextnode = (struct bufman *) (((struct MinNode *) worknode)->mln_Succ)))
        {
          if (AbortReq(&worknode->rxqueue, ioreq, base) == S2ERR_NO_ERROR)
          {
            /* success, break out
            */
            break;
          }
          worknode = nextnode;
        }
        break;

      case S2_READORPHAN:
        AbortReq(&base->dd_orphanlist, ioreq, base);
        break;

      case CMD_WRITE:
      case S2_MULTICAST:
      case S2_BROADCAST:
        AbortReq(&base->dd_writelist, ioreq, base);
        break;

      case S2_ONEVENT:
        AbortReq(&base->dd_eventlist, ioreq, base);
        break;

      default:
        KPRINTF(1, ("devAbortIO: not READ, READORPHAN, WRITE, MULTICAST, BROADCAST or ONEVENT\n"));
        break;
    }
  }
  Enable();
}


/*
*======================================================================
*                  abortlist(list, error, device)
*======================================================================
*
* Aborts all pending ioreqs in given list with io_Error & ios2_WireError
* set.
*/
stcode void AbortList(struct MinList *list,
                      short error,
                      struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct IOSana2Req *ioreq;

  while ((ioreq = (struct IOSana2Req *) RemHead((struct List *) list)))
  {
    /*ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;*/
    ioreq->ios2_Req.io_Error = error & 0xff;
    ioreq->ios2_WireError = error >> 8;
    ReplyMsg(&ioreq->ios2_Req.io_Message);
  }
}


/*
*======================================================================
*                  abortrw(error, device)
*======================================================================
*
* Aborts all pending reads and writes with io_Error & ios2_WireError
* set.
*/

stcode void AbortRW(short error,
                    struct MyDevice *base)
{
  struct bufman *worknode, *nextnode;

  worknode = (struct bufman *) base->dd_bufmanlist.mlh_Head;
  while ((nextnode = (struct bufman *) (((struct MinNode *) worknode)->mln_Succ)))
  {
    AbortList(&worknode->rxqueue, error, base);

    worknode = nextnode;
  }

  AbortList(&base->dd_orphanlist, error, base);
  AbortList(&base->dd_writelist, error, base);
}


stcode short cmdRead(struct IOSana2Req *ioreq,
                     struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct bufman *bufman;

  KPRINTF(1, ("CMD_READ ioreq: 0x%08lx type: %lu\n", ioreq, ioreq->ios2_PacketType));

  /* configured?
  */
  if (!(base->dd_flags & DDF_CONFIGURED))
  {
    return deverror(S2ERR_BAD_STATE, S2WERR_NOT_CONFIGURED);
  }

  /* online?
  */
  if (!(base->dd_flags & DDF_ONLINE))
  {
    return deverror(S2ERR_OUTOFSERVICE, S2WERR_UNIT_OFFLINE);
  }

  /* valid bufman? got copytobuf routine?
  */
  if (((bufman = ioreq->ios2_BufferManagement) == NULL) ||
      (bufman->copytobuf == NULL))
  {
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_NULL_POINTER);
  }

  /* must be queued
  */
  ioreq->ios2_Req.io_Flags &= ~IOF_QUICK;

  /* add to this ioreq's bufman rxqueue
  */
  Disable();
  AddTail((struct List *) &bufman->rxqueue, (struct Node *) ioreq);
  Enable();

  /* don't reply the ioreq, it's pending
  */
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdWrite(struct IOSana2Req *ioreq,
                      struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct bufman *bufman;
  int size;

  KPRINTF(1, ("CMD_WRITE ioreq: 0x%08lx type: %lu len: %lu\n",
              ioreq, ioreq->ios2_PacketType, ioreq->ios2_DataLength));

  /*
  KPRINTF(1, ("write 0x%08lx%04lx -> 0x%08lx%04lx\n",
              *((ULONG *) (ioreq->ios2_SrcAddr)),
              *((UWORD *) (ioreq->ios2_SrcAddr + 4)),
              *((ULONG *) (ioreq->ios2_DstAddr)),
              *((UWORD *) (ioreq->ios2_DstAddr + 4))));
  */

  /* configured?
  */
  if (!(base->dd_flags & DDF_CONFIGURED))
  {
    return deverror(S2ERR_BAD_STATE, S2WERR_NOT_CONFIGURED);
  }

  /* online?
  */
  if (!(base->dd_flags & DDF_ONLINE))
  {
    return deverror(S2ERR_OUTOFSERVICE, S2WERR_UNIT_OFFLINE);
  }

  /* valid bufman? got copyfrombuf routine?
  */
  if (((bufman = ioreq->ios2_BufferManagement) == NULL) ||
      (bufman->copyfrombuf == NULL))
  {
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_NULL_POINTER);
  }

  /* check packet size
  */
  size = (ioreq->ios2_Req.io_Flags & SANA2IOF_RAW) ? RAWPKT_SIZE : ETHERPKT_SIZE;
  if (ioreq->ios2_DataLength > size)
  {
    /* return any TX events
    */
    DoEvent(S2EVENT_ERROR | S2EVENT_TX, base);
    return deverror(S2ERR_MTU_EXCEEDED, S2WERR_GENERIC_ERROR);
  }

  /* must be queued
  */
  ioreq->ios2_Req.io_Flags &= ~IOF_QUICK;

  Disable();
  AddTail((struct List *) &base->dd_writelist, (struct Node *) ioreq);
  Enable();

  Cause(&base->dd_txint);

  /* don't reply the ioreq, it's pending
  */
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdFlush(struct IOSana2Req *ioreq,
                      struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;

  KPRINTF(1, ("CMD_FLUSH ioreq: 0x%08lx\n", ioreq));

  Disable();
  AbortRW(deverror(IOERR_ABORTED, S2WERR_GENERIC_ERROR), base);
  AbortList(&base->dd_eventlist, deverror(IOERR_ABORTED, S2WERR_GENERIC_ERROR), base);
  Enable();

  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}


stcode short cmdDeviceQuery(struct IOSana2Req *ioreq,
                            struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct Sana2DeviceQuery *query = (struct Sana2DeviceQuery *) ioreq->ios2_StatData;

  KPRINTF(1, ("S2_DEVICEQUERY ioreq: 0x%08lx\n", ioreq));

  /* NULL ptr?
  */
  if (query == NULL)
  {
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_NULL_POINTER);
  }

  /* enough space to store the info ?
  */
  if (query->SizeAvailable < sizeof(struct Sana2DeviceQuery))
  {
    KPRINTF(1, ("S2_DEVICEQUERY: too small buffer supplied! (%lu / %lu)\n",
                 query->SizeAvailable, sizeof(struct Sana2DeviceQuery)));

    query->SizeSupplied = 0;
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_BAD_STATDATA);
  }

  CopyMem(&((struct Sana2DeviceQuery *) DeviceQueryBlock)->SizeSupplied,
          &query->SizeSupplied, sizeof(struct Sana2DeviceQuery) - sizeof(ULONG));

  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdGetStationAddress(struct IOSana2Req *ioreq,
                                  struct MyDevice *base)
{
  int r;

  KPRINTF(1, ("S2_GETSTATIONADDRESS ioreq: 0x%08lx\n", ioreq));

  /* source address = current station address
     dest address = default station address
  */
  for (r = 0; r < ETHER_ADDR_SIZE; r++)
  {
    ioreq->ios2_SrcAddr[r] = base->dd_stationaddress[r];
    ioreq->ios2_DstAddr[r] = base->dd_romstationaddress[r];
  }
  /* clear the rest
  */
  for (; r < SANA2_MAX_ADDR_BYTES; r++)
  {
    ioreq->ios2_SrcAddr[r] = 0;
    ioreq->ios2_DstAddr[r] = 0;
  }

  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdConfigInterface(struct IOSana2Req *ioreq,
                                struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  int r;

  KPRINTF(1, ("S2_CONFIGINTERFACE ioreq: 0x%08lx\n", ioreq));

  /* NOTE: a default station address has already been set by nic_init
  */

  /* this stuff must be atomic
  */
  Disable();
  if (base->dd_flags & DDF_CONFIGURED)
  {
    Enable();
    /* error, already configured
    */
    return deverror(S2ERR_BAD_STATE, S2WERR_IS_CONFIGURED);
  }

  /* check for valid address
  */
  if (*((LONG *) ioreq->ios2_SrcAddr) <= 0)
  {
    Enable();
    return deverror(S2ERR_BAD_ADDRESS, S2WERR_SRC_ADDRESS);
  }

  /* copy the address
  */
  for (r = 0; r < ETHER_ADDR_SIZE; r++)
  {
    base->dd_stationaddress[r] = ioreq->ios2_SrcAddr[r];
  }

  /* set the station address
  */
  nic_setstationaddress(base);

  /* now configured
  */
  base->dd_flags |= DDF_CONFIGURED;

  Enable();

  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}


/*
*===========================================================================
*                          Calculate 32 bit crc
*===========================================================================
*
* - for each bit: if (crcbit=1 XOR databit=1) then crc=(crc XOR polynomial).
* - polynomial is correct when data bits range 0-x, crc bits range 31-0.
* - crc wraps at 32 bits.
*
*  crc = calc_crc(addr)
*/
#define CRC_SEED  -1
#define CRC_MASK  0x04c11db7U

stcode inline unsigned long int ether_crc(int length,
                                          unsigned char *data)
{
  long int crc = CRC_SEED;

  while (--length >= 0)
  {
    unsigned char c = *data++;
    unsigned char bit;

    for (bit = 0; bit < 8; bit++, c >>= 1)
    {
      crc = (crc << 1) ^ (((crc < 0) ^ (c & 1)) ? CRC_MASK : 0);
    }
  }
  return (unsigned long int) crc;
}


/*
*================================================================
*                    Update Multicasts
*================================================================
*
*  calculate multicast hash table and send it to nic
*
*
*  as of 1.2beta4 multicasts can be a address range, also single
*  addresses are defined with lower = upper.
*
*  multicasts list must be locked!
*
*==============================================================
*                 hash multicast address
*==============================================================
* The nic does not compare multicast addresses directly, but
* instead uses a bitfield of 64 bits - each bit corresponds
* to a 6 bit hash code for the multicast address. If the bit
* is set then any multicast address that matches the hash code
* will be accepted.
*
* The hash code is generated by taking the upper 6 bits of the
* 32 bit crc of the multicast address.
*
*/

stcode void nic_update_multicasts(struct MyDevice *base)
{
  struct multicastaddressrange *mcastrnode;
  int r;
  ULONG addr[2];

  KPRINTF(1, ("nic_update_multicasts\n"));

  /* clear all multicast bits
  */
  for (r = 0; r < 8; r++)
  {
    base->dd_mcastaddr[r] = 0;
  }

  initlistwalk(struct multicastaddressrange *, mcastrnode, &base->dd_multicasts);
  while (listwalk(struct multicastaddressrange *, mcastrnode))
  {
    addr[0] = *((UWORD *) (mcastrnode->loweraddr + 0));
    addr[1] = *((ULONG *) (mcastrnode->loweraddr + 2));

    for (;;)
    {
      /* calc the hash
      */
      UBYTE hash = ether_crc(ETHER_ADDR_SIZE, ((UBYTE *) addr) + 2) >> 26;

      /* set the hash bit
      */
      base->dd_mcastaddr[hash >> 3] |= (1 << (hash & 0x7));

      /* are we done with this range?
      */
      if (mcmp(((char *) addr) + 2, mcastrnode->upperaddr))
      {
        break;
      }

      /* bump to next address
      */
      if (++addr[1] == 0)
      {
        addr[0]++;
      }
    }
  }

  KPRINTF(2, ("nic_update_multicasts: mask: 0x%08lx %08lx\n",
              ((ULONG *) base->dd_mcastaddr)[0],
              ((ULONG *) base->dd_mcastaddr)[1]));

  /* load multicast registers
  */
  nic_put_multi(base);
}

stcode short addmcastrange(struct IOSana2Req *ioreq,
                           UBYTE *lower,
                           UBYTE *upper,
                           struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct multicastaddressrange *mcastrnode;
  int r;

  /* valid multicast addresses ?
  */
  if (((lower[0] & 1) == 0) || ((upper[0] & 1) == 0))
  {
    return deverror(S2ERR_BAD_ADDRESS, S2WERR_BAD_MULTICAST);
  }

  /* lock access to list
  */
  ObtainSemaphore(&base->dd_multicastssema);

  /* find matching node
  */
  initlistwalk(struct multicastaddressrange *, mcastrnode, &base->dd_multicasts);
  while (listwalk(struct multicastaddressrange *, mcastrnode))
  {
    if (mcmp(lower, mcastrnode->loweraddr) &&
        mcmp(upper, mcastrnode->upperaddr))
    {
      goto release;
    }
  }

  /* add new node
  */
  if ((mcastrnode = alloc(sizeof(struct multicastaddressrange), base)) == NULL)
  {
    ReleaseSemaphore(&base->dd_multicastssema);
    return deverror(S2ERR_NO_RESOURCES, S2WERR_GENERIC_ERROR);
  }

  for (r = 0; r < ETHER_ADDR_SIZE; r++)
  {
    mcastrnode->loweraddr[r] = *lower++;
    mcastrnode->upperaddr[r] = *upper++;
  }

  AddTail((struct List *)&base->dd_multicasts, (struct Node *) mcastrnode);

  /* update multicasts in nic
  */
  nic_update_multicasts(base);

  /* NOTE: mcastrnode->usecount == 0
  */

  release:
  mcastrnode->usecount++;

  /* release lock on list
  */
  ReleaseSemaphore(&base->dd_multicastssema);

  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short delmcastrange(struct IOSana2Req *ioreq,
                           UBYTE *lower,
                           UBYTE *upper,
                           struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct multicastaddressrange *mcastrnode;

  /* lock access to list
  */
  ObtainSemaphore(&base->dd_multicastssema);

  /* find matching node
  */
  initlistwalk(struct multicastaddressrange *, mcastrnode, &base->dd_multicasts);
  while (listwalk(struct multicastaddressrange *, mcastrnode))
  {
    if (mcmp(lower, mcastrnode->loweraddr) &&
        mcmp(upper, mcastrnode->upperaddr))
    {
      if (--mcastrnode->usecount == 0)
      {
        Remove((struct Node *) mcastrnode);
        free(mcastrnode, sizeof(struct multicastaddressrange), base);

        /* update multicasts in nic
        */
        nic_update_multicasts(base);
      }

      /* release lock on list
      */
      ReleaseSemaphore(&base->dd_multicastssema);

      /* exit ok
      */
      TermIO(ioreq, base);
      return deverror(S2ERR_NO_ERROR, 0);
    }
  }

  /* release lock on list
  */
  ReleaseSemaphore(&base->dd_multicastssema);

  /* return with failure
  */
  return deverror(S2ERR_BAD_STATE, S2WERR_BAD_MULTICAST);
}


stcode short cmdAddMulticastAddress(struct IOSana2Req *ioreq,
                                    struct MyDevice *base)
{
  KPRINTF(1, ("S2_ADDMULTICASTADDRESS 0x%08lx%04lx\n",
              ((ULONG *) ioreq->ios2_SrcAddr)[0], ((UWORD *) ioreq->ios2_SrcAddr)[2]));

  return addmcastrange(ioreq, ioreq->ios2_SrcAddr, ioreq->ios2_SrcAddr, base);
}

stcode short cmdDelMulticastAddress(struct IOSana2Req *ioreq,
                                    struct MyDevice *base)
{
  KPRINTF(1, ("S2_DELMULTICASTADDRESS 0x%08lx%04lx\n",
              ((ULONG *) ioreq->ios2_SrcAddr)[0], ((UWORD *) ioreq->ios2_SrcAddr)[2]));

  return delmcastrange(ioreq, ioreq->ios2_SrcAddr, ioreq->ios2_SrcAddr, base);
}

stcode short cmdMulticast(struct IOSana2Req *ioreq,
                          struct MyDevice *base)
{

  KPRINTF(1, ("S2_MULTICAST ioreq: 0x%08lx to: 0x%08lx%04lx\n",
              ioreq,
              ((ULONG *) ioreq->ios2_DstAddr)[0], ((UWORD *) ioreq->ios2_DstAddr)[2]));

  /* is dest a multicast address ?
  */
  if (ioreq->ios2_DstAddr[0] & 1)
  {
    return cmdWrite(ioreq, base);
  }

  /* return any TX events
  */
  DoEvent(S2EVENT_ERROR | S2EVENT_TX, base);

  /* wasn't a multicast addr
  */
  return deverror(S2ERR_BAD_ADDRESS, S2WERR_BAD_MULTICAST);
}

stcode short cmdBroadcast(struct IOSana2Req *ioreq,
                          struct MyDevice *base)
{
  int r;

  KPRINTF(1, ("S2_BROADCAST ioreq: 0x%08lx\n", ioreq));

  /* dest address = broadcast
  */
  for (r = 0; r < ETHER_ADDR_SIZE; r++)
  {
    ioreq->ios2_DstAddr[r] = 0xff;
  }

  return cmdWrite(ioreq, base);
}

stcode short cmdTrackType(struct IOSana2Req *ioreq,
                          struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct packettypestats *tracknode, *new;

  KPRINTF(1, ("S2_TRACKTYPE ioreq: 0x%08lx type: %lu\n",
              ioreq, ioreq->ios2_PacketType));

  /* alloc tracking node
  */
  if ((new = alloc(sizeof(struct packettypestats), base)) == NULL)
  {
    return deverror(S2ERR_NO_RESOURCES, S2WERR_NOT_TRACKED);
  }

  Disable();

  /* find matching node
  */
  initlistwalk(struct packettypestats *, tracknode, &base->dd_tracklist);
  while (listwalk(struct packettypestats *, tracknode))
  {
    /* our packet type?
    */
    if (tracknode->packettype == ioreq->ios2_PacketType)
    {
      Enable();
      free(new, sizeof(struct packettypestats), base);
      return deverror(S2ERR_BAD_STATE, S2WERR_ALREADY_TRACKED);
    }
  }

  /* set up tracking node
  */
  new->packettype = ioreq->ios2_PacketType;

  /* add the new node to tracklist
  */
  AddTail((struct List *) &base->dd_tracklist, (struct Node *) new);
  refresh_typestats(base);
  Enable();

  /* return success
  */
  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdUntrackType(struct IOSana2Req *ioreq,
                            struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct packettypestats *tracknode;

  KPRINTF(1, ("S2_UNTRACKTYPE ioreq: 0x%08lx type: %lu\n",
              ioreq, ioreq->ios2_PacketType));

  Disable();

  /* find matching node
  */
  initlistwalk(struct packettypestats *, tracknode, &base->dd_tracklist);
  while (listwalk(struct packettypestats *, tracknode))
  {
    /* our packet type?
    */
    if (tracknode->packettype == ioreq->ios2_PacketType)
    {
      Remove((struct Node *) tracknode);
      refresh_typestats(base);
      Enable();

      free(tracknode, sizeof(struct packettypestats), base);

      /* return success
      */
      TermIO(ioreq, base);
      return deverror(S2ERR_NO_ERROR, 0);
    }
  }

  Enable();

  /* return failure
  */
  return deverror(S2ERR_BAD_STATE, S2WERR_NOT_TRACKED);
}


stcode void refresh_typestats(struct MyDevice *base)
{
  struct packettypestats *tracknode;

  base->dd_typestats2048 = NULL;
  base->dd_typestats2054 = NULL;

  /* find matching node
  */
  initlistwalk(struct packettypestats *, tracknode, &base->dd_tracklist);
  while (listwalk(struct packettypestats *, tracknode))
  {
    /* cache most used packet types
    */
    switch (tracknode->packettype)
    {
      case 2048:
        base->dd_typestats2048 = &tracknode->stats;
        break;

      case 2054:
        base->dd_typestats2054 = &tracknode->stats;
        break;
    }
  }
}

stcode struct Sana2PacketTypeStats *findpackettypestats(UWORD packettype,
                                                        struct MyDevice *base)
{
  struct packettypestats *tracknode;

  switch (packettype)
  {
    case 2048:
      return base->dd_typestats2048;
      break;

    case 2054:
      return base->dd_typestats2054;
      break;

    default:
      /* find matching node - slowly
      */
      initlistwalk(struct packettypestats *, tracknode, &base->dd_tracklist);
      while (listwalk(struct packettypestats *, tracknode))
      {
        /* our packet type?
        */
        if (tracknode->packettype == packettype)
        {
          return &tracknode->stats;
        }
      }
      break;
  }
  return NULL;
}


stcode short cmdGetTypeStats(struct IOSana2Req *ioreq,
                             struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct Sana2PacketTypeStats *tostats = (struct Sana2PacketTypeStats *) ioreq->ios2_StatData;
  struct Sana2PacketTypeStats *fromstats;

  KPRINTF(1, ("S2_GETTYPESTATS ioreq: 0x%08lx type: %lu\n",
              ioreq, ioreq->ios2_PacketType));

  /* NULL ptr?
  */
  if (tostats == NULL)
  {
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_NULL_POINTER);
  }

  Disable();

  if ((fromstats = findpackettypestats(ioreq->ios2_PacketType, base)))
  {
    /* copy the stats
    */
    CopyMem(fromstats, tostats,
            sizeof(struct Sana2PacketTypeStats));
    Enable();

    /* return success
    */
    TermIO(ioreq, base);
    return deverror(S2ERR_NO_ERROR, 0);
  }

  Enable();

  /* return error
  */
  return deverror(S2ERR_BAD_STATE, S2WERR_NOT_TRACKED);
}

stcode short cmdGetSpecialStats(struct IOSana2Req *ioreq,
                                struct MyDevice *base)
{
  struct Sana2SpecialStatHeader *stats = (struct Sana2SpecialStatHeader *) ioreq->ios2_StatData;
  struct Sana2SpecialStatRecord *record;
  ULONG maxcount;

  KPRINTF(1, ("S2_GETSPECIALSTATS ioreq: 0x%08lx\n", ioreq));

  /* NULL ptr?
  */
  if (stats == NULL)
  {
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_NULL_POINTER);
  }

  stats->RecordCountSupplied = 0;
  maxcount = stats->RecordCountMax;
  record = (struct Sana2SpecialStatRecord *) (stats + 1);

  if (maxcount--)
  {
    stats->RecordCountSupplied++;

    record->Type = S2SS_ETHERNET_BADMULTICAST;
    record->Count = base->dd_badmulticasts;
    record->String = "bad multicasts";

    record++;

    if (maxcount--)
    {
      stats->RecordCountSupplied++;

      record->Type = S2SS_ETHERNET_RETRIES;
      record->Count = base->dd_retries;
      record->String = "retries";

      record++;
    }
  }

  /* return success
  */
  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdGetGlobalStats(struct IOSana2Req *ioreq,
                               struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct Sana2DeviceStats *stats = (struct Sana2DeviceStats *) ioreq->ios2_StatData;

  KPRINTF(1, ("S2_GETGLOBALSTATS ioreq: 0x%08lx\n", ioreq));

  /* NULL ptr?
  */
  if (stats == NULL)
  {
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_NULL_POINTER);
  }

  CopyMem(&base->dd_devicestats, stats, sizeof(struct Sana2DeviceStats));

  /* return success
  */
  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

#define KNOWN_EVENTS (S2EVENT_ERROR |   \
                      S2EVENT_TX |      \
                      S2EVENT_RX |      \
                      S2EVENT_ONLINE |  \
                      S2EVENT_OFFLINE | \
                      S2EVENT_BUFF |    \
                      S2EVENT_HARDWARE)

stcode short cmdOnEvent(struct IOSana2Req *ioreq,
                        struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;

  KPRINTF(1, ("S2_ONEVENT ioreq: 0x%08lx mask: 0x%08lx\n", ioreq,
              ioreq->ios2_WireError));

  /* do we know the requested events?
  */
  if (ioreq->ios2_WireError & ~(KNOWN_EVENTS))
  {
    return deverror(S2ERR_NOT_SUPPORTED, S2WERR_BAD_EVENT);
  }

  /* if online return S2EVENT_ONLINE immeditially
     if offline return S2EVENT_OFFLINE immeditially
  */
  if (base->dd_flags & DDF_ONLINE)
  {
    if (ioreq->ios2_WireError & S2EVENT_ONLINE)
    {
      ioreq->ios2_WireError = S2EVENT_ONLINE;

      /* return success
      */
      TermIO(ioreq, base);
      return deverror(S2ERR_NO_ERROR, 0);
    }
  }
  else
  {
    if (ioreq->ios2_WireError & S2EVENT_OFFLINE)
    {
      ioreq->ios2_WireError = S2EVENT_OFFLINE;

      /* return success
      */
      TermIO(ioreq, base);
      return deverror(S2ERR_NO_ERROR, 0);
    }
  }

  /* must be queued
  */
  ioreq->ios2_Req.io_Flags &= ~IOF_QUICK;

  /* queue the ioreq
  */
  Disable();
  AddTail((struct List *) &base->dd_eventlist, (struct Node *) ioreq);
  Enable();

  /* don't reply the ioreq, it's pending
  */
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdReadOrphan(struct IOSana2Req *ioreq,
                           struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct bufman *bufman;

  KPRINTF(1, ("S2_READORPHAN ioreq: 0x%08lx type: %lu\n", ioreq,
              ioreq->ios2_PacketType));

  /* configured?
  */
  if (!(base->dd_flags & DDF_CONFIGURED))
  {
    return deverror(S2ERR_BAD_STATE, S2WERR_NOT_CONFIGURED);
  }

  /* online?
  */
  if (!(base->dd_flags & DDF_ONLINE))
  {
    return deverror(S2ERR_OUTOFSERVICE, S2WERR_UNIT_OFFLINE);
  }

  /* valid bufman? got copytobuf routine?
  */
  if (((bufman = ioreq->ios2_BufferManagement) == NULL) ||
      (bufman->copytobuf == NULL))
  {
    return deverror(S2ERR_BAD_ARGUMENT, S2WERR_NULL_POINTER);
  }

  /* must be queued
  */
  ioreq->ios2_Req.io_Flags &= ~IOF_QUICK;

  /* queue the ioreq
  */
  Disable();
  AddTail((struct List *) &base->dd_orphanlist, (struct Node *) ioreq);
  Enable();

  /* don't reply the ioreq, it's pending
  */
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdOnline(struct IOSana2Req *ioreq,
                       struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;

  KPRINTF(1, ("S2_ONLINE ioreq: 0x%08lx\n", ioreq));

  Disable();

  /* already online?
  */
  if (!(base->dd_flags & DDF_ONLINE))
  {
    nic_t *nic = ATTRMEM_BASE;

    /* configured?
    */
    if (!(base->dd_flags & DDF_CONFIGURED))
    {
      Enable();
      return deverror(S2ERR_BAD_STATE, S2WERR_NOT_CONFIGURED);
    }

    /* nic initialized?
    */
    if (!(base->dd_flags & DDF_NICUP))
    {
      Enable();
      return deverror(S2ERR_OUTOFSERVICE, S2WERR_UNIT_OFFLINE);
    }

    /* mark no longer offline
    */
    base->dd_flags &= ~DDF_OFFLINE;

    /* mark being online
    */
    base->dd_flags |= DDF_ONLINE;

    /* set receiver to normal mode
    */
    nic->nic_rcr = base->dd_rcr; delay();

    /* return any ONLINE events
    */
    DoEvent(S2EVENT_ONLINE, base);
  }

  Enable();

  /* return success
  */
  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

stcode short cmdOffline(struct IOSana2Req *ioreq,
                        struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;

  KPRINTF(1, ("S2_OFFLINE ioreq: 0x%08lx\n", ioreq));

  Disable();

  /* mark being offline
  */
  base->dd_flags |= DDF_OFFLINE;

  /* already offline?
  */
  if (base->dd_flags & DDF_ONLINE)
  {
    /* mark no longer online
    */
    base->dd_flags &= ~DDF_ONLINE;

    /* is nic initialized?
    */
    if (base->dd_flags & DDF_NICUP)
    {
      nic_t *nic = ATTRMEM_BASE;

      /* set receiver to monitor mode
      */
      nic->nic_rcr = DSRC_MON; delay();
    }

    /* *DO NOT* abort pending read & write requests
       Miami dies a horrible death if this is enabled
    */
#if 0
    AbortRW(deverror(S2ERR_OUTOFSERVICE, S2WERR_UNIT_OFFLINE), base);
#endif

    /* return any OFFLINE events
    */
    DoEvent(S2EVENT_OFFLINE, base);
  }

  Enable();

  /* return success
  */
  TermIO(ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}

stdata const UWORD NSDSupported[] =
{
  CMD_READ, CMD_WRITE, CMD_FLUSH,
  S2_DEVICEQUERY, S2_GETSTATIONADDRESS, S2_CONFIGINTERFACE,
  S2_ADDMULTICASTADDRESS, S2_DELMULTICASTADDRESS,
  S2_MULTICAST, S2_BROADCAST, S2_TRACKTYPE, S2_UNTRACKTYPE,
  S2_GETTYPESTATS, S2_GETSPECIALSTATS, S2_GETGLOBALSTATS,
  S2_ONEVENT, S2_READORPHAN, S2_ONLINE, S2_OFFLINE,
  NSCMD_DEVICEQUERY,
  S2_ADDMULTICASTADDRESSES, S2_DELMULTICASTADDRESSES,
  0
};


stcode short cmdNSDeviceQuery(struct IOStdReq *ioreq,
                              struct MyDevice *base)
{
  struct my_NSDeviceQueryResult *query = (struct my_NSDeviceQueryResult *) ioreq->io_Data;

  KPRINTF(1, ("NSCMD_DEVICEQUERY ioreq: 0x%08lx query: 0x%08lx\n", ioreq, query));

  /* NULL ptr?
     enough data?
     valid request?
  */
  if ((query == NULL) ||
      (ioreq->io_Length < sizeof(struct my_NSDeviceQueryResult)) ||
      (query->DevQueryFormat != 0) ||
      (query->SizeAvailable != 0))
  {
    ioreq->io_Error = IOERR_NOCMD;
    TermIO((struct IOSana2Req *) ioreq, base);
    /* fake ok return (needed, or else devBeginIO() craps
    */
    return deverror(S2ERR_NO_ERROR, 0);
  }

  ioreq->io_Actual = query->SizeAvailable =
    sizeof(struct my_NSDeviceQueryResult);
  query->DeviceType = MY_NSDEVTYPE_SANA2;
  query->DeviceSubType = 0;
  query->SupportedCommands = NSDSupported;

  /* return success
  */
  TermIO((struct IOSana2Req *) ioreq, base);
  return deverror(S2ERR_NO_ERROR, 0);
}


stcode short cmdAddMulticastAddresses(struct IOSana2Req *ioreq,
                                      struct MyDevice *base)
{
  KPRINTF(1, ("S2_ADDMULTICASTADDRESSES 0x%08lx%04lx - 0x%08lx%04lx\n",
              ((ULONG *) ioreq->ios2_SrcAddr)[0], ((UWORD *) ioreq->ios2_SrcAddr)[2],
              ((ULONG *) ioreq->ios2_DstAddr)[0], ((UWORD *) ioreq->ios2_DstAddr)[2]));

  return addmcastrange(ioreq, ioreq->ios2_SrcAddr, ioreq->ios2_DstAddr, base);
}

stcode short cmdDelMulticastAddresses(struct IOSana2Req *ioreq,
                                      struct MyDevice *base)
{
  KPRINTF(1, ("S2_DELMULTICASTADDRESSES 0x%08lx%04lx - 0x%08lx%04lx\n",
              ((ULONG *) ioreq->ios2_SrcAddr)[0], ((UWORD *) ioreq->ios2_SrcAddr)[2],
              ((ULONG *) ioreq->ios2_DstAddr)[0], ((UWORD *) ioreq->ios2_DstAddr)[2]));

  return delmcastrange(ioreq, ioreq->ios2_SrcAddr, ioreq->ios2_DstAddr, base);
}


stcode void NATDECLFUNC_2(devBeginIO,
                          a1, struct IOSana2Req *, ioreq,
                          a6, struct MyDevice *, base)
{
  DECLARG_2(a1, struct IOSana2Req *, ioreq,
            a6, struct MyDevice *, base)

  short ret;

  /*KPRINTF(1, ("devBeginIO ioreq: 0x%08lx base: 0x%08lx cmd: %lu\n", ioreq, base, ioreq->ios2_Req.io_Command));
  */

  ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE;
  ioreq->ios2_Req.io_Error = S2ERR_NO_ERROR;

  if (ioreq->ios2_Req.io_Command < NSCMD_DEVICEQUERY)
  {
    switch (ioreq->ios2_Req.io_Command)
    {

      case CMD_READ:
        ret = cmdRead(ioreq, base);
        break;

      case CMD_WRITE:
        ret = cmdWrite(ioreq, base);
        break;

      case CMD_FLUSH:
        ret = cmdFlush(ioreq, base);
        break;

      case S2_DEVICEQUERY:
        ret = cmdDeviceQuery(ioreq, base);
        break;

      case S2_GETSTATIONADDRESS:
        ret = cmdGetStationAddress(ioreq, base);
        break;

      case S2_CONFIGINTERFACE:
        ret = cmdConfigInterface(ioreq, base);
        break;

      case S2_ADDMULTICASTADDRESS:
        ret = cmdAddMulticastAddress(ioreq, base);
        break;

      case S2_DELMULTICASTADDRESS:
        ret = cmdDelMulticastAddress(ioreq, base);
        break;

      case S2_MULTICAST:
        ret = cmdMulticast(ioreq, base);
        break;

      case S2_BROADCAST:
        ret = cmdBroadcast(ioreq, base);
        break;

      case S2_TRACKTYPE:
        ret = cmdTrackType(ioreq, base);
        break;

      case S2_UNTRACKTYPE:
        ret = cmdUntrackType(ioreq, base);
        break;

      case S2_GETTYPESTATS:
        ret = cmdGetTypeStats(ioreq, base);
        break;

      case S2_GETSPECIALSTATS:
        ret = cmdGetSpecialStats(ioreq, base);
        break;

      case S2_GETGLOBALSTATS:
        ret = cmdGetGlobalStats(ioreq, base);
        break;

      case S2_ONEVENT:
        ret = cmdOnEvent(ioreq, base);
        break;

      case S2_READORPHAN:
        ret = cmdReadOrphan(ioreq, base);
        break;

      case S2_ONLINE:
        ret = cmdOnline(ioreq, base);
        break;

      case S2_OFFLINE:
        ret = cmdOffline(ioreq, base);
        break;

      default:
        ret = deverror(IOERR_NOCMD, S2WERR_GENERIC_ERROR);
        break;
    }
  }
  else
  {
    switch (ioreq->ios2_Req.io_Command)
    {
      case NSCMD_DEVICEQUERY:
        ret = cmdNSDeviceQuery((struct IOStdReq *) ioreq, base);
        break;

      case S2_ADDMULTICASTADDRESSES:
        ret = cmdAddMulticastAddresses(ioreq, base);
        break;

      case S2_DELMULTICASTADDRESSES:
        ret = cmdDelMulticastAddresses(ioreq, base);
        break;

      default:
        ret = deverror(IOERR_NOCMD, S2WERR_GENERIC_ERROR);
        break;
    }
  }

  if (ret)
  {
    ioreq->ios2_Req.io_Error = ret & 0xff;
    ioreq->ios2_WireError = ret >> 8;
    TermIO(ioreq, base);
  }
}


/*
*============================================================
*             PCMCIA Card Removed interrupt
*============================================================
*
*   Occurs whenever a PCMCIA card is unplugged
*
* WARNING *** WARNING *** WARNING *** WARNING *** WARNING
*
* card.resource task has only 512 bytes stack size, so
* we'd better not overflow it here!
*
*/
stcode void iremoved(struct MyDevice *base)
{
  struct Library *CardResource = base->dd_cardres;

  KPRINTF(10, ("PCMCIA card removed base: 0x%08lx\n", base));

  base->dd_flags &= ~(DDF_ONLINE | DDF_NICUP);
  ReleaseCard(&base->dd_cardhandle, 0);
  DoEvent(S2EVENT_OFFLINE, base);
}


stcode void DECLFUNC_1(removed,
                       a1, struct MyDevice *, base)
{
  DECLARG_1(a1, struct MyDevice *, base)

  iremoved(base);
}



/*
*============================================================
*             PCMCIA Card Inserted interrupt
*============================================================
*
*   Occurs whenever a PCMCIA card is plugged in
*
* NOTE: card.resource has given us ownership of the card
*
* WARNING *** WARNING *** WARNING *** WARNING *** WARNING
*
* card.resource task has only 512 bytes stack size, so
* we'd better not overflow it here!
*
*/
stcode void iinserted(struct MyDevice *base)
{
  KPRINTF(10, ("PCMCIA card inserted base: 0x%08lx\n", base));

  /* is someone interested about cards ?
  */
  if (base->dd_library.lib_OpenCnt != 0)
  {
    struct Library *CardResource = base->dd_cardres;

    /* attempt to init the pcmcia card
       attempt to start the controller
    */
    if ((!init_card(1, base)) && (!nic_init(1, base)))
    {
      /* has device been put offline ?
      */
      if (!(base->dd_flags & DDF_OFFLINE))
      {
        /* device now online
        */
        base->dd_flags |= DDF_ONLINE;
        DoEvent(S2EVENT_ONLINE, base);
        return;
      }
    }

    /* stop controller
    */
    nic_kill(base);

    /* release foreign or bad card
    */
    ReleaseCard(&base->dd_cardhandle, 0);
  }
}

stcode void DECLFUNC_1(inserted,
                       a1, struct MyDevice *, base)
{
  DECLARG_1(a1, struct MyDevice *, base)

  iinserted(base);
}



/*
*============================================================
*             PCMCIA status change interrupt
*============================================================
*
*   Occurs whenever a PCMCIA status line changes
*
*   eg. when the network card activates it's interrupt line
*
* WARNING *** WARNING *** WARNING *** WARNING *** WARNING
*
* card.resource task has only 512 bytes stack size, so
* we'd better not overflow it here!
*
*/
stcode UBYTE istatus(struct MyDevice *base,
                     UBYTE intmask)
{

  if (base->dd_flags & DDF_NICUP)
  {
    struct ExecBase *SysBase = base->dd_SysBase;
    nic_t *nic = ATTRMEM_BASE;
    UBYTE oldcmd, isr, x;

/*
    KPRINTF(10, ("*** status int mask: 0x%02lx intable: 0x%02lx base: 0x%08lx\n",
                 intmask, base->dd_imr, base));
*/


    /* save old command
    */
    oldcmd = nic->nic_cr; delay();

    /* prevent nic interrupts
    */
    nic->nic_imr = 0; delay();

    /* interrupt service loop
    */

    /* read nic interrupt status
       clear current interrupt bits
       any valid interrupts?
    */

    isr = nic->nic_isr; delay(); nic->nic_isr = isr; delay();
    while (isr & base->dd_imr)
    {
/*
      KPRINTF(10, ("*** isr: 0x%02lx imr: 0x%02lx\n",
                   isr, base->dd_imr));
*/

      /* Overwrite warning enable ?
      */
      if (isr & DSIS_ROVRN)
      {
        /* receiver ring buffer overflowed (eek!)
        */
        base->dd_overflows++;

        /* reset remote byte count
        */
        nic->nic_rbcr0 = 0; delay();
        nic->nic_rbcr1 = 0; delay();

        /* monitor mode
        */
        nic->nic_tcr = DSTC_LB0; delay();
        nic->nic_rcr = DSRC_MON; delay();

        /* try to restart controller
        */
        nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_START; delay();
        nic->nic_rcr = DSRC_AB; delay();

        /* normal rx mode
        */
        nic->nic_tcr = 0; delay();
      }

      /* Receive error enable ?
      */
      if (isr & DSIS_RXE)
      {
        /* another bad rx packet
        */
        base->dd_devicestats.BadData++;

        /* read rx status
        */
        x = nic->nic_rsr; delay();

        /* read counters
        */
        x = nic->nic_cntr0; delay();
        x = nic->nic_cntr1; delay();
        x = nic->nic_cntr1; delay();
        goto rx;
      }

      /* Packet received enable ?
      */
      if (isr & DSIS_RX)
      {
        rx:
        /* new packet(s) arrived in receive ring buffer
        */

        /* ignore rx ints
        */
        base->dd_imr &= ~(DSIM_OVWE | DSIM_RXEE | DSIM_PRXE);

        /* to copy packet(s) into waiting ioreq(s)
        */
        Cause(&base->dd_rxint);
      }

      /* Transmit error enable ?
         Packet transmitted enable ?
      */
      if (isr & (DSIS_TXE | DSIS_TX))
      {
        /* a packet has just been transmitted
        */

        /* read collision count
        */
        x = nic->nic_ncr; delay();

        /* buffer now free
        */
        base->dd_flags &= ~DDF_TX;

        /* increment retry count
        */
        base->dd_retries += x;

        /* count packets sent
        */
        base->dd_devicestats.PacketsSent++;

        /* transmit next packet(s)
        */
        Cause(&base->dd_txint);
      }

      /* counter overflow ?
      */
      if (isr & DSIS_CTRS)
      {
        /* counter overflow - read counters
        */
        x = nic->nic_cntr0; delay();
        x = nic->nic_cntr1; delay();
        x = nic->nic_cntr1; delay();
      }


      /* read nic interrupt status
         clear current interrupt bits
         loop until no longer valid ints
      */
      isr = nic->nic_isr; delay(); nic->nic_isr = isr; delay();
    }

    /* restore old command
    */
    nic->nic_cr = oldcmd; delay();

    /* enable nic interrupts
    */
    nic->nic_imr = base->dd_imr; delay();

/*
    KPRINTF(10, ("status int exit: 0x%02lx\n", intmask));
*/
  }

  /* enable original intmask
  */
  return intmask;
}


stcode UBYTE DECLFUNC_2(status,
                       a1, struct MyDevice *, base,
                       d0, UBYTE, intmask)
{
  DECLARG_2(a1, struct MyDevice *, base,
            d0, UBYTE, intmask)

  return istatus(base, intmask);
}


stcode inline void *callcopy(void *routine,
                             void *from,
                             void *to,
                             ULONG len)
{
#ifdef __MORPHOS__
  struct EmulCaos caos;

  caos.caos_Un.Function = (APTR) routine;
  caos.reg_a0 = (ULONG) from;
  caos.reg_a1 = (ULONG) to;
  caos.reg_d0 = len;
  return (void *) ((*MyEmulHandle->EmulCall68k) (&caos));

#else

  MASM void * (*call) (MREG(a0, void *), MREG(a1, void *), MREG(d0, ULONG)) =
  (MASM void * (*) (MREG(a0, void *), MREG(a1, void *), MREG(d0, ULONG))) routine;

  return (*call) (from, to, len);
#endif /* __MORPHOS__ */
}

stcode inline BOOL callfilter(void *hook,
                              void *ios2,
                              void *data)
{

#ifdef __MORPHOS__
  struct EmulCaos caos;

  KPRINTF(10, ("callfilter hook: 0x%08lx ioreq: 0x%08lx data: 0x%08lx\n",
                hook, ios2, data));

  caos.caos_Un.Function = (APTR) ((struct Hook *) hook)->h_Entry;
  caos.reg_a0 = (ULONG) hook;
  caos.reg_a2 = (ULONG) ios2;
  caos.reg_a1 = (ULONG) data;
  return (BOOL) ((*MyEmulHandle->EmulCall68k) (&caos));

#else

  MASM BOOL (*call) (MREG(a0, void *), MREG(a2, void *), MREG(a1, void *)) =
  (MASM BOOL (*) (MREG(a0, void *), MREG(a2, void *), MREG(a1, void *)))
  ((struct Hook *) hook)->h_Entry;

  KPRINTF(10, ("callfilter hook: 0x%08lx ioreq: 0x%08lx data: 0x%08lx\n",
                hook, ios2, data));

  return (*call) (hook, ios2, data);
#endif /* __MORPHOS__ */
}



stcode void writepacket(struct IOSana2Req *ioreq,
                        struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  nic_t *nic = ATTRMEM_BASE;
  ULONG packettype = ioreq->ios2_PacketType;
  struct etherpacket_hdr *etherhdr = (struct etherpacket_hdr *) base->dd_txbuffer;
  UBYTE *packetdata = base->dd_txbuffer + sizeof(struct etherpacket_hdr);
  UBYTE *copydest = base->dd_txbuffer;
  UBYTE *dma;
  UWORD writelen = ioreq->ios2_DataLength;
  struct bufman *bufman = ioreq->ios2_BufferManagement;
  struct Sana2PacketTypeStats *stats;
  UWORD retries = TX_RETRIES;

  /* not raw packet?
  */
  if (!(ioreq->ios2_Req.io_Flags & SANA2IOF_RAW))
  {
    int r;

    /* the ethernet header isn't included in the data
    */

    /* build ethernet packet header
    */
    for (r = 0; r < ETHER_ADDR_SIZE; r++)
    {
      etherhdr->dest[r] = ioreq->ios2_DstAddr[r];
      etherhdr->src[r] = base->dd_stationaddress[r];
    }
    etherhdr->type = packettype;

    /* packet data is at txbuffer
    */
    copydest += sizeof(struct etherpacket_hdr);
    writelen += sizeof(struct etherpacket_hdr);
  }

  /* do we have the tag for DMA?
     if so, ask for the dma address
  */
  if ((bufman->dmacopyfrombuf32) &&
      ((dma = callcopy(bufman->dmacopyfrombuf32, ioreq->ios2_Data, NULL, 0))))
  {
    /* RAW write?
    */
    if (ioreq->ios2_Req.io_Flags & SANA2IOF_RAW)
    {
      /* with RAW write the header is included in the DMA data
      */
      etherhdr = (struct etherpacket_hdr *) dma;
      packetdata = dma + sizeof(struct etherpacket_hdr);
    }
    else
    {
      /* with non-RAW write the header is not included in the DMA data
      */
      packetdata = dma;
    }
  }
  else
  {
    /* dma not available, fallback to regular copy
    */
    if (callcopy(bufman->copyfrombuf, copydest, ioreq->ios2_Data,
                 ioreq->ios2_DataLength) == NULL)
    {
      KPRINTF(10, ("writepacket: copyfrom returned failure!\n"));

      DoEvent(S2EVENT_ERROR | S2EVENT_TX | S2EVENT_BUFF, base);
      ioreq->ios2_DataLength = 0;
      ioreq->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
      ioreq->ios2_WireError = S2WERR_BUFF_ERROR;
      TermIO(ioreq, base);
      return;
    }
  }


  /* copy the transfer buffer to cards memory
  */

  /* set the transmit flag
  */
  base->dd_flags |= DDF_TX;

  for (;;)
  {
    if (RemoteWrite((UWORD *) etherhdr, (UWORD *) packetdata,
                    writelen - sizeof(struct etherpacket_hdr), base) != 0)
    {
      /* transmit ok - break out
      */
      break;
    }
    else
    {
      /* transmit failed. now unless retrycount become 0 retry.
      */
      if (--retries == 0)
      {
        /* clear the transmit flag! (MUST be done!)
        */
        base->dd_flags &= ~DDF_TX;

        KPRINTF(10, ("writepacket: transmit error!\n"));
        DoEvent(S2EVENT_ERROR | S2EVENT_TX, base);
        ioreq->ios2_DataLength = 0;
        ioreq->ios2_Req.io_Error = S2ERR_TX_FAILURE;
        ioreq->ios2_WireError = S2WERR_GENERIC_ERROR;
        TermIO(ioreq, base);
        return;
      }
    }
  }

  /* adjust writelen to legal packet size
  */
  if (writelen < ETHER_MIN_LEN)
  {
    writelen = ETHER_MIN_LEN;
  }


  DB(
  if (etherhdr->type < 1500)
  {
    KPRINTF(5, ("writepacket: %04lx%08lx > %04lx%08lx (IEEE802.3) len %lu, %lu bytes\n",
                *((UWORD *) etherhdr->src), *((ULONG *) (etherhdr->src + 2)),
                *((UWORD *) etherhdr->dest), *((ULONG *) (etherhdr->dest + 2)),
                etherhdr->type, writelen));
  }
  else
  {
    KPRINTF(5, ("writepacket: %04lx%08lx > %04lx%08lx type %lu, %lu bytes\n",
                *((UWORD *) etherhdr->src), *((ULONG *) (etherhdr->src + 2)),
                *((UWORD *) etherhdr->dest), *((ULONG *) (etherhdr->dest + 2)),
                etherhdr->type, writelen));
  }
  dumpmem(packetdata, writelen - sizeof(struct etherpacket_hdr));
  )


  /* now really start the transfer
  */
  Disable();

  /* set tx byte count lo & hi
  */
  nic->nic_tbcr0 = writelen; delay();
  nic->nic_tbcr1 = writelen >> 8; delay();

  /* start tx
  */
  nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_TRANS | DSCM_START; delay();
  Enable();

  /* update statistics
  */
  if ((stats = findpackettypestats(packettype, base)))
  {
    stats->PacketsSent++;
    stats->BytesSent += writelen;
  }

  /* terminate the ioreq
  */
  TermIO(ioreq, base);
}

/*
*========================================================
*                 txintcode(device)
*========================================================
*
*  send packets to network card. packets will be put
*  into the card's onboard 16 bit ram, and then
*  transmitted to the wire.
*
*/

stcode void DECLFUNC_1(tx,
                       a1, struct MyDevice *, base)
{
  DECLARG_1(a1, struct MyDevice *, base)

  struct ExecBase *SysBase = base->dd_SysBase;
  struct IOSana2Req *ioreq;

  /*KPRINTF(1, ("*** tx int\n"));
  */

  for (;;)
  {
    /* quit if tx in progress (status int
       will restart us when tx complete)
    */
    if (base->dd_flags & DDF_TX)
    {
      break;
    }

    if ((ioreq = (struct IOSana2Req *)
        RemHead((struct List *)&base->dd_writelist)))
    {
      /* got request, process it
      */
      writepacket(ioreq, base);
    }
    else
    {
      /* no more packets to transmit
      */
      break;
    }
  }
}


/*
*==============================================================
*             readpacket( prhdr, pageoff, device)
*==============================================================
*
* get packet from network card and feed it to all different
* CMD_READ queues. Also call packet filter before doing the copy.
* 
* Note that this is a major improvement to previous implementation,
* that only kept one single list of CMD_READs. This finally fixes
* sanamon for example.
*
* Inputs:
*
*       pkthdr = packet header info extracted from nic
*
*       pageoff = page offset in nic RAM that holds packet
*
*       device = device base
*/

#define HEADER_TOTAL (sizeof(struct prhdr) + sizeof(struct etherpacket_hdr))

#define DROPPED      (1<<0)   /* did the packet get dropped? */
#define PACKETFILTER (1<<1)   /* use packet filter? */



stcode void readioreq(struct prhdr *prhdr,
                      UWORD datasize,
                      struct IOSana2Req *ioreq,
                      UBYTE *flags,
                      struct MyDevice *base)
{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct etherpacket_hdr *etherhdr = (struct etherpacket_hdr *) (prhdr + 1);
  UBYTE *copyfrom = (UBYTE *) etherhdr;
  int r;

  /* handle RAW read
  */
  if (ioreq->ios2_Req.io_Flags & SANA2IOF_RAW)
  {
    /* ShapeShifter won't work with `sizeof(struct etherpacket_hdr)'
       here. This is most likely because it want the RAW ethernet
       packet checksum size (4) added to the packet size.
    */

    datasize += sizeof(struct etherpacket_hdr) + 4;
  }
  else
  {
    copyfrom += sizeof(struct etherpacket_hdr);
  }

  /* build up the ios2 structure enough so we can call the packet
     filter
  */
  ioreq->ios2_PacketType = etherhdr->type;
  for (r = 0; r < ETHER_ADDR_SIZE; r++)
  {
    ioreq->ios2_SrcAddr[r] = etherhdr->src[r];
    ioreq->ios2_DstAddr[r] = etherhdr->dest[r];
  }
  ioreq->ios2_DataLength = datasize;


  /* call the packet filter, if available
  */
  if ((*flags & PACKETFILTER) &&
      (((struct bufman *) ioreq->ios2_BufferManagement)->packetfilter) &&
      (!callfilter(((struct bufman *) ioreq->ios2_BufferManagement)->packetfilter,
                   ioreq, copyfrom)))
  {
    /* this packet got filtered out!
    */
    KPRINTF(7, ("readioreq: packet type %lu for ioreq 0x%08lx filtered\n", 
                etherhdr->type, ioreq));
    return;
  }


  /* ok. the packet didn't get filtered, set the BCAST and MCAST
     flags according to dstaddr.
  */

  /* address == Multicast?
  */
  if (ioreq->ios2_DstAddr[0] & 1)
  {
    /* address == Broadcast?
    */
    if ((*((ULONG *) ioreq->ios2_DstAddr) == 0xffffffff) &&
        (*((UWORD *) (ioreq->ios2_DstAddr + 4)) == 0xffff))
    {
      ioreq->ios2_Req.io_Flags |= SANA2IOF_BCAST;
    }
    else
    {
      ioreq->ios2_Req.io_Flags |= SANA2IOF_MCAST;
    }
  }

  /* finally copy the packet data!
  */
  if (callcopy(((struct bufman *)ioreq->ios2_BufferManagement)->copytobuf,
               ioreq->ios2_Data, copyfrom, ioreq->ios2_DataLength) != NULL)
  {
    DB(
    KPRINTF(5, ("readioreq: copytobuffed packet ior 0x%08lx, %04lx%08lx < %04lx%08lx, type %lu, %lu bytes, %s%s%s\n",
                ioreq,
                *((UWORD *) ioreq->ios2_DstAddr), *((ULONG *) (ioreq->ios2_DstAddr + 2)),
                *((UWORD *) ioreq->ios2_SrcAddr), *((ULONG *) (ioreq->ios2_SrcAddr + 2)),
                ioreq->ios2_PacketType, ioreq->ios2_DataLength,
                (ioreq->ios2_Req.io_Flags & SANA2IOF_RAW) ? "RAW " : "",
                (ioreq->ios2_Req.io_Flags & SANA2IOF_BCAST) ? "BCAST " : "",
                (ioreq->ios2_Req.io_Flags & SANA2IOF_MCAST) ? "MCAST " : ""
                ));
    dumpmem(copyfrom, ioreq->ios2_DataLength);
    )

    /* clear the dropped flag
    */
    *flags &= ~DROPPED;
  }
  else
  {
    KPRINTF(10, ("readioreq: copyto returned failure!\n"));

    DoEvent(S2EVENT_ERROR | S2EVENT_RX | S2EVENT_BUFF, base);
    ioreq->ios2_DataLength = 0;
    ioreq->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
    ioreq->ios2_WireError = S2WERR_BUFF_ERROR;
  }

  /* Pull the ioreq off the list & terminate it
  */
  Remove((struct Node *) ioreq);
  TermIO(ioreq, base);
}


stcode void readpacket(struct prhdr *prhdr,
                       UWORD pageoff,
                       struct MyDevice *base)
{
  struct etherpacket_hdr *etherhdr = (struct etherpacket_hdr *) (prhdr + 1);
  UBYTE *packetdata = (UBYTE *) (etherhdr + 1);
  struct bufman *bufman;
  struct IOSana2Req *worknode, *nextnode;
  struct Sana2PacketTypeStats *stats = findpackettypestats(etherhdr->type, base);
  UBYTE flags = DROPPED | PACKETFILTER;

  /* calculate size of the actual data
  */
  UWORD datasize = ((prhdr->sz1 << 8) | prhdr->sz0) - HEADER_TOTAL;

  /* is packet datasize valid?
  */
  if ((datasize >= (ETHER_MIN_LEN - HEADER_TOTAL)) &&
      (datasize <= (ETHER_MAX_LEN - HEADER_TOTAL)))
  {
    /* read the packet data off the nic memory
    */
    RemoteRead((UWORD *) packetdata, pageoff + HEADER_TOTAL, datasize, base);

    DB(
    KPRINTF(5, ("readpacket: %04lx%08lx < %04lx%08lx, type %lu, %lu bytes\n",
                *((UWORD *) etherhdr->dest), *((ULONG *) (etherhdr->dest + 2)),
                *((UWORD *) etherhdr->src), *((ULONG *) (etherhdr->src + 2)),
                etherhdr->type, datasize + sizeof(struct etherpacket_hdr)));
    dumpmem(packetdata, datasize);
    )


    /* update the packet statistics
    */
    if (stats)
    {
      stats->PacketsReceived++;
      stats->BytesReceived += datasize;  /* NOTE: don't include headers */
    }

    /* for each bufman
    */
    initlistwalk(struct bufman *, bufman, &base->dd_bufmanlist);
    while (listwalk(struct bufman *, bufman))
    {
      /* for each ioreq
      */
      worknode = (struct IOSana2Req *) bufman->rxqueue.mlh_Head;
      while ((nextnode = (struct IOSana2Req *) (((struct MinNode *) worknode)->mln_Succ)))
      {
        /* Check the packet type. Also handles 802.3 packets.
        */
        if ((worknode->ios2_PacketType == etherhdr->type) ||
            ((etherhdr->type < 1500) && (worknode->ios2_PacketType < 1500)))
        {
          readioreq(prhdr, datasize, worknode, &flags, base);

          /* break out - let other callers get the packet too
          */
          break;
        }
        worknode = nextnode;
      }
    }

    /* Now we've tried to give the packet to every CMD_READ caller.
       If DROPPED is set at this point no-one wanted this packet.
    */
    if (flags & DROPPED)
    {
      /* So there were no outstanding CMD_READs or the packet wasn't
         accepted by any of them. Okay, check if we have any pending
         S2_READORPHAN ioreq in list and if we have return this packet
         with it. Note that packet filter must not be used for this
         time!
      */

      /* don't use packet filter for orpanread!
      */
      flags &= ~PACKETFILTER;

      /* process pending orphanread iorequs
      */
      worknode = (struct IOSana2Req *) base->dd_orphanlist.mlh_Head;
      while ((nextnode = (struct IOSana2Req *) (((struct MinNode *) worknode)->mln_Succ)))
      {
        readioreq(prhdr, datasize, worknode, &flags, base);

        worknode = nextnode;
      }
    }
    else
    {
      /* packet not dropped - return ok
      */
      return;
    }
  }

  /* update global dropped packet counter
  */
  base->dd_devicestats.UnknownTypesReceived++;

  /* update dropped packet statistics.
  */
  if (stats)
  {
    stats->PacketsDropped++;
  }
  KPRINTF(9, ("readpacket: packet type %lu dropped\n", etherhdr->type));

  DoEvent(S2EVENT_ERROR | S2EVENT_RX, base);
}


/*
*============================================================
*                   rxintcode(device)
*============================================================
*
*                service rx interrupts
*
*/

stcode void DECLFUNC_1(rx,
                       a1, struct MyDevice *, base)
{
  DECLARG_1(a1, struct MyDevice *, base)

  struct ExecBase *SysBase = base->dd_SysBase;
  nic_t *nic = ATTRMEM_BASE;
  UBYTE page, next;

  /*KPRINTF(1, ("*** rx int\n"));
  */

  for(;;)
  {
    Disable();

    /* select bank 1
    */
    nic->nic_cr = DSCM_NODMA | DSCM_PG1 | DSCM_START; delay();

    /* read current page
    */
    page = nic->nic_curr; delay();

    /* select bank 0
    */
    nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_START; delay();

    Enable();

    /* get next page
    */
    next = nic->nic_bnry + 1; delay();

    /* end of buffer mem  ?
    */
    if (next >= (RBUFEND / 256))
    {
      /* wrap around to start
      */
      next = RBUF / 256;
    }
    
    /* current page == next page ?
    */
    if (page == next)
    {
      /* if so then nothing to get - quit
      */
      break;
    }

    /* read the packet header and ethernet header
    */
    RemoteRead((UWORD *) base->dd_rxbuffer, next << 8, HEADER_TOTAL, base);

    /* incomplete packet received ?
    */
    if (((struct prhdr *) base->dd_rxbuffer)->status & DSRS_RPC)
    {
      /*
      KPRINTF(1, ("rx: packet received status: 0x%02lx nxtpg: 0x%02lx size: 0x%04lx\n",
                  ((struct prhdr *) base->dd_rxbuffer)->status,
                  ((struct prhdr *) base->dd_rxbuffer)->nxtpg,
                  (((struct prhdr *) base->dd_rxbuffer)->sz1 << 8) |
                  ((struct prhdr *) base->dd_rxbuffer)->sz0));

      KPRINTF(1, ("rx: 0x%08lx%04lx -> 0x%08lx%04lx type: %lu\n",
                  *((ULONG *)(((struct etherpacket_hdr *) (base->dd_rxbuffer + sizeof(struct prhdr)))->src)),
                  *((UWORD *)(((struct etherpacket_hdr *) (base->dd_rxbuffer + sizeof(struct prhdr)))->src + 4)),
                  *((ULONG *)(((struct etherpacket_hdr *) (base->dd_rxbuffer + sizeof(struct prhdr)))->dest)),
                  *((UWORD *)(((struct etherpacket_hdr *) (base->dd_rxbuffer + sizeof(struct prhdr)))->dest + 4)),
                  ((struct etherpacket_hdr *) (base->dd_rxbuffer + sizeof(struct prhdr)))->type));
      */


      /* count packets received
      */
      base->dd_devicestats.PacketsReceived++;

      /* feed packet into waiting ioreqs
      */
      readpacket((struct prhdr *) base->dd_rxbuffer, next << 8, base);
    }
    else
    {
      /* count bad packets received
      */
      KPRINTF(10, ("rx: incomplete packet received\n"));

      base->dd_devicestats.BadData++;
    }

    /* get next page number
       nxtpage-1 = new boundary
    */
    next = ((struct prhdr *) base->dd_rxbuffer)->nxtpg - 1;

    if (next < (RBUF / 256))
    {
      /* wrap if before 1st page
      */
      next = (RBUFEND / 256) - 1;
    }

    /* set new boundary
    */
    nic->nic_bnry = next; delay();

    /* loop...
    */
  }

  /* all packets processed
  */

  /* select bank 0
  */
  nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_START; delay();

  Disable();

  /* read rx status
     read counters
  */
  page = nic->nic_rsr; delay();
  page = nic->nic_cntr0; delay();
  page = nic->nic_cntr1; delay();
  page = nic->nic_cntr2; delay();

  /* allow rx interrupts
  */
  base->dd_imr |= (DSIM_OVWE | DSIM_RXEE | DSIM_PRXE);
  nic->nic_imr = base->dd_imr; delay();

  Enable();
}



/*
*==================================================================
*        RemoteRead(buffer, nicbuffer, length)
*==================================================================
*
* Get a copy of data stored in the network card's onboard RAM.
*
*  buffer     = Amiga RAM
*
*  nicbuffer  = 16 bit address in card memory
*/

stcode void RemoteRead(UWORD *buffer,
                       UWORD nicbuffer,
                       UWORD len,
                       struct MyDevice *base)
{
  nic_t *nic = ATTRMEM_BASE;
  UBYTE oldcmd;
  short r;

  /* bump up count to even value
  */
  len = (len + 3) & -4;

  /*
  KPRINTF(1, ("RemoteRead: reading %lu bytes at %lu\n",
              len, nicbuffer));
  */

  /* save old command
  */
  oldcmd = nic->nic_cr; delay();

  /* select bank 0
  */
  nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_START; delay();

  /* setup remote dma
  */
  nic->nic_isr = DSIS_RDC; delay();

  /* set count.lo & count.hi
  */
  nic->nic_rbcr0 = len; delay();
  nic->nic_rbcr1 = (len >> 8); delay();

  /* set address.lo & address.hi
  */
  nic->nic_rsar0 = nicbuffer; delay();
  nic->nic_rsar1 = (nicbuffer >> 8); delay();

  /* request Remote Read
  */
  nic->nic_cr = DSCM_RREAD | DSCM_PG0 | DSCM_START; delay();

  /* transfer the data
  */
  do
  {
    *buffer++ = nic->nic_data;
    *buffer++ = nic->nic_data;
  } while (len -= 4);

  /* wait for remote DMA complete
  */
  for (r = 30000; (!(nic->nic_isr & DSIS_RDC)) && (r > 0); r--)
  {
    delay();
  }

  /* remote DMA complete
  */
  nic->nic_isr = DSIS_RDC; delay();

  /* restore old command
  */
  nic->nic_cr = oldcmd; delay();
}


/*
*=================================================================
*         RemoteWrite(etherhdr, packetdata, datalen, device)
*=================================================================
*
*      Puts data into the network card's onboard RAM
*
*/

stcode short RemoteWrite(UWORD *etherhdr,
                         UWORD *packetdata,
                         UWORD datalen,
                         struct MyDevice *base)
{
  nic_t *nic = ATTRMEM_BASE;
  UWORD writelen;
  UBYTE oldcmd;
  short r;

  /* bump up count to even value
  */
  datalen = (datalen + 3) & -4;

  /* calculate the write len
  */
  writelen = datalen + sizeof(struct etherpacket_hdr);

  /* save old command
  */
  oldcmd = nic->nic_cr; delay();

  /* remote DMA complete
  */
  nic->nic_isr = DSIS_RDC; delay();

  /* select bank 0
  */
  nic->nic_cr = DSCM_NODMA | DSCM_PG0 | DSCM_START; delay();

  /* set address.lo & address.hi
  */
  nic->nic_rsar0 = (TBUF & 0xff); delay();
  nic->nic_rsar1 = (TBUF >> 8); delay();

  /* set count.lo & count.hi
  */
  nic->nic_rbcr0 = writelen; delay();
  nic->nic_rbcr1 = (writelen >> 8); delay();

  /* request Remote Write
  */
  nic->nic_cr = DSCM_RWRITE | DSCM_PG0 | DSCM_START; delay();

  /* write the etherpacket header
  */
  for (r = 0; r < (sizeof(struct etherpacket_hdr) / 2); r++)
  {
    nic->nic_data = *etherhdr++;
  }

  /* write the packet data
  */
  do
  {
    nic->nic_data = *packetdata++;
    nic->nic_data = *packetdata++;
  } while (datalen -= 4);

  /* wait for remote DMA complete
  */
  for (r = 30000; (!(nic->nic_isr & DSIS_RDC)) && (r > 0); r--)
  {
    delay();
  }

  /* remote DMA complete
  */
  nic->nic_isr = DSIS_RDC; delay();

  /* restore old command
  */
  nic->nic_cr = oldcmd; delay();

  /* return status
  */
  return r;
}


stcode void EasyReq(const UBYTE *bodymsg,
                    LONG *array,
                    struct MyDevice *base)

{
  struct ExecBase *SysBase = base->dd_SysBase;
  struct StackSwapStruct *sss;
  struct IntuitionBase *IntuitionBase;

  if ((sss = AllocVec(4096 + sizeof(struct StackSwapStruct), MEMF_PUBLIC)))
  {
    sss->stk_Lower   = sss + 1;
    sss->stk_Upper   = ((ULONG) sss->stk_Lower) + 4096;
    sss->stk_Pointer = (APTR) sss->stk_Upper;
    StackSwap(sss);

    if ((IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 36)))
    {
      struct EasyStruct es =
      {
        sizeof(struct EasyStruct), 0, (UBYTE *) vers + 5, NULL, "Ok"
      };

      es.es_TextFormat = (UBYTE *) bodymsg;
      EasyRequestArgs(NULL, &es, NULL, array);

      CloseLibrary((struct Library *) IntuitionBase);
    }

    StackSwap(sss);
    FreeVec(sss);
  }
}

#ifdef DEBUG
void dumpmem(APTR mem, ULONG len)
{
  unsigned char b, c, *p = (unsigned char *) mem, str[17];

  if (len == 0) return;

  dprintf("\n");

  do
  {
    for (b = 0; b < 16; b++)
    {
      c = *p++;
      str[b] = ((c >= ' ') && (c <= 'z')) ? c : '.';
      str[b + 1] = 0;
      dprintf("%02lx ", c);
      if (--len == 0) break;
    }

    while (++b < 16)
    {
      dprintf("   ");
    }

    dprintf("  %s\n", str);
  } while (len);

  dprintf("\n\n");
}

#ifndef __MORPHOS__

void dprintf(const char *format, ...)
{
  struct ExecBase *SysBase;
  static const UWORD dputch[5] = {0xCD4B, 0x4EAE, 0xFDFC, 0xCD4B, 0x4E75};
  va_list args;

  SysBase = *((struct ExecBase **) (4L));
  va_start(args, format);

  RawDoFmt((STRPTR) format, (APTR) args,
           (void (*)()) dputch, (APTR) SysBase);

  va_end(args);
}

#endif /* !__MORPHOS__ */
#endif /* DEBUG */

/* the end
*/
