/*
 *  DNET.C
 *
 *  DNet device dnet.device
 *
 *  Simulates a serial device using dnet.
 *
 *  This code is based on Matt Dillon's fms.device. (In fact whatever did not 
 *  need changing did not get changed).
 *
 *  Written by Karl R. Hakimian
 *
 *  Bugs & Limits
 *  Only one read can be done at a time. A second read queued will abort the 
 *  first  read.
 *  SDCMD_BREAK, SDCMD_SETPARAMS are not supported.
 *
 *  Modification History
 *  11-24-90 Karl Hakimian Original coding (hacking of fms.device)
 *  12-02-90 Bug fixes support for SDCMD_QUERY added.
 */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/errors.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <libraries/dosextens.h>

/*#define DEBUG 1*/

#ifdef DEBUG
int d_out;
#endif

long DOSBase = NULL;

#define ABORTED (1<<5)

#define CMD_ABORTREQ	(0x7FF0)
#define CMD_KILLPROC	(0x7FF1)
#define CMD_STARTUP	(0x7FF1)

void CoProc();

extern int TagDevOpen();
extern int TagDevClose();
extern int TagDevExpunge();
extern int TagDevBeginIO();
extern int TagDevAbortIO();

extern void *CreateProc();
extern void *CreatePort();
extern void *AllocMem();
extern void DUMmySeg();

typedef struct Library	LIB;
typedef struct Device	DEV;
typedef struct Process	PROC;
typedef struct MsgPort	PORT;
typedef struct Message	MSG;
typedef struct List	LIST;
typedef struct Node	NODE;
typedef long (*func_ptr)();

typedef struct {
    PORT    *Port;
} NDUnit;

typedef struct {
    LIB     Lib;
    char    *Host;  /* NULL. I will do something with this when Matt does. */
    int     Id;
} NDev;

/* Taken from dnet:amiga/lib/dnetlib.h */
typedef unsigned char   ubyte;
typedef unsigned short  uword;

#define CHANN   struct _CHANN

CHANN {
    PORT    port;       /*  receive data, replies       */
    PORT    *dnetport;      /*  dnet's master port          */
    LIST    rdylist;        /*  ready to be read        */
    uword   chan;       /*  channel # for open channels */
    ubyte   eof;        /*  channel remotely closed/eof */
    ubyte   filler;
    int     qlen;       /*  allowed write queue size    */
    int     queued;     /*  current # packets queued    */
};

#define DNCMD_WRITE 36  /*  Write data to a channel     */

typedef struct IOStdReq IOSTD;
/* End of dnet stuff */

typedef struct IOExtSer IOB;

extern char DeviceName[];
extern char IdString[];

long SysBase	= NULL;
NDev *DevBase	= NULL;
APTR DevSegment = NULL;

NDev *
Init(seg)
APTR seg;
{
    static func_ptr DevVectors[] = {
	(func_ptr)TagDevOpen,
	(func_ptr)TagDevClose,
	(func_ptr)TagDevExpunge,
	NULL,
	(func_ptr)TagDevBeginIO,
	(func_ptr)TagDevAbortIO,
	(func_ptr)-1
    };
    NDev *db;


    SysBase = *(long *)4;
    DOSBase = OpenLibrary("dos.library",0);

    if (DOSBase == NULL)
	return(NULL);

    DevBase = db = (NDev *)MakeLibrary((long **)DevVectors,NULL,NULL,sizeof(NDev),NULL);
    db->Lib.lib_Node.ln_Type = NT_DEVICE;
    db->Lib.lib_Node.ln_Name = DeviceName;
    db->Lib.lib_Flags = LIBF_CHANGED|LIBF_SUMUSED;
    db->Lib.lib_Version = 1;
    db->Lib.lib_IdString= (APTR)IdString;
    db->Host = NULL;
    db->Id = 0;

    DevSegment = seg;
    AddDevice((DEV *)db);
#ifdef DEBUG
    d_out = Open("con:0/0/320/200/Debug", 1006);
#endif
    return(db);
}

NDev *
DevOpen(unitnum, iob, flags)
long unitnum;
IOB  *iob;
long flags;
{
    NDUnit *unit; 
    IOB siob;
    ULONG port;
    char temp[32];
    int id;
    int i;

#ifdef DEBUG
    Write(d_out,"Open\n",5);
#endif
    if ((unit = (NDUnit *)AllocMem(sizeof(NDUnit),MEMF_PUBLIC|MEMF_CLEAR)) == NULL) {
	iob->IOSer.io_Error = IOERR_OPENFAIL;
	iob->IOSer.io_Device = NULL;
	return(DevBase);
    }

    DevBase->Lib.lib_OpenCnt++;
    id = DevBase->Id++;
/*
  Initialize CoProc process.
*/
    sprintf(temp,"DNet %d",id);
    port = (ULONG)CreateProc(temp, 0, (long)DUMmySeg >> 2, 4096);
    sprintf(temp,"%x",port);

    if ((siob.IOSer.io_Message.mn_ReplyPort = CreatePort(NULL,0)) == NULL) {
    	DevBase->Lib.lib_OpenCnt--;
    	FreeMem(unit,sizeof(NDUnit));
	iob->IOSer.io_Error = IOERR_OPENFAIL;
	iob->IOSer.io_Device = NULL;
    	return(DevBase);
    }

    siob.IOSer.io_Command = CMD_STARTUP;
    siob.IOSer.io_Data = (APTR)unitnum;
    siob.IOSer.io_Length = -1;
/*
  Lookup for childs port. Timeout in 10 seconds.
*/
    for (i = 0; i < 10 && !(unit->Port = (PORT *)FindPort(temp)); i++)
        Delay(50);

    if (unit->Port == NULL) {
	iob->IOSer.io_Error = IOERR_OPENFAIL;
	iob->IOSer.io_Device = NULL;
    	DevBase->Lib.lib_OpenCnt--;
    	FreeMem(unit,sizeof(NDUnit));
	DeletePort(siob.IOSer.io_Message.mn_ReplyPort);
	return(DevBase);
    }
/*
  Send startup message.
*/
    PutMsg(unit->Port,&siob.IOSer.io_Message);
    WaitPort(siob.IOSer.io_Message.mn_ReplyPort);
    (void)GetMsg(siob.IOSer.io_Message.mn_ReplyPort);
    DeletePort(siob.IOSer.io_Message.mn_ReplyPort);
/*
  If there was an error on startup then quit.
*/
    if (siob.IOSer.io_Error) {
	DevBase->Lib.lib_OpenCnt--;
	FreeMem(unit,sizeof(NDUnit));
	iob->IOSer.io_Error = IOERR_OPENFAIL;
	iob->IOSer.io_Device = NULL;
	return(DevBase);
    }
/*
  Initialize device and io request structure.
*/
    DevBase->Lib.lib_Flags &= ~LIBF_DELEXP;
    iob->IOSer.io_Unit = (struct NDUnit *)unit;
    iob->IOSer.io_Error = 0;
    return(DevBase);
}

APTR
DevExpunge()
{

    if (DevSegment == NULL)
	Alert(24, (char *)24);
    if (DevBase->Lib.lib_OpenCnt) {
	DevBase->Lib.lib_Flags |= LIBF_DELEXP;
	return(NULL);
    }
    Remove((NODE *)DevBase);
    CloseLibrary(DOSBase);
    FreeMem((char *)DevBase - DevBase->Lib.lib_NegSize, DevBase->Lib.lib_NegSize + DevBase->Lib.lib_PosSize);
#ifdef DEBUG
    Close(d_out);
#endif
    return(DevSegment);
}

APTR
DevClose(iob)
IOB *iob;
{
    IOB kiob;
    NDUnit *unit;

    unit = (NDUnit *)iob->IOSer.io_Unit;

    if (unit == NULL  || DevBase->Lib.lib_OpenCnt == 0)
    	return(NULL);
/*
  Kill CoProc if it is still there.
*/
    if (unit->Port) {
	kiob.IOSer.io_Message.mn_ReplyPort = CreatePort(NULL, 0);
	kiob.IOSer.io_Command = CMD_KILLPROC;
	PutMsg(unit->Port, &kiob.IOSer.io_Message);
	WaitPort(kiob.IOSer.io_Message.mn_ReplyPort);
	DeletePort(kiob.IOSer.io_Message.mn_ReplyPort);
    }

    FreeMem(unit,sizeof(NDUnit));

    if (DevBase->Lib.lib_OpenCnt && --DevBase->Lib.lib_OpenCnt)
	return(NULL);

    if (DevBase->Lib.lib_Flags & LIBF_DELEXP)
	return(DevExpunge());
    /*
     *	close down resources
     */
    return(NULL);
}

void
DevBeginIO(iob)
IOB *iob;
{
    NDUnit *unit;
    IOB *fiob;

    unit = (NDUnit *)iob->IOSer.io_Unit;
#ifdef DEBUG
    Write(d_out,"BeginIO\n",8);
#endif
    if (unit->Port == NULL) {
	iob->IOSer.io_Error = SerErr_LineErr;
	ReplyMsg(&iob->IOSer.io_Message);
	return;
    }

    iob->IOSer.io_Error = 0;
    iob->IOSer.io_Actual = 0;

    switch(iob->IOSer.io_Command) {
    case CMD_INVALID:
	iob->IOSer.io_Error = IOERR_NOCMD;
	break;
    case CMD_RESET:
#ifdef DEBUG
	Write(d_out,"RESET\n",6);
#endif
	Forbid();

	while ((fiob = (IOB *)GetMsg(unit->Port))) {
	    fiob->IOSer.io_Flags |= ABORTED;
	    ReplyMsg(fiob);
	}

	Permit();
	PutMsg(unit->Port, &iob->IOSer.io_Message);
	iob->IOSer.io_Flags &= ~IOF_QUICK;
	iob = NULL;
	break;
    case CMD_READ:
#ifdef DEBUG
	Write(d_out,"READ\n",5);
#endif
	PutMsg(unit->Port, &iob->IOSer.io_Message);
	iob->IOSer.io_Flags &= ~IOF_QUICK;	/*  not quick */
	iob = NULL;
	break;
    case CMD_WRITE:
#ifdef DEBUG
	Write(d_out,"WRITE\n",6);
#endif
	PutMsg(unit->Port, &iob->IOSer.io_Message);
	iob->IOSer.io_Flags &= ~IOF_QUICK;	/*  not quick */
	iob = NULL;
	break;
    case CMD_CLEAR:
#ifdef DEBUG
	Write(d_out,"CLEAR\n",6);
#endif
	PutMsg(unit->Port, &iob->IOSer.io_Message);
	iob->IOSer.io_Flags &= ~IOF_QUICK;
	iob = NULL;
	break;
    case CMD_FLUSH:
#ifdef DEBUG
	Write(d_out,"FLUSH\n",6);
#endif
	Forbid();

	while ((fiob = (IOB *)GetMsg(unit->Port))) {
	    fiob->IOSer.io_Flags |= ABORTED;
	    ReplyMsg(fiob);
	}

	Permit();
	break;
    case SDCMD_QUERY:
#ifdef DEBUG
	Write(d_out,"QUERY\n",6);
#endif
	PutMsg(unit->Port, &iob->IOSer.io_Message);
	iob->IOSer.io_Flags &= ~IOF_QUICK;
	iob = NULL;
	break;
    case CMD_STOP:
    case CMD_START:
    case SDCMD_SETPARAMS:
    case SDCMD_BREAK:
#ifdef DEBUG
	Write(d_out,"OTHER\n",6);
#endif
	break;
    default:
#ifdef DEBUG
	Write(d_out,"BAD\n",4);
#endif
	iob->IOSer.io_Error = IOERR_NOCMD;
	break;
    }
    if (iob) {
	if ((iob->IOSer.io_Flags & IOF_QUICK) == 0)
	    ReplyMsg((MSG *)iob);
    }

}

DevAbortIO(iob)
IOB *iob;
{
    IOB aiob;
    IOB *riob;
    NDUnit *unit; 
    struct List *l;

    unit = (NDUnit *)iob->IOSer.io_Unit;

    if (unit->Port == NULL) {
	iob->IOSer.io_Flags |= ABORTED;
	ReplyMsg(iob);
	return(IOERR_ABORTED);
    }
	
    l = &unit->Port->mp_MsgList;
/*
  Search for the request in the message queue.
*/
#ifdef DEBUG
    Write(d_out,"AbortIO\n",8);
#endif
    Forbid();

    for (riob = (IOB *)l->lh_Head; riob; riob = (IOB *)(((struct Node *)riob)->ln_Succ)) {
/*
  Request found, remove it.
*/
	if (riob == iob) {
	    Remove((struct Node *)riob);
	    iob->IOSer.io_Flags |= ABORTED;
	    break;
	}

    }

    Permit();
/*
  If the request was not found, Tell CoProc to abort.
*/
    if (!riob) {

	if ((aiob.IOSer.io_Message.mn_ReplyPort = CreatePort(NULL,0)) == NULL)
	    return(NULL);

	aiob.IOSer.io_Command = CMD_ABORTREQ;
	aiob.IOSer.io_Data = (APTR)iob;
	aiob.IOSer.io_Length = -1;
	PutMsg(unit->Port, &aiob.IOSer.io_Message);
	WaitPort(aiob.IOSer.io_Message.mn_ReplyPort);
	DeletePort(aiob.IOSer.io_Message.mn_ReplyPort);
    }
    else
	ReplyMsg(riob);

    if (iob->IOSer.io_Flags & ABORTED)
	return(IOERR_ABORTED);

    return(0);
}

/*
 *	SERVER SIDE (IS A PROCESS)
 */

void
CoProc()
{
    IOB *iob;
    IOSTD *dnreq;
    NDUnit *unit;
    PROC *proc;
    PORT *port;
    char notdone = 1;
    int toread;
    int haveread;
    int read;
    int data_ready = 0;
    IOB *riob=NULL;
    char pname[32];
    int Chan;
    int cmask;
    int dmask;
    int sig;
#ifdef DEBUG
    char buf[256];
#endif
/*
  Create command message port.
*/
    proc = (PROC *)FindTask(NULL);
    sprintf(pname,"%x",&(proc->pr_MsgPort));

    if ((port = CreatePort(pname,0)) == NULL) {
	return;
    }
/*
  Wait for startup message.
*/
    (void)Wait(1 << port->mp_SigBit);

    iob = (IOB *)GetMsg(port);

    if (iob->IOSer.io_Command != CMD_STARTUP || (Chan = DOpen(DevBase->Host,iob->IOSer.io_Data)) == NULL) {
	iob->IOSer.io_Error = IOERR_OPENFAIL;
    	ReplyMsg(&iob->IOSer.io_Message);
    	DeletePort(port);
    	return;
    }

    iob->IOSer.io_Error = 0;
    ReplyMsg(&iob->IOSer.io_Message);

    DQueue(Chan,128);
    dmask = 1 << ((PORT *)Chan)->mp_SigBit;
    cmask = 1 << port->mp_SigBit;
#ifdef DEBUG
    sprintf(buf,"dmask = %xx, cmask = %xx\n",dmask,cmask);
    Write(d_out,buf,strlen(buf));
#endif

    while (notdone) {
	sig = Wait(cmask|dmask);

	if (sig&cmask) {

	    while (iob = (IOB *)GetMsg(port)) {
		unit = (NDUnit *)iob->IOSer.io_Unit;

		switch(iob->IOSer.io_Command) {
		case CMD_ABORTREQ:
#ifdef DEBUG
		    Write(d_out,"ABORT\n",6);
#endif

		    if (iob->IOSer.io_Data == (APTR)riob) {
			riob->IOSer.io_Flags |= ABORTED;
			ReplyMsg(&riob->IOSer.io_Message);
			riob = NULL;
		    }

		    break;
		case CMD_RESET:
#ifdef DEBUG
		    Write(d_out,"RESET\n",6);
#endif

		    if (riob) {
			riob->IOSer.io_Flags |= ABORTED;
			ReplyMsg(&riob->IOSer.io_Message);
		    }
/*
  We want to fall through to clear.
*/
		case CMD_CLEAR:
#ifdef DEBUG
		    Write(d_out,"CLEAR\n",6);
#endif
/*
  Remove all data from the read buffer.
*/
		    while (DNRead(Chan,(char *)&data_ready,1))
			;

		    data_ready = 0;
		    break;
		case SDCMD_QUERY:
#ifdef DEBUG
		    Write(d_out,"QUERY\n",5);
#endif
		    iob->io_Status = 0xf8;
		    Forbid();

		    for (dnreq = (IOSTD *)((CHANN *)Chan)->rdylist.lh_Head; dnreq; dnreq = (IOSTD *)((struct Node *)dnreq)->ln_Succ) {

			if (dnreq->io_Command == DNCMD_WRITE)
			    iob->IOSer.io_Actual += dnreq->io_Length - dnreq->io_Actual;

		    }

		    for (dnreq = (IOSTD *)((CHANN *)Chan)->port.mp_MsgList.lh_Head; dnreq; dnreq = (IOSTD *)((struct Node *)dnreq)->ln_Succ) {

			if (dnreq->io_Command == DNCMD_WRITE)
			    iob->IOSer.io_Actual += dnreq->io_Length - dnreq->io_Actual;

		    }

		    Permit();
		    break;
		case CMD_KILLPROC:
#ifdef DEBUG
		    Write(d_out,"KILL\n",5);
#endif
		    goto exit_proc;
		    break;
		case CMD_READ:
#ifdef DEBUG
		    Write(d_out,"READ\n",5);
#endif
/*
  Currently only allows one read at a time. If a read request comes in while one
  is still pending then abort the first read.
*/
	    	    if (riob) {
			riob->IOSer.io_Error = SerErr_LineErr;
			ReplyMsg(&riob->IOSer.io_Message);
			riob = NULL;
		    }

		    toread = iob->IOSer.io_Length;
		    haveread = 0;
		    riob = iob;
		    iob = NULL;
		    break;
		case CMD_WRITE:
#ifdef DEBUG
		    Write(d_out,"WRITE\n",6);
#endif

		    if (iob->IOSer.io_Length >= 0)
			iob->IOSer.io_Actual = DWrite(Chan, (char *)iob->IOSer.io_Data, iob->IOSer.io_Length);
		    else if (iob->IOSer.io_Length == -1)
			iob->IOSer.io_Actual = DWrite(Chan, (char *)iob->IOSer.io_Data, strlen((char *)iob->IOSer.io_Data));
		    else
			iob->IOSer.io_Actual = iob->IOSer.io_Length;

		    if (iob->IOSer.io_Actual < 0) {
			iob->IOSer.io_Error = SerErr_LineErr;
			goto exit_proc;
		    }

		    break;
		default:
		    iob->IOSer.io_Error = SerErr_LineErr;
		    break;
		}

		if (iob)
		    ReplyMsg(&iob->IOSer.io_Message);

	    }

	}
/*
  Do the read if there is data and a pending read request.
*/
	if (((sig&dmask) || data_ready) && riob) {

	    if (toread == -1) {

		while (riob && (read = DNRead(Chan,(char *)&riob->IOSer.io_Data[haveread],1))) {
		    if ((char *)&riob->IOSer.io_Data[haveread] == '\0') {
			riob->IOSer.io_Actual = haveread;
			ReplyMsg(&riob->IOSer.io_Message);
			riob = NULL;
			toread = haveread = 0;
		    }

		    haveread++;
		}

		continue;
	    }

	    read = DNRead(Chan, (char *)&riob->IOSer.io_Data[haveread], toread);

	    if (read < 0) {
		riob->IOSer.io_Error = SerErr_LineErr;
		ReplyMsg(&riob->IOSer.io_Message);
		iob = NULL;
		goto exit_proc;
	    }
	    else if (read == 0)
	    	data_ready = 0;
	    else {
		data_ready = 1;	/* data_read is true until no data is left. */
		haveread += read;
		toread -= haveread;
	    }

	    if (toread <= 0) {
		riob->IOSer.io_Actual = haveread;
		ReplyMsg(&riob->IOSer.io_Message);
		riob = NULL;
		toread = haveread = 0;
	    }

	}
	else if (sig&dmask)	/* There is data ready so record rember it. */
	    data_ready = 1;

    }
    /* fall through to exit */

    Forbid();		/*  Forbid before exiting. */
    return;

exit_proc:
#ifdef DEBUG
    Write(d_out,"exiting\n",8);
#endif

    if (riob) {
	riob->IOSer.io_Error = SerErr_LineErr;
	ReplyMsg(&riob->IOSer.io_Message);
    }

    DClose(Chan);
#ifdef DEBUG
    Write(d_out,"DNET closed\n",12);
#endif

    while (riob = (IOB *)GetMsg(port)) {
	riob->IOSer.io_Error = SerErr_LineErr;
	riob->IOSer.io_Flags |= ABORTED;
	ReplyMsg(&riob->IOSer.io_Message);
    }

    DeletePort(port);
    unit->Port = NULL;
#ifdef DEBUG
    Write(d_out,"Port gone\n",10);
#endif
    Forbid();		/*  Forbid before exiting. */

    if (iob)
	ReplyMsg(&iob->IOSer.io_Message);

}
