/*-
 * $Id: device.c,v 1.5 90/01/27 20:34:43 Rhialto Exp $
 * $Log:       device.c,v $
 * Revision 1.5  90/01/27  20:34:43  Rhialto
 * Commented out #undef DEBUG
 * 
 * Revision 1.4  90/01/23  00:24:50  Rhialto
 * io_Error=0 for immediate commands
 *
 * Revision 1.3  89/12/17  21:29:37  Rhialto
 * Revision 1.1  89/12/17  20:03:55  Rhialto
 *
 * DEVICE.C
 *
 * The messydisk.device code that makes it a real Exec .device.
 * Mostly based on the 1.1 RKM example and Matt Dillon's library code.
 *
 * This code is (C) Copyright 1989 by Olaf Seibert. All rights reserved. May
 * not be used or copied without a licence.
-*/

#include "dev.h"
#include "device.h"

/*#undef DEBUG                 /**/
#ifdef DEBUG
#   define     debug(x)  dbprintf x
#else
#   define     debug(x)
#endif

/*
 * The first executable location. This should return an error in case
 * someone tried to run you as a program (instead of loading you as a
 * device)
 */
/* INDENT OFF */
#asm
       moveq.l #20,d0
       rts
#endasm
/* INDENT ON */

/*
 * A romtag structure. Both "exec" and "ramlib" look for this structure to
 * discover magic constants about you (such as where to start running you
 * from...).
 */
/* INDENT OFF */
#asm
       public  __H0_end
_EndCode equ   __H0_end
       public  _RomTag
_RomTag:
       dc.w    $4AFC           ; RTC_MATCHWORD
       dc.l    _RomTag         ; rt_MatchTag
       dc.l    __H0_end        ; rt_EndSkip
       dc.b    0               ; rt_Flags (no RTF_AUTOINIT)
       dc.b    VERSION         ; rt_Version
       dc.b    3               ; rt_Type  NT_DEVICE
       dc.b    RTPRI           ; rt_Pri
       dc.l    _DevName        ; rt_Name
       dc.l    _idString       ; rt_IdString
       dc.l    _Init           ; rt_Init
#endasm
/* INDENT ON */

char           DevName[] = "messydisk.device";
char           idString[] = "messydisk.device $Revision: 1.5 $ $Date: 90/01/27 20:34:43 $\r\n";

/*
 * -30-6*X  Library vectors:
 */

void           (*LibVectors[]) () =
{
    _DevOpen, _DevClose, _DevExpunge, _LibNull,

    _DevBeginIO,
    _DevAbortIO,
    (void (*) ()) -1
};

/*
 * Device commands:
 */

void           (*funcTable[]) () = {
    CMD_Invalid, CMD_Reset, CMD_Read, CMD_Write, CMD_Update, CMD_Clear,
    CMD_Stop, CMD_Start, CMD_Flush, TD_Motor, TD_Seek, TD_Format,
    TD_Remove, TD_Changenum, TD_Changestate, TD_Protstatus, TD_Rawread,
    TD_Rawwrite, TD_Getdrivetype, TD_Getnumtracks, TD_Addchangeint,
    TD_Remchangeint,
};

/*
 * Here begin the system interface commands. When the user calls
 * OpenDevice/CloseDevice/RemDevice, this eventually gets trahslated into
 * a call to the following routines (Open/Close/Expunge).  Exec has
 * already put our device pointer in A6 for us.  Exec has turned off task
 * switching while in these routines (via Forbid/Permit), so we should not
 * take too long in them.
 */
/* INDENT OFF */
#asm
       public  _Init
_Init:                         ;a0=segment list
       movem.l D2-D3/A0/A6,-(sp)
       jsr     _CInit
       movem.l (sp)+,D2-D3/A0/A6
       rts

       public  __DevOpen
__DevOpen:                     ;d0=unitnum,d1=flags,a1=ioreq,a6=device
       movem.l D0-D3/A1/A6,-(sp)
       jsr     _DevOpen
       movem.l (sp)+,D0-D3/A1/A6
       rts

       public  __DevClose
__DevClose:                    ;a1=ioreq,a6=device
       movem.l D2-D3/A1/A6,-(sp)
       jsr     _DevClose
       movem.l (sp)+,D2-D3/A1/A6
       rts

       public  __DevExpunge
__DevExpunge:                  ;a6=device
       movem.l D2-D3/A6,-(sp)
       jsr     _DevExpunge
       movem.l (sp)+,D2-D3/A6
       rts

       public  __LibNull
__LibNull:
       clr.l   d0
       rts

       public  __DevBeginIO
__DevBeginIO:                  ;a1=ioreq,a6=device
       movem.l D2-D3/A1/A6,-(sp)
       jsr     _DevBeginIO
       movem.l (sp)+,D2-D3/A1/A6
       rts

       public  __DevAbortIO
__DevAbortIO:                  ;a1=ioreq,a6=device
       movem.l D2-D3/A1/A6,-(sp)
       jsr     _DevAbortIO
       movem.l (sp)+,D2-D3/A1/A6
       rts
#endasm

#ifdef HANDLE_IO_QUICK
#asm
;;;;
;
;   C interface to the atomic set bit and test old value instruction.
;
;   Called as  BSET_ACTIVE(byte *address).
;
;   Old value of the bit returned all over d0.w

_BSET_ACTIVE:
       move.l  4(sp),a0
       bset    #0,(a0)         ; UNITB_ACTIVE
       sne     d0
       rts

#endasm
#endif
/* INDENT ON */

long           SysBase;        /* Argh! A global variable! */

/*
 * The Initialization routine is given only a seglist pointer. Since we
 * are NOT AUTOINIT we must construct and add the device ourselves and
 * return either NULL or the device pointer.  Exec has Forbid() for us
 * during the call.
 *
 * If you have an extended device structure you must specify the size of the
 * extended structure in MakeLibrary().
 */

DEV           *
CInit(D2, D3, segment)
ulong          D2,
               D3;
long           segment;
{
    DEV           *dev;

    SysBase = *(long *) 4;
#ifdef DEBUG
    dbinit();
#endif
    dev = MakeLibrary(LibVectors, NULL, NULL, (long) sizeof (DEV), NULL);
    if (DevInit(dev)) {
       dev->dev_Node.ln_Type = NT_DEVICE;
       dev->dev_Node.ln_Name = DevName;
       dev->dev_Flags = LIBF_CHANGED | LIBF_SUMUSED;
       dev->dev_Version = VERSION;
       dev->dev_Revision = REVISION;
       dev->dev_IdString = (APTR) idString;
       dev->md_Seglist = segment;
       AddDevice(dev);
       return (dev);
    }
    FreeMem((char *) dev - dev->dev_NegSize, dev->dev_NegSize + dev->dev_PosSize);
    return NULL;
}

/*
 * Open is given the device pointer, unitno and flags. Either return the
 * device pointer or NULL.  Remove the DELAYED-EXPUNGE flag. Exec has
 * Forbid() for us during the call.
 */

void
DevOpen(unitno, flags, D2, D3, ioreq, dev)
ulong          unitno;
ulong          flags;
ulong          D2,
               D3;
struct IOStdReq *ioreq;
DEV           *dev;
{
    UNIT          *unit;

    debug(("OpenDevice unit %ld, flags %lx\n", unitno, flags));
    if (unitno >= MD_NUMUNITS)
       goto error;

    if ((unit = dev->md_Unit[unitno]) == NULL) {
       if ((unit = UnitInit(dev, unitno)) == NULL)
           goto error;
       dev->md_Unit[unitno] = unit;
    }
    ioreq->io_Unit = (struct Unit *) unit;

    ++unit->mu_OpenCnt;
    ++dev->dev_OpenCnt;
    dev->dev_Flags &= ~LIBF_DELEXP;

    return;

error:
    ioreq->io_Error = IOERR_OPENFAIL;
}

/*
 * Close is given the device pointer and the io request.  Be sure not to
 * decrement the open count if already zero.   If the open count is or
 * becomes zero AND there is a LIBF_DELEXP, we expunge the device and
 * return the seglist. Otherwise we return NULL.
 *
 * Note that this routine never sets LIBF_DELEXP on its own.
 *
 * Exec has Forbid() for us during the call.
 */

long
DevClose(D2, D3, ioreq, dev)
ulong          D2,
               D3;
struct IOStdReq *ioreq;
DEV           *dev;
{
    UNIT          *unit;

    unit = (UNIT *) ioreq->io_Unit;
    debug(("CloseDevice io %08lx unit %08lx\n", ioreq, unit));

    /*
     * See if the unit is still in use. If not, close it down. This may
     * need to do an update, which requires the ioreq.
     */

    if (unit->mu_OpenCnt && --unit->mu_OpenCnt == 0) {
       dev->md_Unit[unit->mu_UnitNr] = NULL;
       UnitCloseDown(ioreq, dev, unit);
    }
    /*
     * Make sure the ioreq is not used again.
     */
    ioreq->io_Unit = (void *) -1;
    ioreq->io_Device = (void *) -1;

    if (dev->dev_OpenCnt && --dev->dev_OpenCnt)
       return (NULL);
    if (dev->dev_Flags & LIBF_DELEXP)
       return (DevExpunge(D2, D3, dev));
    return (NULL);
}

/*
 * We expunge the device and return the Seglist ONLY if the open count is
 * zero. If the open count is not zero we set the DELAYED-EXPUNGE
 * flag and return NULL.
 *
 * Exec has Forbid() for us during the call.  NOTE ALSO that Expunge might be
 * called from the memory allocator and thus we CANNOT DO A Wait() or
 * otherwise take a long time to complete (straight from RKM).
 *
 * Apparently RemLibrary(lib) calls our expunge routine and would therefore
 * freeze if we called it ourselves.  As far as I can tell from RKM,
 * DevExpunge(lib) must remove the device itself as shown below.
 */

long
DevExpunge(D2, D3, dev)
ulong          D2,
               D3;
DEV           *dev;
{
    long           Seglist;

    if (dev->dev_OpenCnt) {
       dev->dev_Flags |= LIBF_DELEXP;
       return (NULL);
    }
    Remove(dev);
    DevCloseDown(dev);          /* Should be quick! */
#ifdef DEBUG
    dbuninit();
#endif
    Seglist = dev->md_Seglist;
    FreeMem((char *) dev - dev->dev_NegSize,
           (long) dev->dev_NegSize + dev->dev_PosSize);
    return (Seglist);
}

/*
 * BeginIO entry point. We don't handle any QUICK requests, we just send
 * the request to the proper unit to handle.
 */

void
DevBeginIO(D2, D3, ioreq, dev)
ulong          D2,
               D3;
register struct IOStdReq *ioreq;
DEV           *dev;
{
    UNIT          *unit;

    /*
     * Bookkeeping.
     */
    unit = (UNIT *) ioreq->io_Unit;
    debug(("BeginIO: io %08lx dev %08lx u %08lx\n", ioreq, dev, unit));

    /*
     * See if the io command is within range.
     */
    if (STRIP(ioreq->io_Command) > TD_LASTCOMM)
       goto NoCmd;

#ifdef HANDLE_IO_QUICK
    Forbid();                   /* Disable(); is a bit too strong for us. */
#endif

    /*
     * Process all immediate commands no matter what. Don't even require
     * an exclusive lock on the unit.
     */
    if (IMMEDIATE & (1L << STRIP(ioreq->io_Command)))
       goto Immediate;

    /*
     * We don't handle any QUICK I/O since that only gives trouble with
     * message ports and so. Other devices normally would include the code
     * below.
     */
#ifdef HANDLE_IO_QUICK
    /*
     * See if the user does not request QUICK IO. If not, it is likely to
     * be async and therefore we don't do it sync.
     */
    if (!(ioreq->io_Flags & IOF_QUICK))
       goto NoQuickRequested;

    /*
     * See if the unit is STOPPED. If so, queue the msg.
     */
    if (unit->mu_Flags & UNITF_STOPPED)
       goto QueueMsg;

    /*
     * This is not an immediate command. See if the device is busy. If
     * not, process the action in this (the caller's) context.
     */
    if (!BSET_ACTIVE(&unit->mu_Flags))
       goto Immediate;
#endif

    /*
     * We need to queue the device. Clear the QUICK flag.
     */
QueueMsg:
    ioreq->io_Flags &= ~IOF_QUICK;
NoQuickRequested:
#ifdef HANDLE_IO_QUICK
    Permit();                   /* Enable(); is a bit too strong for us. */
#endif
    PutMsg(&unit->mu_Port, ioreq);

    return;

Immediate:
#ifdef HANDLE_IO_QUICK
    Permit();                   /* Enable(); is a bit too strong for us. */
#endif
    debug(("BeginIO: Immediate\n"));
    ioreq->io_Error = TDERR_NoError;
    PerformIO(ioreq, unit);
    return;

NoCmd:
    ioreq->io_Error = IOERR_NOCMD;
    TermIO(ioreq);
    return;

}

/*
 * Terminate an io request. Called (normally) for every BeginIO. 'Funny'
 * commands that don't call TermIO, or call it multiple times, may not be
 * properly handled unless you are careful. TD_ADDCHANGEINT and
 * TD_REMCHANGEINT are obvious examples.
 */

void
TermIO(ioreq)
register struct IOStdReq *ioreq;
{
    register UNIT  *unit;

    unit = (UNIT *) ioreq->io_Unit;
    debug(("TermIO: io %08lx u %08lx %ld %d\n", ioreq, unit,
          ioreq->io_Actual, ioreq->io_Error));

#ifdef HANDLE_IO_QUICK
    /*
     * Since immediate commands don't even require an exclusive lock on
     * the unit, don't unlock it.
     */
    if (IMMEDIATE & (1L << STRIP(ioreq->io_Command)))
       goto Immediate;

    /*
     * We may need to turn the active (lock) bit off, but not if we are
     * within the task.
     */
    if (unit->mu_Flags & UNITF_INTASK)
       goto Immediate;

    unit->mu_Flags &= ~UNITF_ACTIVE;

    /*
     * The task may have work to do that came in while we were processing
     * in the caller's context.
     */
    if (unit->mu_Flags & UNITF_WAKETASK) {
       unit->mu_Flags &= ~UNITF_WAKETASK;
       WakePort(&unit->mu_Port);
    }
#endif

Immediate:
    /*
     * If the quick bit is still set then wen don't need to reply the msg
     * -- just return to the user.
     */

    if (!(ioreq->io_Flags & IOF_QUICK))
       ReplyMsg(&ioreq->io_Message);

    return;
}

/*
 * AbortIO entry point. We don't abort IO here.
 */

long
DevAbortIO(D2, D3, ioreq, dev)
ulong          D2,
               D3;
DEV           *dev;
struct IOStdReq *ioreq;
{
    return 1;
}

void
WakePort(port)
register struct MsgPort *port;
{
    Signal(port->mp_SigTask, 1L << port->mp_SigBit);
}

/*
 * This is the main loop of the Unit tasks. It must be very careful with
 * global data.
 */

void
UnitTask()
{
    /* DEV *dev; */
    UNIT          *unit;
    long           waitmask;
    struct IOExtTD *ioreq;

    {
       struct Task    *task,
                      *FindTask();

       task = FindTask(NULL);
       unit = (UNIT *) task->tc_UserData;
       /* dev = unit->mu_Dev; */
       task->tc_UserData = NULL;
    }

    /*
     * Now finish initializing the message ports and other signal things
     */

    {
       byte            sigbit;

       unit->mu_DiskReplyPort.mp_SigBit = AllocSignal(-1L);
       unit->mu_DiskReplyPort.mp_Flags = PA_SIGNAL;

       sigbit = AllocSignal(-1L);
       unit->mu_Port.mp_SigBit = sigbit;
       unit->mu_Port.mp_Flags = PA_SIGNAL;
       waitmask = 1L << sigbit;

       unit->mu_DmaSignal = AllocSignal(-1L);
    }

    for (;;) {
       debug(("Task: Waiting... "));
       Wait(waitmask);

       /*
        * See if we are stopped.
        */
       if (unit->mu_Flags & UNITF_STOPPED)
           continue;

#ifdef HANDLE_IO_QUICK
       /*
        * Lock the device. If it fails, we have set a flag such that the
        * TermIO wakes us again.
        */
       unit->mu_Flags |= UNITF_WAKETASK;
       if (BSET_ACTIVE(&unit->mu_Flags))
           continue;

       unit->mu_Flags |= UNITF_INTASK;
#endif

       while (ioreq = (struct IOExtTD *) GetMsg(&unit->mu_Port)) {
           debug(("Task: io %08lx %x\n", ioreq, ioreq->iotd_Req.io_Command));
           ioreq->iotd_Req.io_Error = 0;
           PerformIO((&ioreq->iotd_Req), unit);
       }

#ifdef HANDLE_IO_QUICK
       unit->mu_Flags &= ~(UNITF_ACTIVE | UNITF_INTASK | UNITF_WAKETASK);
#endif
    }
}

void
CMD_Invalid(ioreq, unit)
struct IOStdReq *ioreq;
UNIT          *unit;
{
    ioreq->io_Error = IOERR_NOCMD;
    TermIO(ioreq);
}

void
CMD_Stop(ioreq, unit)
struct IOExtTD *ioreq;
UNIT          *unit;
{
    unit->mu_Flags |= UNITF_STOPPED;
    TermIO(ioreq);
}

void
CMD_Start(ioreq, unit)
struct IOExtTD *ioreq;
UNIT          *unit;
{
    unit->mu_Flags &= ~UNITF_STOPPED;
    WakePort(&unit->mu_Port);
    TermIO(ioreq);
}

void
CMD_Flush(ioreq, unit)
struct IOExtTD *ioreq;
UNIT          *unit;
{
    register struct IOStdReq *req;

    /* Flush our own command queue */
    Forbid();
    while (req = (struct IOStdReq *) GetMsg(unit->mu_Port)) {
       req->io_Error = IOERR_ABORTED;
       ReplyMsg(&req->io_Message);
    }
    Permit();

    WakePort(&unit->mu_Port);
    TermIO(ioreq);
}

void
TrackdiskGateway(ioreq, unit)
register struct IOExtTD *ioreq;
UNIT          *unit;
{
    register struct IOExtTD *tdioreq;

    debug(("Trackdisk: %x ", ioreq->iotd_Req.io_Command));
    tdioreq = unit->mu_DiskIOReq;

    /*
     * Clone almost the entire io request to relay to the
     * trackdisk.device.
     */

    tdioreq->iotd_Req.io_Command = ioreq->iotd_Req.io_Command;
    tdioreq->iotd_Req.io_Flags = ioreq->iotd_Req.io_Flags | IOF_QUICK;
    tdioreq->iotd_Req.io_Length = ioreq->iotd_Req.io_Length;
    tdioreq->iotd_Req.io_Data = ioreq->iotd_Req.io_Data;
    tdioreq->iotd_Req.io_Offset = ioreq->iotd_Req.io_Offset;
    if (ioreq->iotd_Req.io_Command & TDF_EXTCOM) {
       tdioreq->iotd_Count = ioreq->iotd_Count;
       tdioreq->iotd_SecLabel = ioreq->iotd_SecLabel;
    }
    BeginIO(tdioreq);
    WaitIO(tdioreq);

    ioreq->iotd_Req.io_Error = tdioreq->iotd_Req.io_Error;
    ioreq->iotd_Req.io_Actual = tdioreq->iotd_Req.io_Actual;

    TermIO(ioreq);
}

#ifdef DEBUG
/* DEBUGGING                       */
struct MsgPort *Dbport;        /* owned by the debug process       */
struct MsgPort *Dback;         /* owned by the DOS device driver  */
short          DBEnable;
struct SignalSemaphore PortUse;

#define CTOB(x) (void *)(((long)(x))>>2)        /* BCPL conversion */

/*
 * DEBUGGING CODE.     You cannot make DOS library calls that access
 * other devices from within a device driver because the caller may not be
 * a process.  If you need to make such calls you must create a port and
 * construct the DOS messages yourself.  I do not do this.  To get
 * debugging info out another PROCESS is created to which debugging
 * messages can be sent. The replyport gets a new SigTask for every
 * dbprintf call, therefore the semaphore.
 */

extern void    debugproc();
struct Library *DOSBase,
              *OpenLibrary();

dbinit()
{
    struct Task    *task = FindTask(NULL);

    DOSBase = OpenLibrary("dos.library", 0L);
    Dback = CreatePort("Dback", -1L);
    FreeSignal((long) Dback->mp_SigBit);
    Dback->mp_SigBit = 2;
    InitSemaphore(&PortUse);
    CreateProc("messydisk_DB", (long) TASKPRI + 1, CTOB(debugproc), 2000L);
    WaitPort(Dback);            /* handshake startup    */
    GetMsg(Dback);              /* remove dummy msg     */
    DBEnable = 1;
    dbprintf("Debugger running V1.11\n");
}

dbuninit()
{
    struct Message  killmsg;

    if (Dbport) {
       killmsg.mn_Length = 0;  /* 0 means die      */
       ObtainSemaphore(&PortUse);
       Dback->mp_SigTask = FindTask(NULL);
       PutMsg(Dbport, &killmsg);
       WaitPort(Dback);        /* He's dead jim!      */
       GetMsg(Dback);
       ReleaseSemaphore(&PortUse);
       Dback->mp_SigBit = -1;
       DeletePort(Dback);

       /*
        * Since the debug process is running at a greater priority, I am
        * pretty sure that it is guarenteed to be completely removed
        * before this task gets control again.
        */
    }
    CloseLibrary(DOSBase);
}

dbprintf(a, b, c, d, e, f, g, h, i, j)
long           a, b, c, d, e, f, g, h, i, j;
{
    struct {
       struct Message  msg;
       char            buf[256];
    }              msgbuf;
    register struct Message *msg = &msgbuf.msg;
    register long   len;

    if (Dbport && DBEnable) {
       ObtainSemaphore(&PortUse);      /* sprintf is not re-entrant */
       sprintf(msgbuf.buf, a, b, c, d, e, f, g, h, i, j);
       len = strlen(msgbuf.buf) + 1;
       msg->mn_Length = len;   /* Length NEVER 0        */
       Dback->mp_SigTask = FindTask(NULL);
       PutMsg(Dbport, msg);
       WaitPort(Dback);
       GetMsg(Dback);
       ReleaseSemaphore(&PortUse);
    }
}

/*
 * BTW, the DOS library used by debugmain() was actually opened by the
 * opener of the device driver.
 */

debugmain()
{
    register struct Message *msg;
    register long   len;
    register void  *fh,
                  *Open();
    void          *fh2;
    struct Message  DummyMsg;

    Dbport = CreatePort("Dbport", -1L);
    fh = Open("CON:0/20/640/101/Device debug", MODE_NEWFILE);
    fh2 = Open("PAR:", MODE_OLDFILE);
    PutMsg(Dback, &DummyMsg);
    for (;;) {
       WaitPort(Dbport);
       msg = GetMsg(Dbport);
       len = msg->mn_Length;
       if (len == 0)
           break;
       --len;                  /* Fix length up         */
       if (DBEnable & 1)
           Write(fh, msg + 1, len);
       if (DBEnable & 2)
           Write(fh2, msg + 1, len);
       PutMsg(Dback, msg);
    }
    Close(fh);
    Close(fh2);
    DeletePort(Dbport);
    PutMsg(Dback, msg);         /* Kill handshake  */
}

/*
 * The assembly tag for the DOS process:  CNOP causes alignment problems
 * with the Aztec assembler for some reason.  I assume then, that the
 * alignment is unknown.  Since the BCPL conversion basically zero's the
 * lower two bits of the address the actual code may start anywhere within
 * 8 bytes of address (remember the first longword is a segment pointer
 * and skipped).  Sigh....  (see CreateProc() above).
 */
/* INDENT OFF */
#asm
       public  _debugproc
       public  _debugmain

       cseg
_debugproc:
       nop
       nop
       nop
       nop
       nop
       movem.l D2-D7/A2-A6,-(sp)
       jsr     _debugmain
       movem.l (sp)+,D2-D7/A2-A6
       rts
#endasm

#endif                         /* DEBUG */
