/*
** $VER: server.c 1.15 (01 Apr 1998)
**
** magplip.device - Parallel Line Internet Protocol
**
** Original code written by Oliver Wagner and Michael Balzer.
**
** This version has been completely reworked by Marius Gröger, introducing
** slight protocol changes. The new source is a lot better organized and
** maintainable.
**
** Additional changes and code cleanup by Jan Kratochvil and Martin Mares.
** The new source is significantly faster and yet better maintainable.
**
** (C) Copyright 1993-1994 Oliver Wagner & Michael Balzer
** (C) Copyright 1995 Jan Kratochvil & Martin Mares
** (C) Copyright 1995-1996 Marius Gröger
**     All Rights Reserved
**
** $HISTORY:
**
** 01 Apr 1998 : 001.015 :  integrated modifications for linPLIP from Stephane
** 19 Mar 1998 : 001.014 :  fixed S2_ONLINE bug, which returned an
**                          error by a call to go online if it was
**                          already online. Report from Holger Kruse.
**                          Fixed by Stefan Ruppert.
** 29 Mar 1996 : 001.014 :  changed copyright note
** 24 Feb 1996 : 001.013 :  added PRTRSEL data direction signal
** 30 Dec 1995 : 001.012 :  + dynamic allocation of only one frame buffer
**                          PLIP_MAXMTU now 128k
**                          + a bad MTU setting in ENV: will be
**                          forced to PLIP_MAXMTU instead of PLIP_DEFMTU
**                          + server task acknowledge now after calling
**                          readargs to avoid inconsistencies
** 03 Sep 1995 : 001.011 :  hardware addressing nicer
** 30 Aug 1995 : 001.010 :  + support for timer-timed timeout :-)
**                          + minor declaration related changes
** 20 Aug 1995 : 001.009 :  support for ASM xfer routines
**                          removed obsolete CIA macros (mag/jk/mm)
** 29 Jul 1995 : 001.008 :  support for arbitration delay
**                          symmetrical handling
** 26 Apr 1995 : 001.007 :  _very_ nasty bug would miss packets and get
**                          the driver totally irritated
** 25 Apr 1995 : 001.006 :  now compiles with ANSI and STRICT
**                          fixed bug with resource allocation
** 08 Mar 1995 : 001.005 :  write req. are now handled by device.c
** 06 Mar 1995 : 001.004 :  collision delay added
** 06 Mar 1995 : 001.003 :  hardware transmission errors are no longer retried
**                          because this is any upper layers job
** 04 Mar 1995 : 001.002 :  event tracking *much* more conform to SANA-2
** 18 Feb 1995 : 001.001 :  startup now a bit nicer
**                          using BASEPTR
** 12 Feb 1995 : 001.000 :  reworked original
*/

#define DEBUG 0

/*F*/ /* includes */
#ifndef CLIB_EXEC_PROTOS_H
#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>
#endif
#ifndef CLIB_DOS_PROTOS_H
#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>
#endif
#ifndef CLIB_CIA_PROTOS_H
#include <clib/cia_protos.h>
#include <pragmas/cia_pragmas.h>
#endif
#ifndef CLIB_MISC_PROTOS_H
#include <clib/misc_protos.h>
#include <pragmas/misc_pragmas.h>
#endif
#ifndef CLIB_TIME_PROTOS_H
#include <clib/timer_protos.h>
#include <pragmas/timer_pragmas.h>
#endif
#ifndef CLIB_UTILITY_PROTOS_H
#include <clib/utility_protos.h>
#include <pragmas/utility_pragmas.h>
#endif

#ifndef EXEC_MEMORY_H
#include <exec/memory.h>
#endif
#ifndef EXEC_INTERRUPTS_H
#include <exec/interrupts.h>
#endif
#ifndef EXEC_DEVICES_H
#include <exec/devices.h>
#endif
#ifndef EXEC_IO_H
#include <exec/io.h>
#endif

#ifndef DEVICES_SANA2_H
#include <devices/sana2.h>
#endif

#ifndef HARDWARE_CIA_H
#include <hardware/cia.h>
#endif

#ifndef RESOURCES_MISC_H
#include <resources/misc.h>
#endif

#ifndef _STRING_H
#include <string.h>
#endif

#ifndef __MAGPLIP_H
#include "magplip.h"
#endif
#ifndef __DEBUG_H
#include "debug.h"
#endif
#ifndef __COMPILER_H
#include "compiler.h"
#endif
/*E*/

/*F*/ /* defines, types and enums */

   /*
   ** return codes for arbitratedwrite()
   */
typedef enum { AW_OK, AW_ABORTED, AW_BUFFER_ERROR, AW_ERROR } AW_RESULT;

   /* return val, cut to min or max if exceeding range */
#define BOUNDS(val, min, max) ((val) <= (max) ? ((val) >= (min) ? (val) :\
							 (min)) : (max))

/*E*/
/*F*/ /* imports */
   /* external functions */
GLOBAL VOID dotracktype(BASEPTR, ULONG type, ULONG ps, ULONG pr, ULONG bs, ULONG br, ULONG pd);
GLOBAL VOID DevTermIO(BASEPTR, struct IOSana2Req *ios2);
GLOBAL USHORT ASM CRC16(REG(a0) UBYTE *, REG(d0) SHORT);
GLOBAL BOOL ASM hwsend(REG(a0) BASEPTR);
GLOBAL BOOL ASM hwrecv(REG(a0) BASEPTR);
GLOBAL VOID ASM interrupt(REG(a1) BASEPTR);

   /* amiga.lib provides for these symbols */
GLOBAL FAR volatile struct CIA ciaa,ciab;
/*E*/
/*F*/ /* exports */
PUBLIC VOID SAVEDS ServerTask(void);
/*E*/
/*F*/ /* private */
PRIVATE struct PLIPBase *startup(void);
PRIVATE REGARGS VOID DoEvent(BASEPTR, long event);
PRIVATE VOID readargs(BASEPTR);
PRIVATE BOOL init(BASEPTR);
PRIVATE BOOL hwattach(BASEPTR);
PRIVATE VOID hwdetach(BASEPTR);
PRIVATE REGARGS BOOL goonline(BASEPTR);
PRIVATE REGARGS VOID gooffline(BASEPTR);
PRIVATE REGARGS AW_RESULT arbitratedwrite(BASEPTR, struct IOSana2Req *ios2);
PRIVATE REGARGS VOID dowritereqs(BASEPTR);
PRIVATE REGARGS VOID doreadreqs(BASEPTR);
PRIVATE REGARGS VOID dos2reqs(BASEPTR);
/*E*/

/*F*/ /* CIA access macros & functions */

#define CLEARINT        SetICR(CIAABase, CIAICRF_FLG)
#define DISABLEINT      AbleICR(CIAABase, CIAICRF_FLG)
#define ENABLEINT       AbleICR(CIAABase, CIAICRF_FLG | CIAICRF_SETCLR)

#ifdef LINPLIP

/* Most of these defines have no meaning at all, but it's safe ... */
#  define SETCIAOUTPUT
#  define SETCIAINPUT
#  define PARINIT(b)      ciaa.ciaddrb = 0xff; ciaa.ciaprb = 0x00

/* Good line : SELECT=POUT=PUSY=0 */
#  define TESTLINE(b)     ((ciab.ciapra&(CIAF_PRTRSEL|CIAF_PRTRPOUT|CIAF_PRTRBUSY)) || pb->pb_Flags&PLIPF_RECEIVING)
#  define SETREQUEST(b)
#  define CLEARREQUEST(b)

#else

#  define SETCIAOUTPUT    ciab.ciapra |= CIAF_PRTRSEL; ciaa.ciaddrb = 0xFF
#  define SETCIAINPUT     ciab.ciapra &= ~CIAF_PRTRSEL; ciaa.ciaddrb = 0x00
#  define PARINIT(b)      SETCIAINPUT;                                       \
			ciab.ciaddra &= ~((b)->pb_HandshakeMask[HS_LINE]); \
			ciab.ciaddra |= (b)->pb_HandshakeMask[HS_REQUEST]

#  define TESTLINE(b)     (ciab.ciapra & (b)->pb_HandshakeMask[HS_LINE])
#  define SETREQUEST(b)   ciab.ciapra |= (b)->pb_HandshakeMask[HS_REQUEST]
#  define CLEARREQUEST(b) ciab.ciapra &= ~((b)->pb_HandshakeMask[HS_REQUEST])

#endif

/*E*/

   /*
   ** functions to gain/release hardware, initialise communication
   ** and for xmission timeout handling
   */
/*F*/ PRIVATE BOOL hwattach(BASEPTR)
{
   BOOL rc = FALSE;

   d(("entered\n"));

   if (MiscBase = OpenResource("misc.resource"))
   {
      if (CIAABase = OpenResource("ciaa.resource"))
      {
	 CiaBase = CIAABase;

	 d(("ciabase is %lx\n",CiaBase));

	 /* obtain exclusive access to the parallel hardware */
	 if (!AllocMiscResource(MR_PARALLELPORT, pb->pb_DevNode.lib_Node.ln_Name))
	 {
	    pb->pb_AllocFlags |= 1;
	    if (!AllocMiscResource(MR_PARALLELBITS, pb->pb_DevNode.lib_Node.ln_Name))
	    {
	       pb->pb_AllocFlags |= 2;

	       /* Add our interrupt to handle CIAICRB_FLG.
	       ** This is also cia.resource means of granting exclusive
	       ** access to the related registers in the CIAs.
	       */
	       pb->pb_Interrupt.is_Node.ln_Type = NT_INTERRUPT;
	       pb->pb_Interrupt.is_Node.ln_Pri  = 127;
	       pb->pb_Interrupt.is_Node.ln_Name = SERVERTASKNAME;
	       pb->pb_Interrupt.is_Data         = (APTR)pb;
	       pb->pb_Interrupt.is_Code         = (VOID (*)())&interrupt;

	       /* We must Disable() bcos there could be an interrupt already
	       ** waiting for us. We may, however, not Able/SetICR() before
	       ** we have access!
	       */
	       Disable();
	       if (!AddICRVector(CIAABase, CIAICRB_FLG, &pb->pb_Interrupt))
	       {
		  DISABLEINT;                       /* this is what I meant */
		  rc = TRUE;
	       }
	       Enable();

	       if (rc)
	       {
		  pb->pb_AllocFlags |= 4;
		  PARINIT(pb);    /* cia to input, handshake in/out setting */
		  CLEARREQUEST(pb);                /* setup handshake lines */
		  CLEARINT;                         /* clear this interrupt */
		  ENABLEINT;                            /* allow interrupts */
	       }

	    }
	    else
	       d(("no parallelbits\n"));
	 }
	 else
	    d(("no parallelport\n"));
      }
      else
	 d(("no misc resource\n"));
   }
   else
      d(("no misc resource\n"));

   return rc;
}
/*E*/
/*F*/ PRIVATE VOID hwdetach(BASEPTR)
{
   if (pb->pb_AllocFlags & 4)
   {
      DISABLEINT;
      CLEARINT;
      RemICRVector(CIAABase, CIAICRB_FLG, &pb->pb_Interrupt);
   }

   if (pb->pb_AllocFlags & 2) FreeMiscResource(MR_PARALLELBITS);

   if (pb->pb_AllocFlags & 1) FreeMiscResource(MR_PARALLELPORT);

   pb->pb_AllocFlags = 0;
}
/*E*/
/*F*/ PRIVATE ULONG ASM SAVEDS exceptcode(REG(d0) ULONG sigmask, REG(a1) BASEPTR)
{
   /*extern void KPrintF(char *,...);
   KPrintF("exceptcode\n");*/

   /* remove the I/O Block from the port */
   WaitIO((struct IORequest*)&pb->pb_TimeoutReq);

   /* this tells the xfer routines to cease polling */
   pb->pb_TimeoutSet = 0xff;

   return sigmask;            /* re-enable the signal */
}
/*E*/

   /*
   ** functions to go online/offline
   */
/*F*/ PRIVATE REGARGS VOID rejectpackets(BASEPTR)
{
   struct IOSana2Req *ios2;

   ObtainSemaphore(&pb->pb_WriteListSem);
   while(ios2 = (struct IOSana2Req *)RemHead((struct List*)&pb->pb_WriteList))
   {
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      DevTermIO(pb,ios2);
   }
   ReleaseSemaphore(&pb->pb_WriteListSem);

   ObtainSemaphore(&pb->pb_ReadListSem);
   while(ios2 = (struct IOSana2Req *)RemHead((struct List*)&pb->pb_ReadList))
   {
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      DevTermIO(pb,ios2);
   }
   ReleaseSemaphore(&pb->pb_ReadListSem);

   ObtainSemaphore(&pb->pb_ReadOrphanListSem);
   while(ios2 = (struct IOSana2Req *)RemHead((struct List*)&pb->pb_ReadOrphanList))
   {
      ios2->ios2_Req.io_Error = S2ERR_OUTOFSERVICE;
      ios2->ios2_WireError = S2WERR_UNIT_OFFLINE;
      DevTermIO(pb,ios2);
   }
   ReleaseSemaphore(&pb->pb_ReadOrphanListSem);
}
/*E*/
/*F*/ PRIVATE REGARGS BOOL goonline(BASEPTR)
{
   BOOL rc = TRUE;

   d(("trying to go online\n"));

   if (pb->pb_Flags & (PLIPF_OFFLINE | PLIPF_NOTCONFIGURED))
   {
      if (!hwattach(pb))
      {
	 d(("error going online\n"));
	 rc = FALSE;
      }
      else
      {
	 GetSysTime(&pb->pb_DevStats.LastStart);
	 pb->pb_Flags &= ~(PLIPF_OFFLINE | PLIPF_NOTCONFIGURED);
	 DoEvent(pb, S2EVENT_ONLINE);
	 d(("i'm now online!\n"));
      }
   }

   return rc;
}
/*E*/
/*F*/ PRIVATE REGARGS VOID gooffline(BASEPTR)
{
   if (!(pb->pb_Flags & (PLIPF_OFFLINE | PLIPF_NOTCONFIGURED)))
   {
      hwdetach(pb);

      pb->pb_Flags |= PLIPF_OFFLINE;

      DoEvent(pb, S2EVENT_OFFLINE);
   }
   d(("ok!\n"));
}
/*E*/

   /*
   ** SANA-2 Event management
   */
/*F*/ PRIVATE REGARGS VOID DoEvent(BASEPTR, long event)
{
   struct IOSana2Req *ior, *ior2;

   d(("event is %lx\n",event));

   ObtainSemaphore(&pb->pb_EventListSem );
   
   for(ior = (struct IOSana2Req *) pb->pb_EventList.lh_Head;
       ior2 = (struct IOSana2Req *) ior->ios2_Req.io_Message.mn_Node.ln_Succ;
       ior = ior2 )
   {
      if (ior->ios2_WireError & event)
      {
	 Remove((struct Node*)ior);
	 DevTermIO(pb, ior);
      }
   }
   
   ReleaseSemaphore(&pb->pb_EventListSem );
}
/*E*/

   /*
   ** writing packets
   */
/*F*/ PRIVATE REGARGS AW_RESULT arbitratedwrite(BASEPTR, struct IOSana2Req *ios2)
{
   BOOL having_line;
   AW_RESULT rc;
   struct PLIPFrame *frame = pb->pb_Frame;

   /*
   ** Arbitration
   ** ===========================================================
   **
   ** Pseudo code of the arbitration:
   **
   **  if LINE is high (other side is ready to receive) then
   **     set REQUEST high (tell the other side we're ready to send)
   **     if LINE is high (other side is still ready to receive) then
   **        we have the line, do transfer
   **     reset REQUEST to low (tell other side we're ready to receive)
   **
   **    AW_OK             if we could transmit all the data correctly
   **    AW_BUFF_ERROR     if the BufferManagement callback failed
   **    AW_ERROR          if we got the line, but the actual transfer
   **                      failed, perhaps due to a timeout
   **    AW_ABORT          if we couldn't get the line
   */

#ifdef LINPLIP
   having_line = !TESTLINE(pb);
   if(!having_line)
   {
	 if (!(pb->pb_Flags & PLIPF_RECEIVING))
	    CLEARREQUEST(pb);                         /* reset line state */
	 d2(("couldn't get the line\n"));
   }
#else
   having_line = FALSE;

   if (!TESTLINE(pb))                               /* is the line free ? */
   {
      SETREQUEST(pb);                     /* indicate our request to send */
      
#if 0
   /*
   ** I have removed again the ARBITRATIONDELAY feature, although I am not
   ** really sure if this is a good thing. Anyway I didn't experience
   ** any more those nasty line errors, that initially made me implementing
   ** this. For now I've left the code here to let you play with it. Please
   ** report any comments concerning this.
   **
   ** In fact the arbitration leaves a small door for undetected, real
   ** collisision, as the request lines are used for handshake during the
   ** transmission process. The CLEARREQUEST after NOT getting the line
   ** could be misinterpreted by the other side as the first handshake. Up
   ** to now I couldn't conceive a satisfying solution for this.
   */

      if (pb->pb_ArbitrationDelay > 0)
      {
	 pb->pb_CollReq.tr_time.tv_secs    = 0;
	 pb->pb_CollReq.tr_time.tv_micro   = pb->pb_ArbitrationDelay;
	 pb->pb_CollReq.tr_node.io_Command = TR_ADDREQUEST;
	 DoIO((struct IORequest*)&pb->pb_CollReq);
      }
#endif

      if (!TESTLINE(pb))                      /* is the line still free ? */
	 having_line = TRUE;
      else
      {
	 if (!(pb->pb_Flags & PLIPF_RECEIVING))
	    CLEARREQUEST(pb);                         /* reset line state */
	 d2(("couldn't get the line-1\n"));
      }
   }
   else d2(("couldn't get the line-2\n"));
#endif /* LINPLIP */

   if (having_line)
   {
      struct BufferManagement *bm;

      if (!(pb->pb_Flags & PLIPF_RECEIVING))
      {
	 d(("having line for: type %08lx, size %ld\n",ios2->ios2_PacketType,
						      ios2->ios2_DataLength));

	 frame->pf_Type = ios2->ios2_PacketType;
	 frame->pf_Size = ios2->ios2_DataLength + PKTFRAMESIZE_2;

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

	 if (!(*bm->bm_CopyFromBuffer)((UBYTE*)(frame+1),
				     ios2->ios2_Data, ios2->ios2_DataLength))
	 {
	    rc = AW_BUFFER_ERROR;
	    CLEARREQUEST(pb);                         /* reset line state */
	 }
	 else
	 {
	    /* wait until I/O block is safe to be reused */
	    while(!pb->pb_TimeoutSet) Delay(1L);
	    pb->pb_TimeoutReq.tr_time.tv_secs = 0;
	    pb->pb_TimeoutReq.tr_time.tv_micro = pb->pb_Timeout;
	    pb->pb_TimeoutSet = 0;
	    SendIO((struct IORequest*)&pb->pb_TimeoutReq);
	    rc = hwsend(pb) ? AW_OK : AW_ERROR;
	    AbortIO((struct IORequest*)&pb->pb_TimeoutReq);
#if DEBUG&8
	    if(rc==AW_ERROR) d8(("Error sending packet (size=%ld)\n", (LONG)pb->pb_Frame->pf_Size));
#endif
	 }
      }
      else
      {
	 d4(("arbitration error!\n"));
	 rc = AW_ABORTED;
      }
   }
   else
      rc = AW_ABORTED;

   return rc;
}
/*E*/
/*F*/ PRIVATE REGARGS VOID dowritereqs(BASEPTR)
{
   struct IOSana2Req *currentwrite, *nextwrite;
   AW_RESULT code;

   ObtainSemaphore(&pb->pb_WriteListSem);

   for(currentwrite = (struct IOSana2Req *)pb->pb_WriteList.lh_Head;
       nextwrite = (struct IOSana2Req *) currentwrite->ios2_Req.io_Message.mn_Node.ln_Succ;
       currentwrite = nextwrite )
   {
      if (pb->pb_Flags & PLIPF_RECEIVING)
      {
	 d(("incoming data!"));
	 break;
      }

      code = arbitratedwrite(pb, currentwrite);

      if (code == AW_ABORTED)                         /* arbitration failed */
      {
	 pb->pb_Flags |= PLIPF_COLLISION;
	 d(("couldn't get the line, trying again later\n"));
	 pb->pb_SpecialStats[S2SS_COLLISIONS].Count++;
	 d(("pb->pb_SpecialStats[S2SS_COLLISIONS].Count = %ld\n",pb->pb_SpecialStats[S2SS_COLLISIONS].Count));
	 if ((currentwrite->ios2_Req.io_Error++) > pb->pb_Retries)
	 {
	    pb->pb_SpecialStats[S2SS_TXERRORS].Count++;
	    d(("pb->pb_SpecialStats[S2SS_TXERRORS].Count = %ld\n",pb->pb_SpecialStats[S2SS_TXERRORS].Count));
	    currentwrite->ios2_Req.io_Error = S2ERR_TX_FAILURE;
	    currentwrite->ios2_WireError = S2WERR_TOO_MANY_RETIRES;
	    Remove((struct Node*)currentwrite);
	    DevTermIO(pb, currentwrite);
	 }
	 break;
      }
      else if (code == AW_BUFFER_ERROR)  /* BufferManagement callback error */
      {
	 d(("buffer error\n"));
	 DoEvent(pb, S2EVENT_ERROR | S2EVENT_BUFF | S2EVENT_SOFTWARE);
	 pb->pb_SpecialStats[S2SS_TXERRORS].Count++;
	 d(("pb->pb_SpecialStats[S2SS_TXERRORS].Count = %ld\n",pb->pb_SpecialStats[S2SS_TXERRORS].Count));
	 currentwrite->ios2_Req.io_Error = S2ERR_SOFTWARE;
	 currentwrite->ios2_WireError = S2WERR_BUFF_ERROR;
	 Remove((struct Node*)currentwrite);
	 DevTermIO(pb, currentwrite);
      }
      else if (code == AW_ERROR)
      {
	 /*
	 ** this is a real line error, upper levels (e.g. Internet TCP) have
	 ** to care for reliability!
	 */
	 d(("error while transmitting packet\n"));
	 DoEvent(pb, S2EVENT_ERROR | S2EVENT_TX | S2EVENT_HARDWARE);
	 pb->pb_SpecialStats[S2SS_TXERRORS].Count++;
	 d(("pb->pb_SpecialStats[S2SS_TXERRORS].Count = %ld\n",pb->pb_SpecialStats[S2SS_TXERRORS].Count));
	 currentwrite->ios2_Req.io_Error = S2ERR_TX_FAILURE;
	 currentwrite->ios2_WireError = S2WERR_GENERIC_ERROR;
	 Remove((struct Node*)currentwrite);
	 DevTermIO(pb, currentwrite);
      }
      else /*if (code == AW_OK)*/                             /* well done! */
      {
	 d(("packet transmitted successfully\n"));
	 pb->pb_DevStats.PacketsSent++;
	 dotracktype(pb, (ULONG) pb->pb_Frame->pf_Type, 1, 0, currentwrite->ios2_DataLength, 0, 0);
	 currentwrite->ios2_Req.io_Error = S2ERR_NO_ERROR;
	 currentwrite->ios2_WireError = S2WERR_GENERIC_ERROR;
	 Remove((struct Node*)currentwrite);
	 DevTermIO(pb, currentwrite);
      }
   }

   ReleaseSemaphore(&pb->pb_WriteListSem);
}
/*E*/

   /*
   ** reading packets
   */
/*F*/ PRIVATE REGARGS VOID doreadreqs(BASEPTR)
{
   LONG datasize;
   struct IOSana2Req *got;
   ULONG pkttyp;
   struct BufferManagement *bm;
   BOOL rv;
   struct PLIPFrame *frame = pb->pb_Frame;

   /* wait until I/O block is safe to be reused */
   while(!pb->pb_TimeoutSet) Delay(1L);
   pb->pb_TimeoutReq.tr_time.tv_secs    = 0;
   pb->pb_TimeoutReq.tr_time.tv_micro   = pb->pb_Timeout;
   pb->pb_TimeoutSet = 0;
   SendIO((struct IORequest*)&pb->pb_TimeoutReq);
   rv = hwrecv(pb);
   AbortIO((struct IORequest*)&pb->pb_TimeoutReq);

   if (rv)
   {
      pb->pb_DevStats.PacketsReceived++;

      datasize = frame->pf_Size - PKTFRAMESIZE_2;

      dotracktype(pb, pkttyp = frame->pf_Type, 0, 1, 0, datasize, 0);

      d(("packet %08lx, size %ld received\n",pkttyp,datasize));

      ObtainSemaphore(&pb->pb_ReadListSem);

	 /* traverse the list of read-requests */
      for(got = (struct IOSana2Req *)pb->pb_ReadList.lh_Head;
	  got->ios2_Req.io_Message.mn_Node.ln_Succ;
	  got = (struct IOSana2Req *)got->ios2_Req.io_Message.mn_Node.ln_Succ )
      {
	    /* check if this one requests for the new packet we got */
	 if (got->ios2_PacketType == pkttyp )
	 {
	    Remove((struct Node*)got);

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

	    if (!(*bm->bm_CopyToBuffer)(got->ios2_Data, (UBYTE*)(frame+1), datasize))
	    {
	       d(("CopyToBuffer: error\n"));
	       got->ios2_Req.io_Error = S2ERR_SOFTWARE;
	       got->ios2_WireError = S2WERR_BUFF_ERROR;
	       DoEvent(pb, S2EVENT_ERROR | S2EVENT_BUFF | S2EVENT_SOFTWARE);
	    }
	    else
	    {
	       got->ios2_Req.io_Error = got->ios2_WireError = 0;
	    }

	    got->ios2_Req.io_Flags = 0;
#ifndef LINPLIP
	    memcpy(got->ios2_SrcAddr, pb->pb_SrcAddr, PLIP_ADDRFIELDSIZE);
	    memcpy(got->ios2_DstAddr, pb->pb_DstAddr, PLIP_ADDRFIELDSIZE);
#endif
	    got->ios2_DataLength = datasize;

	    d(("packet received, satisfying S2Request\n"));
	    DevTermIO(pb, got);
	    got = NULL;
	    break;
	 }
      }

      ReleaseSemaphore(&pb->pb_ReadListSem);
   }
   else
   {
      d8(("Error receiving (%ld. len=%ld)\n", rv, frame->pf_Size));
      /* something went wrong during receipt */
      DoEvent(pb, S2EVENT_HARDWARE | S2EVENT_ERROR | S2EVENT_RX);
      got = NULL;
      pb->pb_DevStats.BadData++;
   }

      /* If no one wanted this packet explicitely, there is one chance
      ** left: somebody waiting for orphaned packets. If this fails, too,
      ** we will drop it.
      */
   if (got)
   {
      d(("unknown packet\n"));

      pb->pb_DevStats.UnknownTypesReceived++;
      
      ObtainSemaphore(&pb->pb_ReadOrphanListSem);
      got = (struct IOSana2Req *)RemHead((struct List*)&pb->pb_ReadOrphanList);
      ReleaseSemaphore(&pb->pb_ReadOrphanListSem);

      if (got)
      {
	 bm = (struct BufferManagement *)got->ios2_BufferManagement;
	 if (!(*bm->bm_CopyToBuffer)(got->ios2_Data, (UBYTE*)(frame+1), datasize))
	 {
	    got->ios2_Req.io_Error = S2ERR_SOFTWARE;
	    got->ios2_WireError = S2WERR_BUFF_ERROR;
	 }
	 else
	 {
	    got->ios2_Req.io_Error = got->ios2_WireError = 0;
	 }
	 
	 got->ios2_Req.io_Flags = 0;
#ifndef LINPLIP
	 memcpy(got->ios2_SrcAddr, pb->pb_SrcAddr, PLIP_ADDRFIELDSIZE);
	 memcpy(got->ios2_DstAddr, pb->pb_DstAddr, PLIP_ADDRFIELDSIZE);
#endif
	 got->ios2_DataLength = datasize;

	 d(("orphan read\n"));

	 DevTermIO(pb, got);
      }
      else
      {
	 dotracktype(pb, pkttyp, 0, 0, 0, 0, 1);
	 d(("packet thrown away...\n"));
      }
   }
}
/*E*/

   /*
   ** 2nd level device command dispatcher (~SANA2IOF_QUICK)
   */
/*F*/ PRIVATE REGARGS VOID dos2reqs(BASEPTR)
{
   struct IOSana2Req *ios2;

   /*
   ** Every pending IO message will be GetMsg()'ed and processed. At the
   ** end of the loop it will be DevTermIO()'ed back to the sender,
   ** _but_only_if_ it is non-NULL. In such cases the message has been
   ** put in a separate queue to be DevTermIO()'ed later (i.e. CMD_WRITEs
   ** and similar stuff).
   ** You find the same mimique in the 1st level dispatcher (device.c)
   */
   while(ios2 = (struct IOSana2Req *)GetMsg(pb->pb_ServerPort))
   {
      if (pb->pb_Flags & PLIPF_RECEIVING)
      {
	 d(("incoming data!"));
	 break;
      }

      d(("sana2req %ld from serverport\n", ios2->ios2_Req.io_Command));

      switch (ios2->ios2_Req.io_Command)
      {
	 case S2_ONLINE:
	    if (!goonline(pb))
	    {
	       ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
	       ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
	    }
	 break;

	 case S2_OFFLINE:
	    gooffline(pb);
	    rejectpackets(pb); /* reject all pending requests */
	 break;

	 case S2_CONFIGINTERFACE:
	    if (pb->pb_Flags & PLIPF_NOTCONFIGURED)
	    {
#ifndef LINPLIP
	       memcpy(ios2->ios2_SrcAddr, pb->pb_SrcAddr, PLIP_ADDRFIELDSIZE);
	       memcpy(ios2->ios2_DstAddr, pb->pb_DstAddr, PLIP_ADDRFIELDSIZE);
#endif
	       if (!goonline(pb))
	       {
		  ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
		  ios2->ios2_WireError = S2WERR_GENERIC_ERROR;
	       }
	    }
	    else
	    {
	       ios2->ios2_Req.io_Error = S2ERR_BAD_STATE;
	       ios2->ios2_WireError = S2WERR_IS_CONFIGURED;
	    }
	 break;
      }

      if (ios2) DevTermIO(pb,ios2);
   }
}
/*E*/

   /*
   ** startup,initialisation and termination functions
   */
/*F*/ PRIVATE struct PLIPBase *startup(void)
{
   struct ServerStartup *ss;
   struct Process *we;
   struct PLIPBase *base;
   LOCALSYSBASE;

   we = (struct Process*)FindTask(NULL);

   d(("waiting for startup msg...\n"));
   WaitPort(&we->pr_MsgPort);
   ss = (struct ServerStartup *)GetMsg(&we->pr_MsgPort);
   base = ss->ss_PLIPBase;
   base->pb_Startup = ss;
   d(("go startup msg at %lx, PLIPBase is %lx\n", ss, ss->ss_PLIPBase));

   /* we will keep the startup message, to inform mother if we
   ** really could come up or if we failed to obtain some
   ** resource.
   */
   return base;
}
/*E*/
/*F*/ PRIVATE VOID readargs(BASEPTR)
{
   struct RDArgs *rda;
   struct PLIPConfig args = { 0 };
   BPTR plipvar, oldinput;

   d(("entered\n"));

   if (plipvar = Open(CONFIGFILE, MODE_OLDFILE))
   {
      oldinput = SelectInput(plipvar);
      
      rda = ReadArgs(TEMPLATE , (LONG *)&args, NULL);
      
      if(rda)
      {
	 if (args.timeout)
	    pb->pb_Timeout =
		  BOUNDS(*args.timeout, PLIP_MINTIMEOUT, PLIP_MAXTIMEOUT);

	 if (args.priority)
	    SetTaskPri((struct Task*)pb->pb_Server,
		  BOUNDS(*args.priority, PLIP_MINPRIORITY, PLIP_MAXPRIORITY));

	 if (args.mtu)
	    pb->pb_MTU = BOUNDS(*args.mtu, PLIP_MINMTU, PLIP_MAXMTU);

	 if (args.bps)
	    pb->pb_ReportBPS = BOUNDS(*args.bps, PLIP_MINBPS, PLIP_MAXBPS);

	 if (args.retries)
	    pb->pb_Retries =
		     BOUNDS(*args.retries, PLIP_MINRETRIES, PLIP_MAXRETRIES);

	 if (args.sendcrc)
	    pb->pb_Flags |= PLIPF_SENDCRC;
	  else
	    pb->pb_Flags &= ~PLIPF_SENDCRC;

	 if (args.collisiondelay)
	    pb->pb_CollisionDelay =
	       BOUNDS(*args.collisiondelay, PLIP_MINCOLLISIONDELAY,
					    PLIP_MAXCOLLISIONDELAY);
	 else
	    pb->pb_CollisionDelay = PLIP_DEFDELAY + (pb->pb_Unit ?
						  PLIP_DELAYDIFF : 0);

	 if (args.arbitrationdelay)
	    pb->pb_ArbitrationDelay =
	       BOUNDS(*args.collisiondelay, PLIP_MINARBITRATIONDELAY,
					    PLIP_MAXARBITRATIONDELAY);
	 else
	    pb->pb_ArbitrationDelay = PLIP_DEFARBITRATIONDELAY;

	 if (args.nospecialstats)
	    pb->pb_ExtFlags |= PLIPEF_NOSPECIALSTATS;

	 FreeArgs(rda);
      }

      Close(SelectInput(oldinput));
   }

   d(("timeout %ld, pri %ld, mtu %ld, bps %ld, retries %ld, flags %08lx, delay %ld\n",
      pb->pb_Timeout, (LONG)pb->pb_Server->pr_Task.tc_Node.ln_Pri, pb->pb_MTU, pb->pb_ReportBPS, pb->pb_Retries,
      pb->pb_Flags, pb->pb_CollisionDelay));

   d(("left\n"));

}
/*E*/
/*F*/ PRIVATE BOOL init(BASEPTR)
{
   BOOL rc = FALSE;
   ULONG sigmask;

   if ((pb->pb_IntSig = AllocSignal(-1)) != -1)
   {
      pb->pb_IntSigMask = 1L << pb->pb_IntSig;
   
      if ((pb->pb_ServerPort = CreateMsgPort()))
      {
	 if ((pb->pb_CollPort = CreateMsgPort()))
	 {
	    if ((pb->pb_TimeoutPort = CreateMsgPort()))
	    {
	       /* save old exception setup */
	       pb->pb_OldExcept = SetExcept(0, 0xffffffff); /* turn'em off */
	       pb->pb_OldExceptCode = pb->pb_Server->pr_Task.tc_ExceptCode;
	       pb->pb_OldExceptData = pb->pb_Server->pr_Task.tc_ExceptData;

	       /* create new exception setup */
	       pb->pb_Server->pr_Task.tc_ExceptCode = (APTR)&exceptcode;
	       pb->pb_Server->pr_Task.tc_ExceptData = (APTR)pb;
	       SetSignal(0, sigmask = (1 << pb->pb_TimeoutPort->mp_SigBit));
	       SetExcept(sigmask, sigmask);

	       /* enter port address */
	       pb->pb_CollReq.tr_node.io_Message.mn_ReplyPort = pb->pb_CollPort;
	       if (!OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest*)&pb->pb_CollReq, 0))
	       {
		  TimerBase = (struct Library *)pb->pb_CollReq.tr_node.io_Device;

		  /* preset the io command, this will never change */
		  pb->pb_CollReq.tr_node.io_Command = TR_ADDREQUEST;

		  /* setup the timeout stuff */
		  pb->pb_TimeoutReq.tr_node.io_Flags = IOF_QUICK;
		  pb->pb_TimeoutReq.tr_node.io_Message.mn_ReplyPort = pb->pb_TimeoutPort;
		  pb->pb_TimeoutReq.tr_node.io_Device = pb->pb_CollReq.tr_node.io_Device;
		  pb->pb_TimeoutReq.tr_node.io_Unit = pb->pb_CollReq.tr_node.io_Unit;
		  pb->pb_TimeoutReq.tr_node.io_Command = TR_ADDREQUEST;
		  pb->pb_TimeoutSet = 0xff;

		  readargs(pb);
		  d(("allocating 0x%lx/%ld bytes frame buffer\n",
				       sizeof(struct PLIPFrame)+pb->pb_MTU,
				       sizeof(struct PLIPFrame)+pb->pb_MTU));
		  if ((pb->pb_Frame = AllocVec((ULONG)sizeof(struct PLIPFrame) +
						  pb->pb_MTU, MEMF_CLEAR|MEMF_ANY)))
		  {
		     rc = TRUE;
		  }
		  else
		  {
		     d(("couldn't allocate frame buffer\n"));
		  }
	       }
	       else
	       {
		  d(("couldn't open timer.device"));
	       }
	    }
	    else
	    {
	       d(("no port for timeout handling\n"));
	    }
	 }
	 else
	 {
	    d(("no port for collision handling\n"));
	 }
      }
      else
      {
	 d(("no server port\n"));
      }
   }
   else
   {
      d(("no signal\n",rc));
   }

   d(("left %ld\n",rc));

   return rc;
}
/*E*/
/*F*/ PRIVATE VOID cleanup(BASEPTR)
{
   struct BufferManagement *bm;

   gooffline(pb);

   while(bm = (struct BufferManagement *)RemHead((struct List *)&pb->pb_BufferManagement))
      FreeVec(bm);

   if (pb->pb_Frame) FreeVec(pb->pb_Frame);

   if (pb->pb_TimeoutPort)
   {
      /* restore old exception setup */
      SetExcept(0, 0xffffffff);    /* turn'em off */
      pb->pb_Server->pr_Task.tc_ExceptCode = pb->pb_OldExceptCode;
      pb->pb_Server->pr_Task.tc_ExceptData = pb->pb_OldExceptData;
      SetExcept(pb->pb_OldExcept, 0xffffffff);

      if (TimerBase)
      {
	 WaitIO((struct IORequest*)&pb->pb_TimeoutReq);
	 CloseDevice((struct IORequest*)&pb->pb_CollReq);
      }
      DeleteMsgPort(pb->pb_TimeoutPort);
   }
   if (pb->pb_CollPort) DeleteMsgPort(pb->pb_CollPort);

   if (pb->pb_ServerPort) DeleteMsgPort(pb->pb_ServerPort);
   if (pb->pb_IntSig != -1) FreeSignal(pb->pb_IntSig);

   if (pb->pb_Flags & PLIPF_REPLYSS)
   {
      Forbid();
      ReplyMsg((struct Message*)pb->pb_Startup);
   }
}
/*E*/

   /*
   ** entry point, mainloop
   */
/*F*/ PUBLIC VOID SAVEDS ServerTask(void)
{
   BASEPTR;

   d(("server running\n"));

   if (pb = startup())
   {
	 /* if we fail to allocate all resources, this flag reminds cleanup()
	 ** to ReplyMsg() the startup message
	 */
      pb->pb_Flags |= PLIPF_REPLYSS;

      if (init(pb))
      {
	 ULONG recv=0, portsigmask, timersigmask, wmask;
	 BOOL running, timerqueued = FALSE;

	 /* Ok, we are fine and will tell this mother personally :-) */
	 pb->pb_Startup->ss_Error = 0;
	 /* don't forget this, or we will have to keep a warm place */
	 /* in our coffin for the system */
	 pb->pb_Flags &= ~PLIPF_REPLYSS;
	 ReplyMsg((struct Message*)pb->pb_Startup);

	 portsigmask  = 1 << pb->pb_ServerPort->mp_SigBit;
	 timersigmask = 1 << pb->pb_CollPort->mp_SigBit;
      
	 wmask = SIGBREAKF_CTRL_F | SIGBREAKF_CTRL_C | pb->pb_IntSigMask | portsigmask | timersigmask;

	 for(running=TRUE;running;)
	 {
	    d(("wmask is 0x%08lx\n", wmask));

	    if (!(pb->pb_Flags & PLIPF_RECEIVING))
	       recv = Wait(wmask);
	    else
	       SetSignal(0, pb->pb_IntSigMask);

	    /*if (recv & pb->pb_IntSigMask)*/
	    if (pb->pb_Flags & PLIPF_RECEIVING)
	    {
	       d(("received an interrupt\n"));
	       doreadreqs(pb);
	    }

	    if (recv & portsigmask)
	    {
	       d(("SANA-II request(s)\n"));
	       dos2reqs(pb);
	    }

	    if (recv & timersigmask)
	    {
	       /* pop message */
	       AbortIO((struct IORequest*)&pb->pb_CollReq);
	       WaitIO((struct IORequest*)&pb->pb_CollReq);
	       timerqueued = FALSE;
	       d(("timer wakeup\n"));
	    }

	       /* try now to do write requests (if any pending) */
	    if (!timerqueued)
	    {
	       dowritereqs(pb);

		  /* don't let the other side wait too long! */
	       if (pb->pb_Flags & PLIPF_RECEIVING)
	       {
		  d(("received an interrupt\n"));
		  SetSignal(0, pb->pb_IntSigMask);
		  doreadreqs(pb);
	       }

	       /*
	       ** Possible a collision has occurred, which is indicated by a
	       ** special flag in PLIPBase.
	       **
	       ** Using timer.device we periodically will be waked up. This
	       ** allows us to delay write packets in cases when we cannot get
	       ** the line immediately.
	       **
	       ** If client and server are very close together, regarding the point
	       ** of performance, the same delay time could even force multiple
	       ** collisions (at least theoretical, I made no practical tests).
	       ** Probably a CSMA/CD-like random-timed delay would be ideal.
	       */
	       if (pb->pb_Flags & PLIPF_COLLISION)
	       {
		  pb->pb_Flags &= ~PLIPF_COLLISION;
		  pb->pb_CollReq.tr_time.tv_secs    = 0;
		  pb->pb_CollReq.tr_time.tv_micro   = pb->pb_CollisionDelay;
		  SendIO((struct IORequest*)&pb->pb_CollReq);
		  timerqueued = TRUE;
	       }
	    }

	    if (recv & SIGBREAKF_CTRL_C)
	    {
	       d(("received break signal\n"));
	       running = FALSE;
	    }
	 }

	 if (timerqueued)
	 {
	       /* finnish pending timer requests */
	    AbortIO((struct IORequest*)&pb->pb_CollReq);
	    WaitIO((struct IORequest*)&pb->pb_CollReq);
	 }
      }
      else
	 d(("init() failed\n"));

      d(("cleaning up\n"));
      cleanup(pb);

	    /* Exec will enable it's scheduler after we're dead. */
      Forbid();
	    /* signal mother we're done */
      if (pb->pb_ServerStoppedSigMask)
	 Signal(pb->pb_Task, pb->pb_ServerStoppedSigMask);
      pb->pb_Flags |= PLIPF_SERVERSTOPPED;
   }
   else
      d(("no startup packet\n"));
}
/*E*/

