/*-
 * DEVICE2.C
 *
 * The xpkdisk.device code that makes it a real Exec .device.
 * Mostly based on the 1.1 RKM example and Matt Dillon's library code.
 *
 * $Id: device2.c,v 1.5 1995/04/08 20:21:52 Rhialto Exp $
 * $Log: device2.c,v $
 * Revision 1.5  1995/04/08  20:21:52  Rhialto
 * Add/correct version strings.
 *
 * Revision 1.4  1995/04/02  14:58:51  Rhialto
 * Change #ifdef into #if.
 * Update for DICE 3.0. Lots more casts.
 *
 * Revision 1.3  1993/11/08  13:11:15  Rhialto
 * Add RCS tags.
 *
 *
 * This code is (C) Copyright 1989-1995 by Olaf Seibert. All rights reserved.
 * May not be used or copied without a licence.
-*/

#include "xpkdisk.h"
#include <exec/initializers.h>

#if DEBUG
#   include "syslog.h"
#else
#   define	debug(x)
#endif
/* INDENT ON */

Prototype __geta4 DEV *Init(__A0 long segment, __D0 struct XpkDiskDevice *dev, __A6 struct ExecBase *execbase);
Prototype __geta4 void DevOpen(__D0 ulong unitno, __D1 ulong flags, __A1 struct IOStdReq *ioreq, __A6 DEV *dev);
Prototype __geta4 long DevClose(__A1 struct IOStdReq *ioreq, __A6 DEV *dev);
Prototype __geta4 long DevExpunge(__A6 DEV *dev);
Prototype __geta4 void DevBeginIO(__A1 struct IOStdReq *ioreq, __A6 DEV *dev);
Prototype __geta4 long DevAbortIO(__A1 struct IOStdReq *ioreq, __A6 DEV *dev);

Prototype void TermIO(struct IOStdReq *ioreq);
Prototype void WakePort(struct MsgPort *port);
Prototype __geta4 void UnitTask(void);
Prototype void CMD_Invalid(struct IOStdReq *ioreq, UNIT *unit);
Prototype void CMD_Stop(struct IOStdReq *ioreq, UNIT *unit);
Prototype void CMD_Start(struct IOStdReq *ioreq, UNIT *unit);
Prototype void CMD_Flush(struct IOStdReq *ioreq, UNIT *unit);

Prototype const char DevName[];
Prototype const char idString[];

const char	DevName[] = "xpkdisk.device";
const char	idString[] = "\0$VER: xpkdisk.device " STR(VERSION) "." STR(REVISION) "\r\n";
static const char rcsId[] = "$Id: device2.c,v 1.5 1995/04/08 20:21:52 Rhialto Exp $";

/*
 * Device commands:
 */

void	(*const funcTable[]) (struct IOStdReq *, UNIT *) = {
    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, TD_Getgeometry, TD_Eject,
};

#define LAST_TD_COMM	    TD_EJECT

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

/*
 * The Initialization routine is given a seglist pointer, the device
 * base pointer, and the Exec base pointer. We are being called from
 * InitResident. Exec has Forbid() for us during the call.
 */


__geta4 DEV    *
Init(segment, dev, execbase)
__A0 long	segment;
__D0 DEV       *dev;
__A6 struct ExecBase *execbase;
{
    SysBase = *(long *) 4;
#if DEBUG
    initsyslog();
    debug(("seg %lx, dev %lx, sys %lx\n", segment, dev, execbase));
#endif
    if (DevInit(dev)) {
	dev->xd_Seglist = segment;
	debug(("Init done.\n"));
	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.
 */

__geta4 void
DevOpen(unitno, flags, ioreq, dev)
__D0 ulong	unitno;
__D1 ulong	flags;
__A1 struct IOStdReq *ioreq;
__A6 DEV       *dev;
{
    UNIT	   *unit;

    debug(("OpenDevice %lx unit %ld, flags %lx\n", dev, unitno, flags));
    ++dev->dev_OpenCnt;
    if (unitno >= XD_NUMUNITS)
	goto error;

    if ((unit = dev->xd_Unit[unitno]) == NULL) {
	debug(("Call UnitInit\n"));
	if ((unit = UnitInit(dev, unitno)) == NULL)
	    goto error;
	debug(("UnitInit succeeded: %x\n", unit));
	dev->xd_Unit[unitno] = unit;
    }
    ioreq->io_Unit = (struct Unit *) unit;

    ++unit->xu_OpenCnt;
    dev->dev_Flags &= ~LIBF_DELEXP;
    ioreq->io_Error = 0;

    debug(("DevOpen: success\n"));
    return;

error:
    --dev->dev_OpenCnt;
    ioreq->io_Error = IOERR_OPENFAIL;
    debug(("DevOpen: fail\n"));
}

/*
 * 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.
 */

__geta4 long
DevClose(ioreq, dev)
__A1 struct IOStdReq *ioreq;
__A6 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.
     */

    if (unit->xu_OpenCnt && --unit->xu_OpenCnt == 0) {
	dev->xd_Unit[unit->xu_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(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.
 */

__geta4 long
DevExpunge(dev)
__A6 DEV       *dev;
{
    long	    Seglist;

    if (dev->dev_OpenCnt) {
	dev->dev_Flags |= LIBF_DELEXP;
	return NULL;
    }
    Remove(&dev->dev_Node);
    DevCloseDown(dev);          /* Should be quick! */
#if DEBUG
    uninitsyslog();
#endif
    Seglist = dev->xd_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.
 */

__geta4 void
DevBeginIO(ioreq, dev)
__A1 struct IOStdReq *ioreq;
__A6 DEV       *dev;
{
    UNIT	   *unit;

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

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

#if 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.
     */
#if 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->xu_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->xu_Flags))
	goto Immediate;
#endif

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

    return;

Immediate:
#if 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 %ld\n", ioreq, unit,
	   ioreq->io_Actual, (long)ioreq->io_Error));

#if 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->xu_Flags & UNITF_INTASK)
	goto Immediate;

    unit->xu_Flags &= ~UNITF_ACTIVE;

    /*
     * The task may have work to do that came in while we were processing
     * in the caller's context.
     */
    if (unit->xu_Flags & UNITF_WAKETASK) {
	unit->xu_Flags &= ~UNITF_WAKETASK;
	WakePort(&unit->xu_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 try to abort IO here.
 */

__geta4 long
DevAbortIO(ioreq, dev)
__A1 struct IOStdReq *ioreq;
__A6 DEV       *dev;
{
    Forbid();
    if (ioreq->io_Flags & IOF_QUICK ||
	IMMEDIATE & (1L << STRIP(ioreq->io_Command))) {
	Permit();
	return 1;
    } else {
	Remove(&ioreq->io_Message.mn_Node);
	Permit();
	ioreq->io_Error = IOERR_ABORTED;
	ReplyMsg(&ioreq->io_Message);

	return 0;
    }
}

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.
 */

__geta4 void
UnitTask(void)
{
    UNIT	   *unit;
    long	    waitmask;
    struct IOExtTD *ioreq;

    debug(("Start UnitTask\n"));
    {
	struct Task    *task;

	task = FindTask(NULL);
	unit = (UNIT *) task->tc_UserData;
	/* dev = unit->xu_Dev; */
	task->tc_UserData = NULL;
	debug(("task %lx unit %lx\n", task, unit));
    }

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

    waitmask = UnitInit2(unit);
    WakePort(&unit->xu_Port);

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

	PollTimer(unit);

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

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

	unit->xu_Flags |= UNITF_INTASK;
#endif

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

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

die:
    debug(("Aaargh! They stabbed me!\n"));
    UnitCloseDown2(unit);
    debug(("I'm gonna dieeeeeeeeeeee___________ .......\n"));
    Forbid();
    ioreq->iotd_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
    Signal((struct Task *)ioreq->iotd_Req.io_Message.mn_ReplyPort, SIGF_SINGLE);
    /* fall off the end of the world */
}

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

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

void
CMD_Start(ioreq, unit)
struct IOStdReq *ioreq;
UNIT	       *unit;
{
    unit->xu_Flags &= ~UNITF_STOPPED;
    WakePort(&unit->xu_Port);
    TermIO(ioreq);
}

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

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

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