
/*
 *  DIO.C
 *  MUST BE COMPILED USING 32 BIT INTS
 *  (C)Copyright 1987 by Matthew Dillon.
 *  Freely distributable.  Donations Welcome, I guess.
 *
 *	Matthew Dillon
 *	891 Regal Rd.
 *	Berkeley, Ca. 94708
 *
 * 
 *  EXEC device driver IO support routines... makes everything easy.
 *
 *  dfd = dio_open(name, unit, flags, req/NULL)
 *
 *	open an IO device.  Note: in some cases you might have to provide
 *	a request structure with some fields initialized (example, the
 *	console device requires certain fields to be initialized).  For
 *	instance, if openning the SERIAL.DEVICE, you would want to give
 *	an IOExtSer structure which is completely blank execept for the
 *	io_SerFlags field.
 *
 *	The request structure's message and reply ports need not be
 *	initialized.  The request structure is no longer needed after
 *	the dio_open().
 *
 *	NULL = error, else descriptor (a pointer) returned.
 *
 *
 *  dio_close(dfd)
 *
 *	close an IO device.  Any pending asyncronous requests are
 *	AbortIO()'d and then Wait'ed on for completion.
 *
 *
 *  dio_closegrp(dfd)
 *
 *	close EVERY DIO DESCRIPTOR ASSOCIATED WITH THE dio_open() call
 *	that was the parent for this descriptor.  That is, you can get
 *	a descriptor using dio_open(), dio_dup() it a couple of times,
 *	then use dio_closegrp() on any ONE of the resulting descriptors
 *	to close ALL of them.
 *
 *
 *  dio_cact(dfd,bool)
 *
 *	If an error occurs (io_Error field), the io_Actual field is usually
 *	not modified by the device driver, and thus contains garbage.  To
 *	provide a cleaner interface, you can have DIO_CTL() and DIO_CTL_TO()
 *	calls automatically pre-clear this field so if an io_Error does
 *	occur, the field is a definate 0 instead of garbage.
 *
 *	In most cases you will want to do this.  An exception is the
 *	TIMER.DEVICE, which uses the io_Actual field for part of the
 *	timeout structure.
 *
 *	This flags the particular dio descriptor to do the pre-clear, and
 *	any new descriptors obtained by DIO_DUP()ing this one will also
 *	have the pre-clear flag set.
 *
 *
 *  dio_dup(dfd)
 *
 *	Returns a new channel descriptor referencing the same device.
 *	The new descriptor has it's own signal and IO request structure.
 *	For instance, if you openned the serial device, you might want
 *	to dup the descriptor so you can use one channel to pend an
 *	asyncronous read, and the other channel to write out to the device
 *	and do other things without disturbing the asyncronous read.
 *
 *
 *  sig = dio_signal(dfd)
 *
 *	get the signal number (0..31) used for a DIO descriptor.
 *	This allows you to Wait() for asyncronous requests.  Note that
 *	if your Wait() returns, you should double check using dio_isdone()
 *
 *
 *  req = dio_ctl_to(dfd, command, buf, len, to)
 *
 *	Same as DIO_CTL() below, but (A) is always syncronous, and
 *	(B) will attempt to AbortIO()+WaitIO() the request if the
 *	timeout occurs before the IO completes.
 *
 *	the 'to' argument is in microseconds.
 *
 *
 *  req = dio_ctl(dfd, command, buf, len)
 *
 *	DIO_CTL() is the basis for the entire library.	It works as follows:
 *
 *	(1) If the channel isn't clear (there is an asyncronous IO request
 *	    still pending), DIO_CTL() waits for it to complete
 *
 *	(2) If the command is 0, simply return a pointer to the io
 *	    request structure.
 *
 *	(3) If the DIO_CACT() flag is TRUE, the io_Actual field of the
 *	    request is cleared.
 *
 *	(4) Set the io_Data field to 'buf', and io_Length field to 'len'
 *	    If the command is positive, use DoIO().  If the command
 *	    negative, take it's absolute value and then do a SendIO().
 *	    (The command is placed in the io_Command field, of course).
 *
 *	(5) return the IO request structure
 *
 *
 *  bool= dio_isdone(dfd)
 *
 *	return 1 if current channel is clear (done processing), else 0.
 *	e.g. if you did, say, an asyncronous read, and dio_isdone() returns
 *	true, you can now use the data buffer returned and look at the
 *	io_Actual field.
 *
 *	You need not do a dio_wait() after dio_isdone() returns 1.
 *
 *
 *  req = dio_wait(dfd)
 *
 *	Wait on the current channel for the request to complete and
 *	then return the request structure. (nop if channel is clear)
 *
 *
 *  req = dio_abort(dfd)
 *
 *	Abort the request on the current channel (nop if channel is
 *	clear).  Sends an AbortIO() if the channel is active and then
 *	WaitIO()'s the request.
 *
 *
 *  MACROS: SEE DIO.H
 *
 */

#include <exec/types.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <devices/timer.h>
#include "xmisc.h"

#define MPC	    (MEMF_CLEAR|MEMF_PUBLIC)
#define CPORT	    ior.ior.io_Message.mn_ReplyPort
#define MAXREQSIZE  128     /* big enough to hold all Amiga iorequests */

typedef struct IORequest IOR;
typedef struct IOStdReq  STD;
typedef struct MsgPort	 PORT;

typedef struct {
    STD ior;
    char filler[MAXREQSIZE-sizeof(STD)];
} MAXIOR;

typedef struct {
    struct _CHAN *list;
    short refs;
} DIO;

typedef struct _CHAN {
    MAXIOR  ior;
    DIO     *base;
    XLIST   link;	/* doubly linked list */
    STD     timer;
    char    notclear;
    char    cact;	/* automatic io_Actual field clear  */
} CHAN;

extern CHAN *dio_ctl(), *dio_ctl_to(), *dio_wait(), *dio_abort();
extern PORT *CreatePort();
extern char *AllocMem();

CHAN *
dio_open(name, unit, flags, req)
char *name;
MAXIOR *req;	 /* not really this big  */
{
    register CHAN *chan;
    register DIO *dio;
    register PORT *port;
    int ret;

    dio = (DIO *)AllocMem(sizeof(DIO), MPC);	if (!dio)   goto fail3;
    chan= (CHAN *)AllocMem(sizeof(CHAN), MPC);	if (!chan)   goto fail2;
    if (req)
	chan->ior = *req;
    chan->CPORT = CreatePort(NULL,0);		if (!chan->CPORT) goto fail1;
    chan->ior.ior.io_Message.mn_Node.ln_Type = NT_MESSAGE;
    chan->base = dio;
    dio->refs = 1;
    if (OpenDevice(name, unit, &chan->ior, flags)) {
	DeletePort(chan->CPORT);
fail1:	FreeMem(chan, sizeof(CHAN));
fail2:	FreeMem(dio, sizeof(DIO));
fail3:	return(NULL);
    }
    llink(&dio->list, &chan->link);
    chan->ior.ior.io_Flags = 0;
    return(chan);
}

void
dio_cact(chan,n)
CHAN *chan;
{
    if (chan)
	chan->cact = n;
}

void
dio_close(chan)
register CHAN *chan;
{
    if (chan) {
	dio_abort(chan);
	lunlink(&chan->link);
	if (--chan->base->refs == 0) {
	    FreeMem(chan->base, sizeof(DIO));
	    CloseDevice(&chan->ior);
	}
	if (chan->timer.io_Message.mn_ReplyPort)
	    CloseDevice(&chan->timer);
	DeletePort(chan->CPORT);
	FreeMem(chan, sizeof(CHAN));
    }
}


void
dio_closegroup(chan)
register CHAN *chan;
{
    register CHAN *nextc;

    for (chan = chan->base->list; chan; chan = nextc) {
	chan = (CHAN *)((char *)chan - ((char *)&chan->link - (char *)chan));
	nextc = (CHAN *)chan->link.next;
	dio_close(chan);
    }
}


CHAN *
dio_dup(chan)
register CHAN *chan;
{
    register CHAN *nc;

    if (chan) {
	nc = (CHAN *)AllocMem(sizeof(CHAN), MPC);   if (!nc) goto fail2;
	nc->ior = chan->ior;
	nc->base = chan->base;
	nc->CPORT = CreatePort(NULL,0);	if (!nc->CPORT) goto fail1;
	nc->ior.ior.io_Flags = NULL;
	nc->cact = chan->cact;
	++nc->base->refs;
	llink(&nc->base->list, &nc->link);
	return(nc);
fail1:	FreeMem(nc, sizeof(CHAN));
    }
fail2:
    return(NULL);
}


dio_signal(chan)
CHAN *chan;
{
    if (chan)
	return(chan->CPORT->mp_SigBit);
    return(-1);
}


CHAN *
dio_ctl_to(chan, com, buf, len, to)
register CHAN *chan;
char *buf;
{
    register long mask;

    if (!chan)
	return(NULL);
    if (chan->timer.io_Message.mn_ReplyPort == NULL) {
	chan->timer.io_Message.mn_ReplyPort = chan->CPORT;
	chan->timer.io_Command = TR_ADDREQUEST;
	chan->timer.io_Actual = 0;
	OpenDevice("timer.device", UNIT_VBLANK, &chan->timer);
    }
    mask = 1 << chan->CPORT->mp_SigBit;
    dio_ctl(chan, (com>0)?-com:com, buf, len);	/* SendIO the request */
    chan->timer.io_Length = to;     /* setup timer  */
    SendIO(&chan->timer);	    /* start timer  */
    while (Wait(mask)) {	    /* Wait for something */
	if (CheckIO(chan))	    /* request done  */
	    break;
	if (CheckIO(&chan->timer)) {  /* timeout?  */
	    dio_abort(chan);
	    break;
	}
    }
    AbortIO(&chan->timer);	/*  kill the timer  */
    WaitIO(&chan->timer);	/*  remove from rp  */
    return(chan);		/*  return ior	*/
}


CHAN *
dio_ctl(chan, com, buf, len)
register CHAN *chan;
char *buf;
{
    if (!chan)
	return(NULL);
    if (chan->notclear) {  /* wait previous req to finish */
	WaitIO(chan);
	chan->notclear = 0;
    }
    if (com) {
	if (chan->cact)
	chan->ior.ior.io_Actual = 0;	    /* initialize io_Actual to 0*/
	chan->ior.ior.io_Error = 0;	    /* initialize error to 0 */
	chan->ior.ior.io_Data = (APTR)buf;  /* buffer	*/
	chan->ior.ior.io_Length = len;	    /* length	*/
	if (com < 0) {			    /* asyncronous IO  */
	    chan->ior.ior.io_Command = -com;
	    chan->notclear = 1;
	    SendIO(chan);
	} else {			    /* syncronous IO  */
	    chan->ior.ior.io_Command = com;
	    DoIO(chan);
	}
    }
    return(chan);
}


CHAN *
dio_isdone(chan)
register CHAN *chan;
{
    if (!chan)
	return(NULL);
    if (chan->notclear) {	/* if not clear */
	if (CheckIO(chan)) {	/* if done	*/
	    WaitIO(chan);	/* clear	*/
	    chan->notclear = 0;
	    return(chan);	/* done	*/
	}
	 return(NULL);		/* notdone	*/
    }
    return(chan);		/* done	*/
}


CHAN *
dio_wait(chan)
register CHAN *chan;
{
    if (!chan)
	return(NULL);
    if (chan->notclear) {
	WaitIO(chan);		/* wait and remove from rp */
	chan->notclear = 0;
    }
    return(chan);
}


CHAN *
dio_abort(chan)
register CHAN *chan;
{
    if (!chan)
	return(NULL);
    if (chan->notclear) {
	AbortIO(chan);		/* Abort it   */
	WaitIO(chan);		/* wait and remove from rp */
	chan->notclear = 0;
    }
    return(chan);
}



