/*
 *  serial.c
 *
 *  Adaptation of Iain Barclay's New8N1 for MorphOS.
 *
 *  Copyright 2000 by E. Lesueur.
 */

#define EMUL_NOQUICKMODE    1
#define DEVICES_SERIAL_H_OBSOLETE
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/execbase.h>
#include <exec/io.h>
#include <exec/ports.h>
#include <exec/errors.h>
#include <exec/resident.h>
#include <exec/initializers.h>
#include <intuition/preferences.h>
#include <devices/timer.h>
#include <devices/serial.h>
#include <hardware/cia.h>
#include <hardware/adkbits.h>
#include <hardware/intbits.h>
#include <hardware/custom.h>
#include <resources/misc.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/misc.h>


#if 0
    void dprintf(const char *,...);
#   define KPRINTF(l,x) do { if (l >= 4) { dprintf("Line %ld: ", __LINE__); dprintf x;} } while(0)
    int tbes;
    int written_chars;
    int caused_tbes;
#   define DB(x)    x
#else
#   define KPRINTF(l,x) ((void)0)
#   define DB(x)
#endif

#define ciaa (*(volatile struct CIA *)0xbfe001)
#define ciab (*(volatile struct CIA *)0xbfd000)
#define custom (*(volatile struct Custom *)0xdff000)

/*#define NEWCODE  1*/
#define SETDSR   0
#define NCOMM    0
#define NEGCHECK 1
#define EOFCODE  1

#ifndef DEVNAME
#   define DEVNAME             "serial.device"
#endif
#define VERSION             43
#define REVISION            6
#define VERSION_STR         "43"
#define REVISION_STR        "6"
#define DATE                "22.7.2000"

#define PRTMASK (CIAF_PRTRPOUT | CIAF_PRTRBUSY)
#define STR(x)  #x

struct MyDevice {
    struct Library      vb_Library;
    UWORD               vb_Pad;
    ULONG               vb_SaveDDRA;
    ULONG               vb_SavePRA;
    APTR                vb_OldLevel1;
    APTR                vb_OldLevel5;
    BPTR                vb_SegList;
    ULONG               vb_DefBaud;
    ULONG               vb_DefRBufLen;
    UBYTE              *vb_CurRBuf;
    ULONG               vb_CurRBufLen;
    ULONG               vb_CurBaud;
    ULONG               vb_SerFlags;
    ULONG               vb_Initialized;
    struct Interrupt   *vb_oldRBFInt;
    struct Interrupt   *vb_oldTBEInt;
    /*struct timerequest *timerReq;*/
};

static void timerRtn(void);

static const struct EmulLibEntry _gate_timer = {
    TRAP_LIB, 0, timerRtn
};
static struct Interrupt timerInterrupt = {
    {NULL, NULL, NT_INTERRUPT, 0, DEVNAME },
    NULL,
    (APTR)&_gate_timer
};
static struct MsgPort timerPort = {
    {NULL, NULL, NT_MSGPORT, 0, NULL},
    PA_SOFTINT, 0, (APTR)&timerInterrupt,
    {(APTR)&timerPort.mp_MsgList.lh_Tail, NULL, (APTR)&timerPort.mp_MsgList}
};
static struct timerequest timerReq = {
    {{{NULL, NULL, NT_MESSAGE, 0, NULL},
    &timerPort, sizeof(timerReq)}, NULL, NULL,
    TR_ADDREQUEST, 0, 0},
    {0, 0}
};

static volatile BOOL Overrun;
static BOOL Handshake;
static volatile int disableRead = -1;

static UBYTE *i_BufPtr;
static UBYTE *i_BufIn;
static UBYTE *i_BufOut;
static UBYTE *i_BufEnd;
static volatile LONG  i_InCnt;
static volatile LONG  i_Thresh;

static struct IOExtSer *cr_IOReq;
static UBYTE *cr_OutPtr;
static LONG cr_Length;
static struct MinList readQ = {
    (APTR)&readQ.mlh_Tail, NULL, (APTR)&readQ
};

static struct IOExtSer *cw_IOReq;
static UBYTE *cw_Buffer;
static LONG cw_Length;
static struct MinList writeQ = {
    (APTR)&writeQ.mlh_Tail, NULL, (APTR)&writeQ
};

struct ExecBase *SysBase;
struct Node *MiscBase;
struct IntuitionBase *IntuitionBase;


static struct MyDevice *devInit(struct MyDevice *base,
				BPTR seglist,
				struct ExecBase *sysbase);
static struct MyDevice *devOpen(void);
static BPTR devExpunge(void);
static BPTR devClose(void);
static void devBeginIO(void);
static void devAbortIO(void);

static void freeBuf(struct MyDevice *base);
static ULONG cmdClear(struct MyDevice *base);
static ULONG cmdInvalid(struct IOExtSer *ioreq);
static ULONG internalReset(struct MyDevice *base, ULONG buflen, ULONG baud);
static ULONG cmdReset(struct MyDevice *base, struct IOExtSer *ioreq);
static void internalFlush(struct MinList *queue);
static ULONG cmdFlush(struct IOExtSer *ioreq);
static ULONG cmdRead(struct IOExtSer *ioreq);
static void queueWrite(struct IOExtSer *ioreq);
static ULONG cmdWrite(struct IOExtSer *ioreq);
static ULONG sdcmdBreak(struct IOExtSer *ioreq);
static ULONG sdcmdQuery(struct IOExtSer *ioreq);
static ULONG sdcmdSetParams(struct MyDevice *base, struct IOExtSer *ioreq);
static void timerRtn(void);
static ULONG copyData(struct IOExtSer *ioreq);
static void level1(void);
static void level2(void);
static void level5(void);

static int devReserved(void) {
    return 0;
}

static const ULONG FuncTable[] = {
    FUNCARRAY_32BIT_NATIVE,
    (ULONG)devOpen,
    (ULONG)devClose,
    (ULONG)devExpunge,
    (ULONG)devReserved,
    (ULONG)devBeginIO,
    (ULONG)devAbortIO,
    ~0
};

const struct {
    ULONG  LibSize;
    const void  *FuncTable;
    const void  *DataTable;
    void (*InitFunc)(void);
} init = {
    sizeof(struct MyDevice),
    FuncTable,
    NULL,
    (void (*)())devInit
};

const struct Resident ROMTag = {
    RTC_MATCHWORD,
    (APTR)&ROMTag,
    (APTR)(&ROMTag + 1),
    RTF_PPC | RTF_AUTOINIT,
    VERSION,
    NT_DEVICE,
    0,
    DEVNAME,
    DEVNAME " " VERSION_STR "." REVISION_STR " (" DATE ")\r\n",
    (APTR)&init
};

const char vers[] = "$VER: " DEVNAME " " VERSION_STR "." REVISION_STR " (" DATE ")";

ULONG __amigappc__ = 1;

static const struct EmulLibEntry _gate_level2 = {
    TRAP_LIBSRNR, 0, (void (*)())level2
};

static struct Interrupt VBInterrupt = {
    {NULL, NULL, NT_INTERRUPT, 0, DEVNAME },
    &readQ,
    (APTR)&_gate_level2
};

static const struct EmulLibEntry _gate_level5 = {
    TRAP_LIBNR, 0, (void (*)())level5
};

static struct Interrupt RBFInterrupt = {
    {NULL, NULL, NT_INTERRUPT, 0, DEVNAME },
    NULL,
    (APTR)&_gate_level5
};

static const struct EmulLibEntry _gate_level1 = {
    TRAP_LIBNR, 0, (void (*)())level1
};

static struct Interrupt TBEInterrupt = {
    {NULL, NULL, NT_INTERRUPT, 0, DEVNAME },
    NULL,
    (APTR)&_gate_level1
};


static struct MyDevice *devInit(struct MyDevice *base,
				BPTR seglist,
				struct ExecBase *sysbase) {
    KPRINTF(10, ("devInit(%lx, %lx, %lx)\n", base, seglist, sysbase));
    SysBase = sysbase;
    base->vb_SegList = seglist;
    base->vb_Initialized = 0;
    base->vb_CurRBuf = NULL;
    base->vb_CurRBufLen = 0;
    KPRINTF(10, ("openCnt = %ld\n", base->vb_Library.lib_OpenCnt));
    return base;
}


static int allocResource(struct MyDevice *base, int bits) {
    UBYTE *user;
    if (user = AllocMiscResource(bits, (UBYTE *)DEVNAME)) {
	struct Device *dev = (APTR)FindName(&SysBase->DeviceList, user);
	if (dev) {
	    RemDevice(dev);
	    user = AllocMiscResource(bits, DEVNAME);
	}
    }
    return user == NULL;
}



static struct MyDevice *devOpen(void) {
    long unit = REG_D0;
    struct IOExtSer *ioreq = (APTR)REG_A1;
    struct MyDevice *base = (APTR)REG_A6;
    LONG err;
    struct MyDevice *ret = NULL;

    KPRINTF(10, ("devOpen(%lx, %lx)\n", ioreq, base));

    ++base->vb_Library.lib_OpenCnt;
    base->vb_Library.lib_Flags &= ~LIBF_DELEXP;

    KPRINTF(10, ("openCnt = %ld\n", base->vb_Library.lib_OpenCnt));

    if (ioreq->IOSer.io_Message.mn_Length < sizeof(struct IOExtSer)) {
	err = IOERR_BADLENGTH;
	KPRINTF(20, ("Bad length\n"));
	goto end;
    }

    if (unit != 0) {
	err = IOERR_OPENFAIL;
	KPRINTF(20, ("Bad unit\n"));
	goto end;
    }

    if (base->vb_Library.lib_OpenCnt == 1) {

	base->vb_SerFlags = ioreq->io_SerFlags;

	if (!base->vb_Initialized) {

	    KPRINTF(5, ("Initialization\n"));

	    MiscBase = OpenResource("misc.resource");
	    if (allocResource(base, MR_SERIALPORT)) {
		if (allocResource(base, MR_SERIALBITS)) {
		    struct Preferences prefs;
		    struct Preferences *prf;

		    if (IntuitionBase = (APTR)OpenLibrary("intuition.library", 0)) {
			static const int baudTable[] = {
			    112, 300, 1200, 2400, 4800, 9600, 19200,
			    31250, 38400, 57600, 62400, 64800,
			    76800, 115200
			};

			prf = GetPrefs(&prefs, sizeof(prefs));

			base->vb_DefBaud = baudTable[prf->BaudRate];
//#if NEWCODE
			base->vb_DefRBufLen = 512 << (prf->SerStopBuf & SBUFSIZE_BITS);
/*#else
			base->vb_DefRBufLen = ((prf->SerStopBuf & SBUFSIZE_BITS) + 8) << 2;
#endif*/
			CloseLibrary((APTR)IntuitionBase);
		    } else {
			base->vb_DefRBufLen = 4096;
			base->vb_DefBaud = 9600;
		    }

		    KPRINTF(10, ("baud = %ld, buflen = %ld\n", base->vb_DefBaud, base->vb_DefRBufLen));

		    if (err = internalReset(base, base->vb_DefRBufLen, base->vb_DefBaud)) {
			FreeMiscResource(MR_SERIALBITS);
			FreeMiscResource(MR_SERIALPORT);
			KPRINTF(20, ("BufErr \n"));
			goto end;
		    } else if (OpenDevice("timer.device", UNIT_VBLANK, &timerReq, 0)) {
			FreeMiscResource(MR_SERIALBITS);
			FreeMiscResource(MR_SERIALPORT);
			err = SerErr_DevBusy;
			KPRINTF(20, ("No timer\n"));
			goto end;
		    }

		    Disable();

		    base->vb_SaveDDRA = ciab.ciaddra;
		    base->vb_SavePRA = ciab.ciapra;
		    ciab.ciaddra = (base->vb_SaveDDRA & PRTMASK) | CIAF_COMDTR | CIAF_COMRTS;
		    ciab.ciapra = base->vb_SavePRA & (PRTMASK | CIAF_COMDSR | CIAF_COMDSR);

		    AddIntServer(INTB_PORTS, &VBInterrupt);
		    base->vb_oldRBFInt = SetIntVector(INTB_RBF, &RBFInterrupt);
		    base->vb_oldTBEInt = SetIntVector(INTB_TBE, &TBEInterrupt);

		    custom.intreq = INTF_RBF | INTF_TBE;
		    custom.intena = INTF_SETCLR | INTF_RBF | INTF_TBE;
		    ++base->vb_Initialized;

		    Enable();
		    KPRINTF(5, ("Interrupt handlers installed\n"));

		} else {
		    err = IOERR_OPENFAIL;
		    KPRINTF(20, ("No serial bits\n"));
		    goto end;
		}
	    } else {
		err = IOERR_OPENFAIL;
		KPRINTF(20, ("No serial port\n"));
		goto end;
	    }
	}
    } else if (!(base->vb_SerFlags & SERB_SHARED) || !(base->vb_SerFlags & SERB_SHARED)) {
	err = SerErr_DevBusy;
	KPRINTF(20, ("Busy\n"));
	goto end;
    }

    Handshake = (ioreq->io_SerFlags & SERF_7WIRE) != 0;

    ioreq->io_ReadLen = 8;
    ioreq->io_WriteLen = 8;
    ioreq->io_StopBits = 1;
    ioreq->io_SerFlags = (ioreq->io_SerFlags | SERF_XDISABLED | SERF_RAD_BOOGIE |
			  SERF_QUEUEDBRK /*| SERF_7WIRE*/) &
			~(SERF_PARTY_ODD | SERF_PARTY_ON);
    ioreq->io_Baud = base->vb_CurBaud;
    ioreq->io_RBufLen = base->vb_CurRBufLen;
    base->vb_Library.lib_Flags &= ~LIBF_DELEXP;
    err = 0;
    ret = base;
    ++base->vb_Library.lib_OpenCnt;
  end:
    --base->vb_Library.lib_OpenCnt;
    KPRINTF(10, ("returned: %lx, %ld\n", ret, err));
    ioreq->IOSer.io_Error = err;
    return ret;
}


static BPTR internalExpunge(struct MyDevice *base) {
    BPTR ret = 0;

    KPRINTF(5, ("internalExpunge(%lx)\n", base));

    base->vb_Library.lib_Flags |= LIBF_DELEXP;
    if (base->vb_Library.lib_OpenCnt == 0) {
	ret = base->vb_SegList;
	Remove(&base->vb_Library.lib_Node);
	FreeMem((char *)base - base->vb_Library.lib_NegSize,
		base->vb_Library.lib_NegSize + base->vb_Library.lib_PosSize);
    }

    return ret;
}

static BPTR devExpunge(void) {
    struct MyDevice *base = (APTR)REG_A6;
    return internalExpunge(base);
}

static BPTR devClose(void) {
    struct IOExtSer *ioreq = (APTR)REG_A1;
    struct MyDevice *base = (APTR)REG_A6;
    BPTR ret = 0;

    KPRINTF(10, ("devClose(%lx, %lx)\n", ioreq, base));
    KPRINTF(4, ("%ld TBEs, %ld chars written, %ld caused TBE\n", tbes, written_chars, caused_tbes));

    ioreq->IOSer.io_Device = (APTR)-1;
    if (--base->vb_Library.lib_OpenCnt == 0) {

	KPRINTF(5, ("Freeing resources.\n"));

	Disable();

	custom.intena = INTF_RBF | INTF_TBE;
	custom.intreq = INTF_RBF | INTF_TBE;
	SetIntVector(INTB_RBF, base->vb_oldRBFInt);
	SetIntVector(INTB_TBE, base->vb_oldTBEInt);
	RemIntServer(INTB_PORTS, &VBInterrupt);

	freeBuf(base);

	ciab.ciaddra |= ~PRTMASK;
	ciab.ciapra = (ciab.ciapra & PRTMASK) | (base->vb_SavePRA & ~PRTMASK);
	ciab.ciaddra = (ciab.ciaddra & PRTMASK) | (base->vb_SaveDDRA & ~PRTMASK);

	CloseDevice(&timerReq);

	FreeMiscResource(MR_SERIALBITS);
	FreeMiscResource(MR_SERIALPORT);

	Enable();

	--base->vb_Initialized;

	base->vb_SerFlags = 0;
	if (base->vb_Library.lib_Flags & LIBF_DELEXP) {
	    ret = internalExpunge(base);
	}
    }
    KPRINTF(10, ("lib_OpenCnt = %ld\n", base->vb_Library.lib_OpenCnt));

    return ret;
}


/* Reset the buffer and baud rate */

static ULONG internalReset(struct MyDevice *base, ULONG buflen, ULONG baud) {
    ULONG ret = 0;

    KPRINTF(5, ("internalReset(%lx, %ld, %ld)\n", base, buflen, baud));

    Disable();

    if (base->vb_CurBaud != baud) {
	base->vb_CurBaud = baud;
	custom.serper = (SysBase->PowerSupplyFrequency == 50 ? 24772416 : 25000000) / (baud * 7);
    }

    if (buflen <= baud / 4)
	buflen = 65536;

    if (base->vb_CurRBufLen != buflen) {
	UBYTE *p = AllocMem(buflen, MEMF_PUBLIC|MEMF_FAST);

	KPRINTF(5, ("buffer = %lx\n", p));

	if (p) {
	    freeBuf(base);
	    base->vb_CurRBuf = p;
	    base->vb_CurRBufLen = buflen;
	    cmdClear(base);
	} else
	    ret = SerErr_BufErr;
    }

    Enable();

    return ret;
}



static void internalAbortIO(struct MyDevice *base, struct IOExtSer *ioreq) {
    int abort = FALSE;

    KPRINTF(8, ("AbortIO(%lx, %lx)\n", base, ioreq));

    Disable();

    if (ioreq->IOSer.io_Flags & IOSERF_QUEUED) {
	KPRINTF(7, ("Removing from queue\n"));
	Remove(&ioreq->IOSer.io_Message.mn_Node);
	abort = TRUE;
    } else if (ioreq->IOSer.io_Flags & IOSERF_ACTIVE) {
	switch(ioreq->IOSer.io_Command) {
	  case CMD_READ:
	    KPRINTF(7, ("Aborting active read: %lx.\n", cw_IOReq));
	    cr_IOReq = NULL;
	    abort = TRUE;
	    break;

	  case CMD_WRITE:
	  case SDCMD_BREAK:
	    KPRINTF(7, ("Aborting active write: %lx, %ld\n", cw_IOReq, cw_Length));
	    cw_Length = 0;
	    cw_IOReq = NULL;
	    ioreq->IOSer.io_Actual = cw_Buffer - (UBYTE *)ioreq->IOSer.io_Data;
	    custom.intreq = INTF_SETCLR | INTF_TBE;
	    abort = TRUE;
	    break;

	}
    }

    if (abort) {
	ioreq->IOSer.io_Error = IOERR_ABORTED;
	ioreq->IOSer.io_Flags = (ioreq->IOSer.io_Flags | IOSERF_ABORT) &
			       ~(IOSERF_QUEUED | IOSERF_ACTIVE);
	if (!(ioreq->IOSer.io_Flags & IOF_QUICK)) {
	    KPRINTF(7, ("Replying \n"));
	    ReplyMsg(&ioreq->IOSer.io_Message);
	}
    }

    Enable();
}

static void devAbortIO(void) {
    struct IOExtSer *ioreq = (APTR)REG_A1;
    struct MyDevice *base = (APTR)REG_A6;
    internalAbortIO(base, ioreq);
}


/* Free internal buffer */

static void freeBuf(struct MyDevice *base) {
    if (base->vb_CurRBuf) {
	FreeMem(base->vb_CurRBuf, base->vb_CurRBufLen);
	base->vb_CurRBuf = NULL;
	base->vb_CurRBufLen = 0;
    }
}


/* Reset the serial read buffer */

static ULONG cmdClear(struct MyDevice *base) {
    KPRINTF(8, ("CMD_CLEAR\n"));
    Disable();
    i_BufPtr = base->vb_CurRBuf;
    i_BufIn = base->vb_CurRBuf;
    i_BufOut = base->vb_CurRBuf;
    i_BufEnd = base->vb_CurRBuf + base->vb_CurRBufLen;
    i_InCnt = 0;
    i_Thresh = base->vb_CurRBufLen - (base->vb_CurBaud >> 4);
    Enable();
    return 0;
}


static ULONG cmdInvalid(struct IOExtSer *ioreq) {
    KPRINTF(20, ("CMD_INVALID\n"));
    ioreq->IOSer.io_Error = IOERR_NOCMD;
    return 0;
}

static ULONG cmdReset(struct MyDevice *base, struct IOExtSer *ioreq) {
    KPRINTF(10, ("CMD_RESET\n"));
    cmdFlush(ioreq);

    Disable();

    if (cr_IOReq)
	internalAbortIO(base, cr_IOReq);

    if (cw_IOReq)
	internalAbortIO(base, cw_IOReq);

    ioreq->io_ReadLen = 8;
    ioreq->io_WriteLen = 8;
    ioreq->io_StopBits = 1;
    ioreq->io_Baud = base->vb_DefBaud;
    ioreq->io_RBufLen = base->vb_DefRBufLen;

    ioreq->IOSer.io_Error = internalReset(base, base->vb_DefRBufLen, base->vb_DefBaud);

    Enable();
    return 0;
}

static void internalFlush(struct MinList *queue) {
    struct IOExtSer *ioreq;
    struct IOExtSer *next;

    Disable();
    ioreq = (struct IOExtSer *)queue->mlh_Head;
    queue->mlh_Head = (APTR)&queue->mlh_Tail;
    queue->mlh_Tail = NULL;
    queue->mlh_TailPred = (APTR)queue;
    Enable();

    while (next = (struct IOExtSer *)ioreq->IOSer.io_Message.mn_Node.ln_Succ) {
	ioreq->IOSer.io_Flags &= ~IOSERF_QUEUED;
	ioreq->IOSer.io_Error = IOERR_ABORTED;
	ReplyMsg(&ioreq->IOSer.io_Message);
	ioreq = next;
    }
}

static ULONG cmdFlush(struct IOExtSer *ioreq) {
    KPRINTF(8, ("CMD_FLUSH\n"));
    internalFlush(&readQ);
    internalFlush(&writeQ);
    return 0;
}


static ULONG cmdRead(struct IOExtSer *ioreq) {
    ULONG ret;

    KPRINTF(3, ("CMD_READ(%lx, %ld%s) inCnt = %ld\n", ioreq, ioreq->IOSer.io_Length, ioreq->io_SerFlags & SERF_EOFMODE ? ", EOFMODE" : "", i_InCnt));

    ioreq->IOSer.io_Actual = 0;

    if (ret = ioreq->IOSer.io_Length) {

	/* This can be used to circumvent a bug in NComm 3.0 which
	 * references the buffer even when there was nothing read.
	 */
#if NCOMM
	*(UBYTE *)ioreq->IOSer.io_Data = 0;
#endif

	/* The disable counter works just like exec's TDNestCnt field.  It's
	 * initialized to -1.  After incrementing, if it is 0, then we
	 * can attempt to process this request immediately.  If it's > 0,
	 * then we're already disabled and we must queue this request.
	 */
	Disable();
	++disableRead; /* not atomic on PPC */
	Enable();

	if (disableRead <= 0) {

	    /* If we're already processing a request, this one has to wait
	     * until that one is done, so go queue it.
	     */
	    if (!cr_IOReq) {

		/* If we don't have enough bytes to satisfy this request then go
		 * queue it.
		 */
		if (i_InCnt >= ioreq->IOSer.io_Length) {

		    /* Setup fields and go copy the data */
		    cr_OutPtr = ioreq->IOSer.io_Data;
		    cr_Length = ioreq->IOSer.io_Length;
		    copyData(ioreq);

		    Disable();
		    --disableRead;
		    Enable();
		    return 0;
		}

	    }
	}

	/* Just set flags and queue.  The read interrupt will handle it. */
	ioreq->IOSer.io_Flags |= IOSERF_QUEUED;
	Disable();
	AddTail((struct List *)&readQ, &ioreq->IOSer.io_Message.mn_Node);
	--disableRead;
	Enable();
    }

    return ret;
}


static void queueWrite(struct IOExtSer *ioreq) {

    KPRINTF(2, ("queueWrite(%lx)\n", ioreq));

    Disable();

    ioreq->IOSer.io_Flags |= IOSERF_QUEUED;

    /* Add request to end of queue. */
    AddTail((struct List *)&writeQ, &ioreq->IOSer.io_Message.mn_Node);

    /* If we have an active request, don't force interrupt. */
    if (!cw_IOReq) {
	KPRINTF(1, ("causing TBE interrupt. Intenar = %lx\n", custom.intenar));
	custom.intreq = INTF_SETCLR | INTF_TBE;
	DB(++caused_tbes;)
    } else
	KPRINTF(4, ("Already a pending write: %lx, %ld\n", cw_IOReq, cw_Length));

    Enable();

}

static ULONG cmdWrite(struct IOExtSer *ioreq) {
    ULONG ret = 0;

    KPRINTF(4, ("CMD_WRITE(%lx, %ld)\n", ioreq, ioreq->IOSer.io_Length));

    ioreq->IOSer.io_Actual = 0;

    if (ioreq->IOSer.io_Length) {

#if EOFCODE
	if (ioreq->IOSer.io_Flags & SERF_EOFMODE) {
	    ioreq->IOSer.io_Error = SerErr_InvParam;
	    return 0;
	}
#endif

#if NEGCHECK
	if (ioreq->IOSer.io_Length == -1)
	    ioreq->IOSer.io_Length = strlen(ioreq->IOSer.io_Data);
#endif

	queueWrite(ioreq);
	ret = 1;
    }

    return ret;
}


static ULONG sdcmdBreak(struct IOExtSer *ioreq) {
    KPRINTF(4, ("SDCMD_BREAK\n"));
    queueWrite(ioreq);
    return 1;
}


static ULONG sdcmdQuery(struct IOExtSer *ioreq) {
    KPRINTF(2, ("SDCMD_QUERY\n"));
    Disable();
    ioreq->io_Status = ciab.ciapra & (~PRTMASK & 0xff);
#if SETDSR
    ioreq->io_Status &= ~CIAF_COMDSR;
#endif
    ioreq->IOSer.io_Actual = i_InCnt;
    Enable();
    KPRINTF(2, ("status = %lx, actual = %ld\n", ioreq->io_Status, ioreq->IOSer.io_Actual));
    return 0;
}


static ULONG sdcmdSetParams(struct MyDevice *base, struct IOExtSer *ioreq) {
    ULONG err = 0;

    KPRINTF(8, ("SDCMD_SETPARAMS: CtlChar = %08lx, ExtFlags = %08lx, SerFlags = %02lx\n"
	     "\t\tTermArray = %08lx %08lx\n",
	     ioreq->io_CtlChar, ioreq->io_ExtFlags, ioreq->io_SerFlags,
	     ioreq->io_TermArray.TermArray0, ioreq->io_TermArray.TermArray1));

    /* Validate the read, write, and stop bit lengths. */

    if (ioreq->io_ReadLen == 8 &&
	ioreq->io_WriteLen == 8 &&
	ioreq->io_StopBits == 1) {

	/*  Get and validate the baud rate. */

	int baud = ioreq->io_Baud;

	if (!baud) {
	    baud = base->vb_CurBaud;
	    if (!baud)
		baud = base->vb_DefBaud;
	}

	if (baud >= 110 && baud <= 292000) {

	    /* Get and validate the buffer length. */

	    int rlen = ioreq->io_RBufLen;

	    if (!rlen) {
		rlen = base->vb_CurRBufLen;
		if (!rlen)
		    rlen = base->vb_DefRBufLen;
	    }

	    err = internalReset(base, rlen, baud);

	    /* If the 7wire bit is not on, we will only use 3-wire protocol */

	    Handshake = (ioreq->io_SerFlags & SERF_7WIRE) != 0;

	} else
	    err = SerErr_InvParam;

    } else
	err = SerErr_InvParam;

    KPRINTF(8, ("err = %d\n", err));

    return err;
}


static void devBeginIO(void) {
    struct IOExtSer *ioreq = (APTR)REG_A1;
    struct MyDevice *base = (APTR)REG_A6;
    ULONG ret = 0;

    KPRINTF(1, ("BeginIO(%lx, %lx, %ld)\n", base, ioreq, ioreq->IOSer.io_Command));

    ioreq->IOSer.io_Message.mn_Node.ln_Type = NT_MESSAGE;
    ioreq->IOSer.io_Error = 0;
    ioreq->IOSer.io_Flags &= ~(IOSERF_QUEUED|IOSERF_ACTIVE);

    switch(ioreq->IOSer.io_Command) {
      case CMD_INVALID:
      case CMD_UPDATE:
      case CMD_STOP:
      case CMD_START:
	ret = cmdInvalid(ioreq);
	break;

      case CMD_RESET:
	ret = cmdReset(base, ioreq);
	break;

      case CMD_READ:
	ret = cmdRead(ioreq);
	break;

      case CMD_WRITE:
	ret = cmdWrite(ioreq);
	break;

      case CMD_CLEAR:
	ret = cmdClear(base);
	break;

      case CMD_FLUSH:
	ret = cmdFlush(ioreq);
	break;

      case SDCMD_QUERY:
	ret = sdcmdQuery(ioreq);
	break;

      case SDCMD_BREAK:
	ret = sdcmdBreak(ioreq);
	break;

      case SDCMD_SETPARAMS:
	ret = sdcmdSetParams(base, ioreq);
	break;

      default:
	ioreq->IOSer.io_Error = IOERR_NOCMD;
	break;
    }

    if (ret)
	ioreq->IOSer.io_Flags &= ~IOF_QUICK;
    else if (!(ioreq->IOSer.io_Flags & IOF_QUICK))
	ReplyMsg(&ioreq->IOSer.io_Message);
}


/* Checks CTS status and if clear generates a TBE interrupt or
 * requeues the timer request.
 *
 * Entered from Exec using the MsgPort callback.
 */
static void timerRtn(void) {

    KPRINTF(4, ("Time request returned\n"));

    Remove(&timerReq.tr_node.io_Message.mn_Node);

    /* If we were breaking, reset adkcon. */
    if (custom.adkconr & ADKF_UARTBRK)
	custom.adkcon = ADKF_UARTBRK;

    if (Handshake && ciab.ciapra & CIAF_COMCTS) {
	KPRINTF(4, ("Sending time request\n"));
	timerReq.tr_time.tv_micro = 1000;
	SendIO(&timerReq.tr_node);
    } else {
	custom.intreq = INTF_SETCLR | INTF_TBE;
	DB(++caused_tbes;)
    }
}



static ULONG copyData(struct IOExtSer *ioreq) {
    UBYTE *from = i_BufOut;
    LONG inCnt = i_InCnt;
    UBYTE *to = cr_OutPtr;
    LONG length = cr_Length;
    ULONG ret = 0;
    int k;

    KPRINTF(2, ("copyData(%ld, %ld)\n", inCnt, length));

    /* If we don't have enough bytes to satisfy the request,
     * set the length to the number of bytes we do have.
     */
    if (length > inCnt)
	length = inCnt;

    do {
	inCnt = length;

	/* If the copy will extend past the end of the buffer, we can
	 * only copy the number of bytes to the end this go around.
	 */
	if (inCnt > i_BufEnd - from)
	    inCnt = i_BufEnd - from;

#if EOFCODE
	if (ioreq->io_SerFlags & SERF_EOFMODE) {
	    ULONG t0 = ioreq->io_TermArray.TermArray0;
	    ULONG t1 = ioreq->io_TermArray.TermArray1;

	    for (k = 1; k <= inCnt; ++k) {
		ULONG c = *from++;
		ULONG t;

/*{static int cnt=0; if(++cnt<=1000)KPRINTF(0, ("copy %04lx: '%lc'\n", c,
					   c >= ' ' && c < 0x7f ? c : '.'));}*/
		*to++ = c;

		t = t0 >> 24;
		if (c < t) {
		    t = (UBYTE)(t0 >> 16);
		    if (c < t) {
			t = (UBYTE)(t0 >> 8);
			if (c < t) {
			    t = (UBYTE)t0;
			    if (c < t) {
				t = t1 >> 24;
				if (c < t) {
				    t = (UBYTE)(t1 >> 16);
				    if (c < t) {
					t = (UBYTE)(t1 >> 8);
					if (c < t) {
					    t = (UBYTE)t0;
					}
				    }
				}
			    }
			}
		    }
		}
		if (c == t) {
		    KPRINTF(1, ("Got break char.\n"));
		    length = k;
		    cr_Length = 0;
		    break;
		}
	    }

	    length -= k;

	} else
#endif
	       {

	    for (k = inCnt; --k >= 0; ) {
/*{static int cnt=0; if(++cnt<=1000)KPRINTF(0, ("copy %04lx: '%lc'\n", *from,
					 (UBYTE)*from >= ' ' && (UBYTE)*from < 0x7f ? (UBYTE)*from : '.'));}*/
		*to++ = *from++;
	    }

	    /* If the following calculation results in a value greater than
	     * zero, then we have a buffer wrap and need to process the
	     * remaining bytes at the beginning of the buffer.
	     */

	    length -= inCnt;
	}

	if (length > 0)
	    from = i_BufPtr;

    } while (length > 0);

    length = to - cr_OutPtr;
    cr_OutPtr = to;

    /* Update output ptr and I/O request */
    i_BufOut = from;
    ioreq->IOSer.io_Actual += length;

    Disable();
    /* Update number of bytes left in the buffer. */
    i_InCnt -= length;

    /* If the threshold becomes positive here, then, if requested,
     * tell the other end that it's okay to start sending more data.
     */

    i_Thresh += length;
    if (i_Thresh >= 0 && Handshake) {
	if (i_Thresh - length < 0)
	    KPRINTF(4, ("Clearing RTS\n"));
	ciab.ciapra &= ~CIAF_COMRTS;
    }

    /* Set error if we've had any overruns. */
    if (Overrun) {
	Overrun = FALSE;
	ioreq->IOSer.io_Error = SerErr_LineErr;
    }
    Enable();

    /* Calc number of bytes left to copy.  If the result is greater
     * than zero, then we have more to copy so return an incomplete
     * status.
     */

    cr_Length -= length;
    if (cr_Length <= 0) {
	/* We've completed the I/O request either by copying the requested
	 * of bytes or by finding an EOFMODE character, so return a "reply"
	 * status.
	 */
	 ret = 1;
    }

    KPRINTF(2, ("io_Actual = %ld, io_Error = %ld\n", ioreq->IOSer.io_Actual, ioreq->IOSer.io_Error));
    return ret;
}


static void level1(void) {

    DB(++tbes;)

    /* Clear the interrupt */
    custom.intreq = INTF_TBE;

    /* If we're not handshaking, bypass it. */
    if (!Handshake || !(ciab.ciapra & CIAF_COMCTS)) {
      start:
	--cw_Length;
	if (cw_Length < 0) {
	    /* Write request completed */

	    if (cw_IOReq) {
		/* Reply it and setup for next */
		KPRINTF(4, ("Replying CMD_WRITE %lx\n", cw_IOReq));
		cw_IOReq->IOSer.io_Flags &= ~IOSERF_ACTIVE;
		cw_IOReq->IOSer.io_Error = 0;
		ReplyMsg(&cw_IOReq->IOSer.io_Message);
	    }

	    if (writeQ.mlh_Head->mln_Succ) {

		struct IOExtSer *ioreq = (struct IOExtSer *)writeQ.mlh_Head;

		Remove(&ioreq->IOSer.io_Message.mn_Node);

		KPRINTF(1, ("Start %lx transmition\n", ioreq));

		if (ioreq->IOSer.io_Command == SDCMD_BREAK) {

		    KPRINTF(1, ("Sending break\n"));
		    cw_IOReq = ioreq;
		    cw_Length = 0;
		    ioreq->IOSer.io_Flags = (ioreq->IOSer.io_Flags & ~IOSERF_QUEUED) | IOSERF_ACTIVE;
		    custom.adkcon = ADKF_SETCLR | ADKF_UARTBRK;
		    timerReq.tr_time.tv_micro = ioreq->io_BrkTime;
		    KPRINTF(4, ("Sending time request\n"));
		    SendIO(&timerReq.tr_node);

		} else {
		    KPRINTF(1, ("write len = %ld\n", ioreq->IOSer.io_Length));
		    /* Check for absolute length */
		    cw_IOReq = ioreq;
		    cw_Buffer = ioreq->IOSer.io_Data;
		    cw_Length = ioreq->IOSer.io_Length;
		    ioreq->IOSer.io_Actual = ioreq->IOSer.io_Length;
		    ioreq->IOSer.io_Flags = (ioreq->IOSer.io_Flags & ~IOSERF_QUEUED) | IOSERF_ACTIVE;
		    goto start;
		}

	    } else {
		/* There aren't anymore requests, so clear and exit */
		cw_Length = 0;
		cw_IOReq = NULL;
		KPRINTF(4, ("Transmission finished.\n"));
	    }

	} else {
	    /* Currently processing a request. */
/*{ static int cnt=0; if(++cnt<=1000)KPRINTF(0, ("write '%lc' %lx\n", *cw_Buffer >= ' ' && *cw_Buffer < 0x7f ? *cw_Buffer : '.', *cw_Buffer));}*/
	    custom.serdat = *cw_Buffer++ | (1 << 8); /* next byte + stop bit */
	    DB(++written_chars;)
	}
    } else {
	/* Queue a timer request to recheck CTS status */
	timerReq.tr_time.tv_micro = 1000;
	KPRINTF(4, ("Sending time request\n"));
	SendIO(&timerReq.tr_node);
    }
}


static void level2(void) {
    struct List *rqueue = (APTR)REG_A1;

    /* If there's nothing in the buffer, there's no point in going
     * any further.
     */
    if (i_InCnt) {

	/* If we've been "disabled" then get out. */

	if (disableRead < 0) {
	    struct IOExtSer *ioreq;

	    /* If we have an active request, try to fulfill it. */

	    if (!cr_IOReq && rqueue->lh_Head->ln_Succ) {
		struct IOExtSer *ioreq = (struct IOExtSer *)rqueue->lh_Head;

		KPRINTF(1, ("Start filling read request\n"));

		/* Remove the node from the list */
		Remove(&ioreq->IOSer.io_Message.mn_Node);

		/* Setup fields for processing a read request */
		cr_IOReq = ioreq;
		cr_OutPtr = ioreq->IOSer.io_Data;
		cr_Length = ioreq->IOSer.io_Length;
		ioreq->IOSer.io_Flags = (ioreq->IOSer.io_Flags & ~IOSERF_QUEUED) | IOSERF_ACTIVE;
	    }

	    if (ioreq = cr_IOReq) {
		/* Process an active I/O request */
		if (cr_Length > 0) {
		    if (copyData(ioreq)) {
			cr_IOReq = NULL;
			ioreq->IOSer.io_Flags &= ~IOSERF_ACTIVE;
			KPRINTF(3, ("Replying CMD_READ %lx\n", ioreq));
			ReplyMsg(&ioreq->IOSer.io_Message);
		    }
		}
	    }
	}
    }

    REG_SR |= 4; /* Set Z flag */
}


static void level5(void) {

    UWORD dat = custom.serdatr;

    /* If we've missed some data, set overrun flag. */
    if ((WORD)dat < 0) {
	KPRINTF(20, ("****** Overrun !\n"));
	Overrun = TRUE;
    }

    /* Clear the interrupt. */
    custom.intreq = INTF_RBF;

    *i_BufIn++ = (UBYTE)dat;
    ++i_InCnt;

/*{static int cnt=0; if(++cnt<=1000)KPRINTF(0, ("read %04lx: '%c'\n", dat,
					 (UBYTE)dat >= ' ' && (UBYTE)dat < 0x7f ? (UBYTE)dat : '.'));}*/

    /* If we hit physical end of buffer, wrap to the start. */
    if (i_BufIn == i_BufEnd)
	i_BufIn = i_BufPtr;

    --i_Thresh;

    if (i_Thresh == 0) {
	/* Hit buffer threshold, so tell other end not to send any more data. */
	if (Handshake) {
	    KPRINTF(4, ("Setting RTS\n"));
	    ciab.ciapra |= CIAF_COMRTS;
	}
    }
}


