
/*
 *  UUSER.C UUSER:devicename/unitnumber/options
 *	    UUSER:serial.device/0/R1000
 *
 *  options:
 *	Rn	Set read timeout when no data available to n millisecs
 *	C0	Ignore carrier detect
 *
 *  features:
 *	1K asynchronous write capability (write 0 bytes to sync up)
 *	programmable read-timeout (Getty starts procs up w/ 1sec to)
 *
 *	combined together, you can easily implement 100% efficient
 *	protocols even with that 1 second read timeout!
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <hardware/cia.h>
#include <proto/all.h>

#define WRITEBUFSIZE	1024
#define MPC (MEMF_PUBLIC|MEMF_CLEAR)        /* options to AllocMem()    */

#define BTOC(x, type)   ((type *)((long)x << 2))
#define CTOB(x)         ((BPTR)((long)x >> 2))

#define DOS_FALSE    0
#define DOS_TRUE     -1

typedef struct IOExtSer     IOS;
typedef struct timerequest  IOT;
typedef struct IORequest    IOR;
typedef struct timeval	    TimeVal;

typedef struct FileLock     LOCK;
typedef struct DosPacket    Packet;
typedef struct Process	    PROC;
typedef struct DeviceNode   DEVNODE;
typedef struct FileHandle   FH;
typedef struct Message	    MSG;
typedef struct Node	    NODE;
typedef struct List	    LIST;
typedef struct MsgPort	    PORT;

typedef struct SHandle {
    NODE    Node;
    IOT     Iot;		/* wait-for-char and read req	*/
    IOS     Ios;
    IOS     Iosr;		/* 1005 			*/
    IOS     Iosw;		/* 1005,1006			*/
    char    IotIP;
    char    IosrIP;
    char    IoswIP;
    char    RxIn[1];		/* one char buffer		*/
    char    *TxOut;		/* asynch write buffer		*/
    Packet  *RPacket;		/* current pending read packet	*/
    Packet  *WPacket;		/* current pending write packet */
    short   Flags;
    LIST    RxWait;		/* requests waiting for data	*/
    LIST    TxWait;		/* requests waiting to write	*/
    LIST    CxWait;		/* wait for char		*/
    TimeVal ReadTo;
} SHandle;

#define HF_IGNORECD	0x01
#define HF_CDLOST	0x02
#define HF_RTO		0x04
#define HF_DONTLOCK	0x08	/* if G option for from-getty	*/

extern Packet *taskwait();   /* wait for a message           */

long SysBase;			/* required to make Exec calls	*/
char ScrBuf[256];		/* Scratch buffer		*/
PORT *IoSink;
LIST HanList;
LIST NodList;
IOT  Iot;			/* Iot master, also used for CD */
char IotIP;

void	AttemptRead();
void	AttemptWrite();
void	AbortPackets();
void	StartTimer();

__saveds void
_dummy()
{
    PROC	*myproc;
    DEVNODE	*mynode;
    UBYTE	notdone;
    long	mask;

    SysBase = *(long *)4;
    IoSink = CreatePort(NULL, 0);
    NewList(&HanList);
    NewList(&NodList);

    Iot.tr_node.io_Message.mn_ReplyPort = IoSink;
    OpenDevice("timer.device", UNIT_VBLANK, &Iot, 0);
    Iot.tr_node.io_Command = TR_ADDREQUEST;
    Iot.tr_time.tv_secs = 4;
    Iot.tr_time.tv_micro= 0;
    SendIO(&Iot);
    IotIP = 1;

    myproc  = (PROC *)FindTask(0L);

    /*
     * INITIAL STARTUP MESSAGE
     */

    {
	Packet *mypkt;

	mypkt	= taskwait(myproc);
	mynode	= BTOC(mypkt->dp_Arg3, DEVNODE);
	mynode->dn_Task = &myproc->pr_MsgPort;
	returnpkt(mypkt, myproc, DOS_TRUE, 0);
    }

loop:
    notdone = 1;
    mask = (1 << IoSink->mp_SigBit) | (1 << myproc->pr_MsgPort.mp_SigBit);
    while (notdone) {
	Packet *mypkt;		 /* dos packet received      */
	IOR *ior;
	SHandle *handle;
	long type;		    /* type of packet		*/

	ior = (IOR *)GetMsg(IoSink);
	mypkt = (Packet *)GetMsg(&myproc->pr_MsgPort);
	if (mypkt)
	    mypkt = (Packet *)(((MSG *)mypkt)->mn_Node.ln_Name);
	while (mypkt == NULL && ior == NULL) {
	    Wait(mask);
	    ior = (IOR *)GetMsg(IoSink);
	    mypkt = (Packet *)GetMsg(&myproc->pr_MsgPort);
	    if (mypkt)
		mypkt = (Packet *)(((MSG *)mypkt)->mn_Node.ln_Name);
	}

	/*
	 *  Make sure there is at least one free node in node list
	 */

	if (NodList.lh_Head == (NODE *)&NodList.lh_Tail) {
	    NODE *pknode = AllocMem(sizeof(NODE), MPC);
	    AddTail(&NodList, pknode);
	}

	/*
	 *  Returned IO request, sift through lists to find it.
	 */

	if (ior) {
	    if (ior == &Iot) {      /*  Check for carrier lost */
		for (handle = (SHandle *)HanList.lh_Head; handle != (SHandle *)&HanList.lh_Tail; handle = (SHandle *)handle->Node.ln_Succ) {
		    if (!(handle->Flags & HF_IGNORECD) && !(handle->Flags & HF_CDLOST)) {
			handle->Ios.IOSer.io_Command = SDCMD_QUERY;
			DoIO(&handle->Ios);
			if (handle->Ios.io_Status & CIAF_COMCD) {
			    handle->Flags |= HF_CDLOST;
			    AbortPackets(handle, myproc);
			}
		    }
		}
		if (HanList.lh_Head == (NODE *)&HanList.lh_Tail) {
		    IotIP = 0;
		} else {
		    Iot.tr_time.tv_secs = 4;
		    Iot.tr_time.tv_micro= 0;
		    SendIO(&Iot);
		    IotIP = 1;
		}
	    } else
	    for (handle = (SHandle *)HanList.lh_Head; handle != (SHandle *)&HanList.lh_Tail; handle = (SHandle *)handle->Node.ln_Succ) {
		if (ior == (IOR *)&handle->Iosr) {
		    returnpkt(handle->RPacket, myproc, handle->Iosr.IOSer.io_Actual, 0);
		    handle->IosrIP = 0;
		    handle->RPacket = NULL;

		    if (handle->IotIP) {
			AbortIO(&handle->Iot);
			WaitIO(&handle->Iot);
			handle->IotIP = 0;
		    }
		    AttemptRead(handle, myproc);
		}
		if (ior == (IOR *)&handle->Iosw) {
		    handle->IoswIP = 0;
		    if (handle->WPacket) {
			handle->Iosw.IOSer.io_Data = (APTR)handle->TxOut;
			handle->Iosw.IOSer.io_Length= handle->WPacket->dp_Arg3 - handle->WPacket->dp_Res1;
			SendIO(&handle->Iosw);
			handle->IoswIP = 1;
			handle->WPacket->dp_Res1 = handle->WPacket->dp_Arg3;
			returnpktplain(handle->WPacket, myproc);
			handle->WPacket = NULL;
		    } else {
			AttemptWrite(handle, myproc);
		    }
		}
		if (ior == (IOR *)&handle->Iot) {
		    handle->IotIP = 0;
		    if ((handle->Flags & HF_RTO) && handle->IosrIP) {
			AbortIO(&handle->Iosr);
		    }
		}
	    }
	}

	if (mypkt) {
	    mypkt->dp_Res1 = DOS_TRUE;	/* default return value     */
	    mypkt->dp_Res2 = 0; 	/* default no error	    */
	    type = mypkt->dp_Type;	/* packet type		    */

	    /*
	     *	Extract pipe pointer (only applies to read/write)
	     */

	    handle = (SHandle *)mypkt->dp_Arg1;     /*  READ/WRITE only */

	    switch(type) {
	    case ACTION_FINDINPUT:
	    case ACTION_FINDOUTPUT:
	    case ACTION_FINDUPDATE:
		if (IotIP == 0)
		    StartTimer(4);
		{
		    FH	 *fh = BTOC(mypkt->dp_Arg1, FH);
		    char *path = BTOC(mypkt->dp_Arg3, char);
		    char *unit;
		    long n;

		    movmem(path + 1, ScrBuf, *path);
		    ScrBuf[*path] = 0;
		    path = ScrBuf;

		    handle = AllocMem(sizeof(SHandle), MPC);

		    if (strcmp(ScrBuf, "*") == 0)
			strcpy(ScrBuf, "serial.device/0");
		    for (unit = path; *unit && *unit != '/'; ++unit) {
			if (*unit == ':')
			    path = unit + 1;
		    }
		    if (*unit == '/') {
			char *opts;

			*unit = 0;
			++unit;
			for (opts = unit; *opts && *opts != '/'; ++opts);
			while (*opts) {
			    n = atoi(opts + 1);
			    switch(*opts) {
			    case '/':
				break;
			    case 'R':
				handle->ReadTo.tv_secs = n / 1000;
				handle->ReadTo.tv_micro= (n % 1000) * 1000;
				handle->Flags |= HF_RTO;
				break;
			    case 'C':
				handle->Flags |= HF_IGNORECD;
				break;
			    case 'G':
				if (n)
				    handle->Flags |= HF_DONTLOCK;
				break;
			    }
			    ++opts;
			}
		    }

		    /* proc = (PROC *)mypkt->dp_Port->mp_SigTask; */

		    /*
		     * Open the device
		     */

		    handle->Ios.IOSer.io_Message.mn_ReplyPort = IoSink;
		    handle->Ios.io_SerFlags = SERF_XDISABLED | SERF_RAD_BOOGIE | SERF_SHARED;
		    if (OpenDevice(path, atoi(unit), &handle->Ios, 0)) {
			FreeMem(handle, sizeof(SHandle));
			mypkt->dp_Res1 = DOS_FALSE;
			mypkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
			returnpktplain(mypkt, myproc);
			break;
		    }
		    fh->fh_Arg1 = (long)handle;
		    fh->fh_Port = (struct MsgPort *)DOS_TRUE;

		    handle->Iosr = handle->Ios;
		    handle->Iosw = handle->Ios;
		    handle->Iosr.IOSer.io_Command = CMD_READ;
		    handle->Iosw.IOSer.io_Command = CMD_WRITE;
		    handle->Iot = Iot;
		    NewList(&handle->RxWait);
		    NewList(&handle->TxWait);
		    NewList(&handle->CxWait);
		    AddTail(&HanList, handle);
		    returnpktplain(mypkt, myproc);
		}
		break;
	    case ACTION_END:
		Remove(handle);
		if (handle->IosrIP) {
		    AbortIO(&handle->Iosr);
		    WaitIO(&handle->Iosr);
		}
		if (handle->IoswIP) {
		    WaitIO(&handle->Iosw);
		}
		if (handle->IotIP) {
		    AbortIO(&handle->Iot);
		    WaitIO(&handle->Iot);
		}
		AbortPackets(handle, myproc);
		CloseDevice(&handle->Ios);
		returnpktplain(mypkt, myproc);
		if (handle->TxOut)
		    FreeMem(handle->TxOut, WRITEBUFSIZE);
		FreeMem(handle, sizeof(SHandle));
		break;
	    case ACTION_READ:
		{
		    NODE *pknode = RemHead(&NodList);
		    mypkt->dp_Res1 = 0;
		    pknode->ln_Name = (char *)mypkt;
		    AddTail(&handle->RxWait, pknode);
		    AttemptRead(handle, myproc);
		}
		break;
	    case ACTION_WRITE:
		{
		    NODE *pknode = RemHead(&NodList);
		    mypkt->dp_Res1 = 0;
		    pknode->ln_Name = (char *)mypkt;
		    AddTail(&handle->TxWait, pknode);
		    AttemptWrite(handle, myproc);
		}
		break;
	    case ACTION_WAIT_CHAR:
	    default:
		returnpkt(mypkt, myproc, DOS_FALSE, ERROR_ACTION_NOT_KNOWN);
		break;
	    }
	}
    }

    /*
     *	Can only exit if no messages pending.  There might be a window
     *	here, but there is nothing that can be done about it.
     */

    Forbid();
    if (taskpktrdy(myproc)) {
	Permit();
	goto loop;
    }
    mynode->dn_Task = FALSE;
    Permit();

    if (IotIP) {
	AbortIO(&Iot);
	WaitIO(&Iot);
    }
    CloseDevice(&Iot);

    /* we are a process "so we fall off the end of the world" */
    /* MUST fall through */
}

void
AttemptRead(handle, myproc)
SHandle *handle;
PROC *myproc;
{
    Packet *mypkt;
    NODE *pknode;

    if (handle->Flags & HF_CDLOST) {
	AbortPackets(handle, myproc);
	return;
    }
loop:
    if (handle->IosrIP == 0 && (pknode = RemHead(&handle->RxWait))) {
	long n;

	AddTail(&NodList, pknode);

	mypkt = (Packet *)pknode->ln_Name;

	/*
	 *  special case.  If you read 0 bytes, 0 is returned if data
	 *  is pending, else -1, and NO timeout occurs.
	 */

	handle->Ios.IOSer.io_Command = SDCMD_QUERY;
	DoIO(&handle->Ios);

	if (mypkt->dp_Arg3 == 0) {
	    if (handle->Ios.IOSer.io_Actual > 0)
		returnpkt(mypkt, myproc, 0, 0);     /* 0=data rdy */
	    else
		returnpkt(mypkt, myproc, -1, 0);    /* -1=data not rdy */
	    goto loop;
	}

	if ((n = handle->Ios.IOSer.io_Actual) > 0) {
	    if (n > mypkt->dp_Arg3)
		n = mypkt->dp_Arg3;
	    handle->Iosr.IOSer.io_Data = (APTR)mypkt->dp_Arg2;
	    handle->Iosr.IOSer.io_Length = n;
	    DoIO(&handle->Iosr);
	    mypkt->dp_Res1 = handle->Iosr.IOSer.io_Actual;
	    returnpktplain(mypkt, myproc);
	    goto loop;
	}
	handle->Iosr.IOSer.io_Data = (APTR)mypkt->dp_Arg2;
	handle->Iosr.IOSer.io_Length = 1;
	SendIO(&handle->Iosr);
	handle->IosrIP = 1;
	handle->RPacket = mypkt;

	if (handle->Flags & HF_RTO) {
	    if (handle->IotIP) {
		AbortIO(&handle->Iot);
		WaitIO(&handle->Iot);
	    }
	    handle->Iot.tr_time = handle->ReadTo;
	    SendIO(&handle->Iot);
	    handle->IotIP = 1;
	}
    }
}

void
AttemptWrite(handle, myproc)
SHandle *handle;
PROC *myproc;
{
    Packet *mypkt;
    NODE *pknode;

    if (handle->Flags & HF_CDLOST) {
	AbortPackets(handle, myproc);
	return;
    }
    if (handle->IoswIP == 0 && (pknode = RemHead(&handle->TxWait))) {
	AddTail(&NodList, pknode);

	mypkt = (Packet *)pknode->ln_Name;

	if (handle->TxOut == NULL)
	    handle->TxOut = AllocMem(WRITEBUFSIZE, MPC);

	if (mypkt->dp_Arg3 <= WRITEBUFSIZE) {   /* fully asynch */
	    movmem((char *)mypkt->dp_Arg2, handle->TxOut, mypkt->dp_Arg3);
	    handle->Iosw.IOSer.io_Data = (APTR)handle->TxOut;
	    handle->Iosw.IOSer.io_Length = mypkt->dp_Arg3;
	    SendIO(&handle->Iosw);
	    mypkt->dp_Res1 = mypkt->dp_Arg3;
	    returnpktplain(mypkt, myproc);
	    handle->WPacket = NULL;
	} else {				/*  semi-asynch */
	    long n = mypkt->dp_Arg3 - WRITEBUFSIZE;
	    handle->Iosw.IOSer.io_Data = (APTR)mypkt->dp_Arg2;
	    handle->Iosw.IOSer.io_Length = n;
	    SendIO(&handle->Iosw);
	    movmem((char *)mypkt->dp_Arg2 + n, handle->TxOut, WRITEBUFSIZE);
	    mypkt->dp_Res1 += n;
	    handle->WPacket = mypkt;
	}
	handle->IoswIP = 1;
    }
}

void
AbortPackets(handle, myproc)
SHandle *handle;
{
    NODE *pknode;
    Packet *mypkt;

    if (handle->RPacket)
	returnpktplain(handle->RPacket, myproc);
    if (handle->WPacket)
	returnpktplain(handle->RPacket, myproc);

    handle->RPacket = NULL;
    handle->WPacket = NULL;

    while (pknode = RemHead(&handle->RxWait)) {
	mypkt = (Packet *)pknode->ln_Name;
	if (mypkt->dp_Arg3 == 0)
	    mypkt->dp_Res1 = 0;     /* for poll, return data rdy */
	else
	    mypkt->dp_Res1 = -1;
	returnpktplain(mypkt, myproc);
	AddTail(&NodList, pknode);
    }

    while (pknode = RemHead(&handle->TxWait)) {
	mypkt = (Packet *)pknode->ln_Name;
	mypkt->dp_Res1 = -1;
	returnpktplain(mypkt, myproc);
	AddTail(&NodList, pknode);
    }
}

void
StartTimer(secs)
{
    if (IotIP) {
	AbortIO(&Iot);
	WaitIO(&Iot);
    }
    Iot.tr_time.tv_secs = secs;
    Iot.tr_time.tv_micro= 0;
    SendIO(&Iot);
    IotIP = 1;
}


