/*
** $Source: dh1:network/parnet/Sana2/Sources/spar_funcs.c,v $
** $State: Exp $
** $Revision: 37.2 $
** $Date: 93/12/17 23:12:43 $
** $Author: S.A.Pechler $
**
** Amiga SANA-II Example PARnet device driver.
**
** SPAR code.
**
** 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"

struct IntuitionBase *IntuitionBase=NULL;

extern int atoi(const char *); /* From stdlib.h */

#ifdef DEBUG

#include "spar_debug.h"

struct Window *DebWin1=NULL;
struct IOStdReq *DebReq=NULL;
struct MsgPort *DebPort=NULL;


# ifdef __STDC__
#  include <stdarg.h>

   extern int vsprintf(char *, const char *, va_list);    /* from stdio.h */

   void LogMessage(char *fmt, ...)
   {
     va_list args;
# else
#  include <varargs.h>

   void LogMessage(va_alist)
     va_dcl
   {
     char *fmt;
     va_list args;
# endif

  char DebugText[128];

#ifdef __STDC__
    va_start(args, fmt);
#else
    va_start(args);
    fmt = va_arg(args, char *);
#endif

   vsprintf(DebugText,fmt,args);

   if(DebReq && DebReq->io_Device)
   {
      DebReq->io_Command=CMD_WRITE;
      DebReq->io_Data=(APTR)DebugText;
      DebReq->io_Length=-1L;
      DoIO((struct IORequest *)DebReq);
   }

   va_end(args);
}


void initsyslog(void)
{

   if(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37L))
   {
     DebWin1=OpenWindow(&DebugWin);

     if(DebPort=CreateMsgPort())
      if(DebReq=CreateIORequest(DebPort,sizeof(struct IOStdReq)))
      {
         DebReq->io_Data=(APTR) DebWin1;
         DebReq->io_Length= sizeof(struct Window);
         OpenDevice("console.device",0, (struct IORequest *)DebReq, 0);
      }

   }
}

void uninitsyslog(void)
{

  AbortIO((struct IORequest *)DebReq);
  WaitIO((struct IORequest *)DebReq);

  while(GetMsg(DebPort));

  CloseDevice((struct IORequest *)DebReq);
  
  if(DebReq) DeleteIORequest(DebReq);
  if(DebPort) DeleteMsgPort(DebPort);


  if (DebWin1) CloseWindow(DebWin1);
  if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
}
#else /* No debugging, use intuition only for error messages */

void initintuition(void)
{
  IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37L);
}

void uninitintuition(void)
{
  if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
}

#endif /* DEBUG */


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

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

    struct EasyStruct es;     /* For errormessage to user */

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

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

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

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

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

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

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

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

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

                /* Parse the line...*/

                /* I use 'DESTADDR' as a string, otherwise I can't check if
                 * it's omitted. (when using 'DESTADDR/N' and you don't
                 * fill in this argument in the config.file, args[3] will
                 * still point to something.)
                 */
                if(ReadArgs("PARNAME/A,PARUNIT/A/N,PARADDR/A/N,DESTADDR",args,rdargs))

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

                  if(args[3]) sdu->sdu_DestAddr = (UBYTE) atoi((char *)args[3]);

/*                if(args[3]) sdu->sdu_DestAddr = (UBYTE) *((ULONG *)args[3]);
*/
                  else sdu->sdu_DestAddr = (UBYTE) 0;

                  /* check hardware addresses */
                  if ((sdu->sdu_HwAddr!=0) && (sdu->sdu_HwAddr!=255) &&
                      (sdu->sdu_DestAddr!=255))
                    status = TRUE;
                  else
                  {
                    /* send a notification to the user */
                    es.es_TextFormat="Invalid hardware address (%d) given.";
                    EasyRequestArgs(NULL, &es, 0, &sdu->sdu_HwAddr);
                  }
                  debug(("Config: Hardware addr:%d, dest.addr:%d\n",(int)sdu->sdu_HwAddr,(int)sdu->sdu_DestAddr))

                  FreeArgs(rdargs);
                  break;
                }
                else  /* Argument error */
                {
                  /* Produce an error message in a requester */
                  es.es_TextFormat="Error in configuration file on line %ld.";
                  EasyRequestArgs(NULL, &es, 0, &linenum);
                  break;
                }
            }
            FreeDosObject(DOS_RDARGS,rdargs);
        }
        FreeMem(linebuff, 256);
      }
      Close(ConfigFile);
    }
  return(status);
}                 


VOID SendPacket(struct SPARDevUnit *sdu, struct IOSana2Req *ios2)
{
    struct IOParReq *iopar;
    struct BufferManagement *bm;
    struct Spar_Hdr *PktHdr;  /* Spar frame header */

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

    /* Check first if destination hardware address is valid */
    if ((ios2->ios2_DstAddr[0]==0) || (ios2->ios2_DstAddr[0]==255))
    {
        PacketDropped(sdu); /* Can't handle addresses 0 and 255 */

        debug(("SendPacket: Bad destination addr:%08lx%04lx (packet dropped).\n",*(LONG *)ios2->ios2_DstAddr,*(UWORD *)(ios2->ios2_DstAddr+4)))
    }

    else
    {
      /* Copy the data out of the packet into our temporary buffer. */
      if((*bm->bm_CopyFromBuffer)(sdu->sdu_TxBuff+SHDR_LEN,ios2->ios2_Data,ios2->ios2_DataLength))
      {
        PktHdr=(struct Spar_Hdr *)sdu->sdu_TxBuff; /* Pointer to frame header */

        /* Create frame header (Ethernet-like) */

        PktHdr->SDstAddr=ios2->ios2_DstAddr[0]; /* Place packet destination Spar address */

        /* Place my source address in packet header */
        if ((ios2->ios2_SrcAddr[0]==0) || (ios2->ios2_SrcAddr[0]==255)) /* Valid source address ? */
          PktHdr->SSrcAddr=sdu->sdu_StAddr; /* Use internal h/w addr. */
        else
          PktHdr->SSrcAddr=ios2->ios2_SrcAddr[0];

        PktHdr->SFrmType=(UWORD)ios2->ios2_PacketType; /* Place frame type */

        /* Update statistics, (without header information) */
        PacketSent(sdu,ios2->ios2_DataLength);

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

        debug(("SendPacket: Sent packet size %ld addr: %08lx%04lx.\n", ios2->ios2_DataLength, *(LONG *)ios2->ios2_DstAddr,*(UWORD *)(ios2->ios2_DstAddr+4)))
      }
      else
      {
        /* Something went wrong...*/
        ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
        ios2->ios2_WireError = S2WERR_BUFF_ERROR;
        DoEvent(sdu,S2EVENT_BUFF);
      }
    }
    TermIO(ios2);
}


/*
** This routine is called whenever we think we've got
** a complete packet to satisfy a CMD_READ request.
*/
VOID GotPacket(struct SPARDevUnit *sdu, ULONG length)
{
    struct IOSana2Req *ios2;
    struct BufferManagement *bm;
    ULONG Offset;
    struct Spar_Hdr *PktHdr;  /* Spar frame header */

    if(length)
    {
      PacketReceived(sdu,length); /* Update statistics */

      PktHdr=(struct Spar_Hdr *)sdu->sdu_RxBuff; /* Pointer to frame header */

      /* Find an appropriate request wanting this packet type.
       * Routine gotten from the agnet.device by ppessi <Pekka.Pessi@hut.fi>
       */
      ObtainSemaphore(&sdu->sdu_ListLock);

      /* For each queued read request... */
      for (ios2 = (struct IOSana2Req *)sdu->sdu_Rx.mlh_Head;
           ios2->ios2_Req.io_Message.mn_Node.ln_Succ; 
           ios2 = (struct IOSana2Req *)ios2->ios2_Req.io_Message.mn_Node.ln_Succ)
      {
        /* Does requested packet type match? */
        if (ios2->ios2_PacketType == (ULONG)PktHdr->SFrmType)
        {
           Remove((struct Node *)ios2); /* Yes, get & remove it from list */
           break;                       /* No more searches needed */
        }
      }

      if(!ios2) /* Nobody wants this packet type? So, it's orphan */
      {
        ReceivedOrphan(sdu);  /* Update statistics */
        if (!(ios2 = (struct IOSana2Req *)RemHead((struct List *)&sdu->sdu_RxOrph)))
          PacketDropped(sdu);  /* Nobody is interested in this packet, drop it */ 
      }

      ReleaseSemaphore(&sdu->sdu_ListLock);

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

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

      /* Copy the data into the protocol stack's buffer using its
       * supplied callback routine. */
      if((*bm->bm_CopyToBuffer)(ios2->ios2_Data,sdu->sdu_RxBuff+Offset,length-Offset))
      {
         debug(("GotPack (%d): %08lx%08lx%08lx%08lx\n",length-Offset,*(LONG *)sdu->sdu_RxBuff,*(LONG *)(sdu->sdu_RxBuff+4),*(LONG *)(sdu->sdu_RxBuff+8),*(LONG *)(sdu->sdu_RxBuff+12)))

         /* Copy source and destination addresses */
         memset(ios2->ios2_DstAddr, 0, SANA2_MAX_ADDR_BYTES); /* first clear */
         memset(ios2->ios2_SrcAddr, 0, SANA2_MAX_ADDR_BYTES); /* them both.  */
         ios2->ios2_DstAddr[0]=PktHdr->SDstAddr;
         ios2->ios2_SrcAddr[0]=PktHdr->SSrcAddr;

         /* Our packetlength (if not SANA2IOB_RAW then strip frame header) */
         ios2->ios2_DataLength = length-Offset;

         /* Our packet type */
         ios2->ios2_PacketType = (ULONG)PktHdr->SFrmType;
#ifdef DEBUG
         debug(("GotPacket: Length:%lx ", ios2->ios2_DataLength))
         if (ios2->ios2_PacketType==ETHERTYPE_ARP)    debug(("Type:ARP "))
         else if(ios2->ios2_PacketType==ETHERTYPE_IP) debug(("Type:IP "))
              else debug(("Type:%08lx ",ios2->ios2_PacketType))
         debug(("Source:%08lx%04lx.\n", *(LONG *)ios2->ios2_SrcAddr,*(UWORD *)(ios2->ios2_SrcAddr+4) ))
#endif
      }
      else
      {
         ios2->ios2_DataLength   = 0;
         ios2->ios2_Req.io_Error = S2ERR_NO_RESOURCES;
         ios2->ios2_WireError    = S2WERR_BUFF_ERROR;
         DoEvent(sdu,S2EVENT_BUFF);
      }
      TermIO(ios2);
    } /* Length */
}


/*
** This routine initializes our IO requests and buffers for PARnet i/o .
*/
BOOL InitPARnet(struct SPARDevUnit *sdu)
{
    ULONG *clr;
    BOOL status = FALSE;

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

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

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

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

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

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

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

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

}


/*
** This routine opens the PARnet device driver and attempts to bring
** the device online.
*/
BOOL OpenPARnet(struct SPARDevUnit *sdu)
{
  BOOL status = FALSE;
  ULONG odflags = 0;
  struct IOParReq *ioTpar,*ioRpar; /* IORequest structures */

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

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

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

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

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

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

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

                /* check first if the reply port has already been initalized */
                if (sdu->sdu_RxPort)  /* Is this really needed?? */
                {
                     /* Queue up the initial CMD_READ command for the PARnet driver. */
                     ioRpar->io_Message.mn_ReplyPort=sdu->sdu_RxPort;
                     ioRpar->io_Port    = SPAR_PORT;  /* PARnet port to listen on */
                     ioRpar->io_Flags   = PRO_DGRAM; /* only DataGram mode supported */
                     ioRpar->io_Command = CMD_READ;
                     ioRpar->io_Length  = SPAR_MTU;
                     if (sdu->sdu_RxBuff)            /* Is this needed?? */
                     {
                          ioRpar->io_Data = sdu->sdu_RxBuff;
                          SendIO((struct IORequest *)ioRpar);
                          status = TRUE; /* All ok! */
                     }
                }
             }
             if(!status)
                 CloseDevice((struct IORequest *)ioTpar);
      }
      else /* Can't set PARnet hardware address */
        CloseDevice((struct IORequest *)ioTpar);
    }
    else /* Can't open device */
    {
        args[0]=(ULONG)sdu->sdu_ParDevName;
        args[1]=(ULONG)sdu->sdu_ParUnitNum;

            es.es_StructSize=sizeof(struct EasyStruct);
            es.es_Flags=0;
            es.es_Title="spar.device";
            es.es_TextFormat="Couldn't open %s unit %ld.";
            es.es_GadgetFormat="Okay";
            EasyRequestArgs(NULL, &es, 0, (APTR)args);
    }
    if(sdu->sdu_State & SPARUF_ONLINE)
      MarkTimeOnline(sdu);
  }
  return(status);
}

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

    while(GetMsg(sdu->sdu_RxPort));

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

    while(GetMsg(sdu->sdu_TxPort));

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

    sdu->sdu_State &= ~SPARUF_ONLINE;

}

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

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