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

#include "device_protos.h"

/* Our device name */
const char SPARName[] = SPARDEVNAME;

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

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

    /* Enforce single threading since we may need to Wait() when starting
     * up the Unit process. If somebody decides to DoExpunge() before we
     * get the semaphore, system is probably blowing up anyways.
     */
    ObtainSemaphore(&SPARDevice->sd_Lock); 

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

    if(s2unit < SD_MAXUNITS)               /* Legal Unit number? */
    {
      initsyslog();

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

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

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

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

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

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

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

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

    return(returncode);
}

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

BPTR ASM DevClose(REG(a1) struct IOSana2Req *ios2,
                  REG(a6) struct SPARDevice *SPARDevice)
{
    struct SPARDevUnit *sdu;
    BPTR seglist = NULL;

    ObtainSemaphore(&SPARDevice->sd_Lock);

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

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

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

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

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

    SPARDevice->sd_Device.lib_OpenCnt--;

    ReleaseSemaphore(&SPARDevice->sd_Lock);

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

    return(seglist);
}


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

BPTR ASM DevExpunge(REG(a6) struct SPARDevice *SPARDevice)
{
    BPTR seglist;
    ULONG devbase;
    LONG devbasesize;

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

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

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

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

      uninitsyslog();

      FreeMem((APTR)devbase,devbasesize);
      ExtDeviceBase = NULL; /* ! */
    }
    return(seglist);
}

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

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

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

    if(!SPARDevice->sd_Units[s2unit])
    {
      /* Open up dos.library */

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

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

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

                        NPTags[0].ti_Data = (ULONG) &DevProcCEntry; /* Assembly entry point for the unit process. */
                                                                    /* Was DevProcEntry in Spar_device.asm */
                        NPTags[1].ti_Data = (ULONG) SPARName;       /* Process name */

                        /* Rhialto: use opener's priority */
                        NPTags[2].ti_Data = (ULONG) FindTask(NULL)->tc_Node.ln_Pri;

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

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

            debug(("InitSPARUnit: opened unit: %d\n",s2unit))

            CloseLibrary(SPARDevice->sd_DOSBase);
        }

    }
    debug(("InitSPARUnit: return(%d)\n",SPARDevice->sd_Units[s2unit]))

    return((struct SPARDevUnit *)SPARDevice->sd_Units[s2unit]);
}

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

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

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

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

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

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

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

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

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

VOID ASM DevBeginIO(REG(a1) struct IOSana2Req *ios2,
                    REG(a6) struct SPARDevice *SPARDevice)
{

    /* set node type: request pending */
    ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE;

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

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

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

      /* We may need to turn the active bit off, when IO came not from the
       * task */
      if (!(sdu->sdu_Unit.unit_flags & UNITB_INTASK))
      {
        /* The task does not have more work to do */
        sdu->sdu_Unit.unit_flags &= ~UNITB_ACTIVE;
      }

      /*
       * Code added from the messydisk.device by Rhialto:
       *
       * The task may have work to do that came in while we were processing
       * in the caller's context.
       */
       if (sdu->sdu_Unit.unit_flags & UNITF_WAKETASK)
       {  sdu->sdu_Unit.unit_flags &= ~UNITF_WAKETASK;
          WakePort(&sdu->sdu_Unit.unit_MsgPort);
       }

   }
#endif

   /* If the quick bit is still set then we don't need to reply
    * msg, just return to the user. */

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

/*
** The device AbortIO() entry point.
**
** A1 - The IO request to be aborted.
** A3 - The unit pointer (NOT!)
** A6 - The device base.
*/
LONG ASM DevAbortIO(REG(a1) struct IOSana2Req *ios2,
                    REG(a6) struct SPARDevice *SPARDevice)
{
    struct SPARDevUnit *sdu = (struct SPARDevUnit *)ios2->ios2_Req.io_Unit;
    LONG result = NULL;

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

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

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

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

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


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

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

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

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

/*
** This routine is called whenever a CMD_READ request
** returns from the PARnet driver.
*/
VOID DoPARnet(struct SPARDevUnit *sdu, struct IOParReq *IOPar)
{
    /* Check if received data is valid in size */

    /* Not to small? */
    if (IOPar->io_Actual < SHDR_LEN) ReceivedGarbage(sdu);
    else
    {
       IOPar->io_Length=IOPar->io_Actual;

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

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

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

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

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

    WaitPort(&proc->pr_MsgPort);

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

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

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

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

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

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

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

    ReplyMsg((struct Message *)sm);

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

      /* Loop...*/

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

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

#ifdef HANDLE_IO_QUICK
	/*
	 * Lock the device. If it fails, we have set a flag such that the
	 * TermIO wakes us again.
	 */
	sdu->sdu_Unit.unit_flags |= UNITF_WAKETASK;
	if (BSET_ACTIVE(&sdu->sdu_Unit.unit_flags))
	    continue;

	sdu->sdu_Unit.unit_flags |= UNITF_INTASK;
#endif

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

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

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

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

                /* Handle message from the receiver */
                if(iopar = (struct IOParReq *)GetMsg(sdu->sdu_RxPort))
                    if(sdu->sdu_State & SPARUF_ONLINE)
                    {
                      sdu->sdu_NoMore = TRUE; /* there might be more? */
                      DoPARnet(sdu, iopar);
                    }

                /* Handle message from the transmitter */
                if(iopar = (struct IOParReq *)GetMsg(sdu->sdu_TxPort))
                {
                  sdu->sdu_NoMore = TRUE;    /* there might be more? */
                  ServiceTxPort(sdu, iopar);
                }
            }
        }

#ifdef HANDLE_IO_QUICK
        sdu->sdu_Unit.unit_flags &= ~(UNITF_ACTIVE | UNITF_INTASK | UNITF_WAKETASK);
#endif
        /* No more messages on my port */
        /* If we're online, we need to shut everything down. */
        if(sdu->sdu_State & SPARUF_ONLINE)
        {
            ClosePARnet(sdu);
            FreeSignal((long)signalbit);
            while(bm = (struct BufferManagement *)RemHead((struct List *)&sdu->sdu_BuffMgmt))
            FreeMem(bm,sizeof(struct BufferManagement));
        }
        DeinitPARnet(sdu);

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

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

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

