/*-
 * DEVIO.C
 *
 * The xpkdisk.device code that does the real work.
 *
 * $Id: devio.c,v 1.5 1995/04/08 20:21:52 Rhialto Exp $
 * $Log: devio.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
 * Work around horrible bug in XPK when compressing non-compressible tracks.
 * Allow users to keep old copy of track file when writing the new one fails.
 * Provide more realistic values for the disk geometry with TD_GETGEOMETRY.
 * Re-initialise more stuff (XPKD:, directories, track cache) on CMD_RESET.
 *
 * 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.
-*/

/*
 * There is a horrible bug in the xpkmaster.library version 2.4.
 * This is the one in Xpk25Usr.lha.
 * See below fow the horrid details.
 */
#define XPKBUG		1

#include <string.h>
#include <stdio.h>
#include "xpkdisk.h"
#if NOXPK
#define XPK_MARGIN	  0	/* Safety margin for output buffer	*/
#define XPKERR_OK	  0
#define XPKERR_IOERROUT    -4	/* Output error happened,look at Result2*/
#else
#define LATTICE			/* Get the #pragmas as well */
#include <libraries/xpk.h>
#undef LATTICE
#endif

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

static const char rcsId[] = "$Id: devio.c,v 1.5 1995/04/08 20:21:52 Rhialto Exp $";

Prototype void UnitSeglist(void);

Prototype void CMD_Read(struct IOStdReq *ioreq, UNIT *unit);
Prototype void CMD_Write(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Format(struct IOStdReq *ioreq, UNIT *unit);
Prototype void CMD_Reset(struct IOStdReq *ioreq, UNIT *unit);
Prototype void CMD_Update(struct IOStdReq *ioreq, UNIT *unit);
Prototype void CMD_Clear(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Seek(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Changenum(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Addchangeint(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Remchangeint(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Getgeometry(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Motor(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Remove(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Protstatus(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Changestate(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Rawread(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Rawwrite(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Getdrivetype(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Getnumtracks(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_Eject(struct IOStdReq *ioreq, UNIT *unit);
Prototype void TD_(struct IOStdReq *ioreq, UNIT *unit);

Prototype int DevInit(DEV *dev);
Prototype int DevCloseDown(DEV *dev);
Prototype UNIT *UnitInit(DEV *dev, ulong UnitNr);
Prototype int UnitCloseDown(struct IOStdReq *ioreq, DEV *dev, UNIT *unit);

struct DosLibrary   *DOSBase;
struct Library	    *XpkBase;
struct IntuitionBase *IntuitionBase;

/* ------------------------------------------------------------------------- */

void	       *
GetHead(struct MinList *list)
{
    if ((void *) list->mlh_Head != (void *) &list->mlh_Tail)
	return list->mlh_Head;
    return NULL;
}

void	       *
GetTail(struct MinList *list)
{
    if ((void *) list->mlh_Head != (void *) &list->mlh_Tail)
	return list->mlh_TailPred;
    return NULL;
}

Prototype long min(long a, long b);
long
min(long a, long b)
{
    return a < b? a: b;
}

/* ------------------------------------------------------------------------- */

struct CacheTrack {
    struct MinNode  trk_Node;
    short	    trk_Number;
    ushort	    trk_Refcount;
    int 	    trk_Size;
    byte	    trk_Data[0];
};

#define TRK_DIRTY   0x8000	/* Bit in trk_Refcount */

static const char Directory[] = XPKDISKDIR "Unit%x";

int
ReadOnly(UNIT *unit)
{
    __aligned struct InfoData infodata;
    BPTR	    fl;

    if (fl = Lock(XPKDISKDIR, SHARED_LOCK)) {
	if (Info(fl, &infodata)) {
	    unit->xu_ReadOnly = (infodata.id_DiskState != ID_VALIDATED);
	}
	UnLock(fl);
    }
    return unit->xu_ReadOnly;
}

Prototype int MakeDirectory(UNIT *unit);

int
MakeDirectory(UNIT *unit)
{
    char	    dirname[TRACKNAME_LENGTH];
    BPTR	    fl;

    sprintf(dirname, Directory, unit->xu_UnitNr);
    fl = Lock(dirname, SHARED_LOCK);
    if (fl == 0) {
	if (fl = CreateDir(dirname)) {
	    UnLock(fl); 	/* exclusive locks not invited */
	    fl = Lock(dirname, SHARED_LOCK);
	}
    }
    debug(("New currentdir: %x\n", fl));
    if (fl)
	fl = CurrentDir(fl);
    debug(("Old currentdir: %x\n", fl));
    if (fl)
	UnLock(fl);
    if (!ReadOnly(unit))
	MakeSubDirs(0, unit->xu_NumTracks - 1);
    return (int)fl;
}

/* ------------------------------------------------------------------------- */

long
ReadTrack(UNIT *unit, struct CacheTrack *trk)
{
    char	    filename[TRACKNAME_LENGTH];
    long	    res = -1;
#if NOXPK
    BPTR	    fh;
#elif DEBUG
    ULONG	    OutLen;
#endif

#if 0
    if (trk->trk_Number >= unit->xu_NumTracks) {
	unit->xu_NumTracks = trk->trk_Number + 1;
	MakeSubDirs(trk->trk_Number, trk->trk_Number);
    }
#endif
    NewName(filename, trk->trk_Number);
    debug(("ReadTrack: Open %s\n", filename));
#if NOXPK
    if (fh = Open(filename, MODE_OLDFILE)) {
	debug(("ReadTrack: Open %s succeeded\n", filename));
	res = Read(fh, trk->trk_Data, unit->xu_TrackLen);
	if (res >= 0 && res < unit->xu_TrackLen) {
	    memset(trk->trk_Data + res, 0, unit->xu_TrackLen - res);
	}
	Close(fh);
#else
    debug(("Call XpkUnpackTags\n"));
    res = XpkUnpackTags(
	    XPK_OutBuf, trk->trk_Data,
	    XPK_OutBufLen, unit->xu_TrackLen + XPK_MARGIN,
#if DEBUG
	    XPK_GetOutLen, &OutLen,
#endif
	    XPK_InName, filename,
	    XPK_PassThru, 1L,
	    TAG_DONE);
#if DEBUG
    if (OutLen != unit->xu_TrackLen) {
	debug(("XpkUnpackTags: wrong unpacked length: %d\n", OutLen));
    }
#endif
    if (res == XPKERR_OK) {
#endif
    } else {
	debug(("ReadTrack: Open %s failed\n", filename));
	memset(trk->trk_Data, 0, unit->xu_TrackLen);
    }
    debug(("res = %d\n", res));
    return res;
}

long
WriteFile(char *filename, char *buf, long length, long *ioerr)
{
    BPTR		fh;
    LONG		result;

    if (fh = Open(filename, MODE_NEWFILE)) {
	debug(("WriteFile: Open %s succeded\n", filename));
	result = Write(fh, buf, length);
	if (result != length) {
	    result = XPKERR_IOERROUT;
	    if (ioerr)
		*ioerr = IoErr();
	} else {
	    result = XPKERR_OK;
	}
	Close(fh);
    }

    return result;
}

long
WriteTrack(UNIT *unit, struct CacheTrack *trk)
{
    char	    filename[TRACKNAME_LENGTH];
    char	    backupname[TRACKNAME_LENGTH + 4];
    char	    errmsg[XPKERRMSGSIZE];
    char	   *errp = errmsg;
#define RETRY	1
#define REVERT	2
#define ABORT	0
    int 	    choice = ABORT;
    long	    res = -1;
    int 	    ioerr = 0;

    if (trk->trk_Number >= unit->xu_NumTracks) {
	unit->xu_NumTracks = trk->trk_Number + 1;
	MakeSubDirs(trk->trk_Number, trk->trk_Number);
    }

    if ((unit->xu_CacheFlags & CACHEF_SAFEWRITE) && !CheckRipcord(unit)) {
	debug(("Set WriteErr\n"));
	unit->xu_WriteErr = TDERR_TooFewSecs;
	return -1;
    }

    NewName(filename, trk->trk_Number);
    debug(("WriteTrack: %s\n", filename));

    strcpy(backupname, filename);
    strcat(backupname, ".old");
    Rename(filename, backupname);

    /* Keep trying to write the track until we succeed. */
    do {
#if !NOXPK
#if !XPKBUG
	debug(("Call XpkPackTags %s\n", unit->xu_XPKPackMethod));
	res = XpkPackTags(
		XPK_InBuf, trk->trk_Data,
		XPK_InLen, (long) unit->xu_TrackLen,
		XPK_OutName, filename,
		XPK_PackMethod, unit->xu_XPKPackMethod,
		XPK_StepDown, 1L,
		XPK_GetError, &errmsg[0],
		TAG_DONE);
#else
	struct XpkFH *xfh;
	long TrackLen = unit->xu_TrackLen;
	char *Data = trk->trk_Data;
	long now;
	long written;
	long save;		/* This is the FIX for the UGLY BUG !! */
	/*
	 * Apparently, the master library trashes the longword following
	 * the input data, if that data is not compressible.
	 * This is also true when using the NONE compression.
	 * We can save and restore this longword, because our track
	 * buffers have extra space at the end for decompressing.
	 */

	debug(("Call XpkOpenTags %s\n", unit->xu_XPKPackMethod));

	res = XpkOpenTags(&xfh,
		XPK_OutName, filename,
		XPK_PackMethod, unit->xu_XPKPackMethod,
		XPK_StepDown, 1L,
		XPK_GetError, &errmsg[0],
		XPK_InLen, TrackLen,
		XPK_ChunkSize, TrackLen,
		TAG_DONE);

	if (res == XPKERR_OK) {
	    for (written = 0; written < TrackLen; written += now, Data += now) {
		now = min(xfh->NLen, TrackLen - written);
		if (now <= 0)
		    break;

		debug(("Call XpkWrite %d\n", now));
		save = *(long *)(&Data[now]);
		res = XpkWrite(xfh, Data, now);
		*(long *)(&Data[now]) = save;

		if (res != XPKERR_OK)
		    break;
	    }

	    res = XpkClose(xfh);
	}
#endif
	if (res == XPKERR_OK)
	    trk->trk_Refcount &= ~TRK_DIRTY;
	else if ((ioerr = IoErr()), res != XPKERR_IOERROUT)
#endif
	{
	write:
	    res = WriteFile(filename, trk->trk_Data, unit->xu_TrackLen, &ioerr);
	    if (res == XPKERR_OK) {
		trk->trk_Refcount &= ~TRK_DIRTY;
	    } else {
		errp = "";
	    }
	}
    } while (res != XPKERR_OK &&
	     (choice = FullRetry(unit, filename, res, ioerr, errp)) == RETRY);

    if (res == XPKERR_OK) {
	DeleteFile(backupname);
    } else if (choice == REVERT) {
	DeleteFile(filename);
	Rename(backupname, filename);
    }
#if 0
    else if (choice == ABORT && !(trk->trk_Refcount & TRK_DIRTY)) {
	DeleteFile(backupname);
    }
#endif
    debug(("res = %d\n", res));
    return res;
}


/*
 * Find a specific track. The cache list is a Least Recently Used stack:
 * Put it on the head of the cache list. So if it is not used anymore in a
 * long time, it bubbles to the end of the list, getting a higher chance
 * of being trashed for re-use.
 */

struct CacheTrack *
FindTrackByNumber(UNIT *unit, int number)
{
    struct CacheTrack *trk;
    struct MinNode  *nexttrk;

    debug(("FindTrackByNumber %ld\n", (long)number));

    trk = (struct CacheTrack *)unit->xu_Cache.LRUList.mlh_Head;
    while (nexttrk = trk->trk_Node.mln_Succ) {
	if (trk->trk_Number == number) {
	    debug((" (%lx) %lx\n", (long)trk->trk_Refcount, trk));
	    Remove((struct Node *)&trk->trk_Node);
	    AddHead((struct List *)&unit->xu_Cache.LRUList,
		    (struct Node *)&trk->trk_Node);
	    return trk;
	}
	debug(("cache %ld %lx; ", (long)trk->trk_Number, trk));
	trk = (struct CacheTrack *)nexttrk;
    }

    return NULL;
}

/*
 * Get a fresh cache buffer. If we are allowed more cache, we just
 * allocate memory. Otherwise, we try to find a currently unused buffer.
 * We start looking at the end of the list, which is the bottom of the LRU
 * stack. If that fails, allocate more memory anyway. Not that is likely
 * anyway, since we currently lock only one track at a time.
 */

struct CacheTrack *
NewCacheTrack(UNIT *unit)
{
    struct CacheTrack *trk;
    struct MinNode  *nexttrk;

    debug(("NewCacheTrack\n"));

#define SIZE (sizeof(*trk) + unit->xu_TrackLen + XPK_MARGIN)

    if (unit->xu_CurrentCache < unit->xu_MaxCache) {
	if (trk = AllocMem(SIZE, MEMF_ANY)) {
	    goto add;
	}
    }
    for (trk = (struct CacheTrack *)unit->xu_Cache.LRUList.mlh_TailPred;
	 nexttrk = trk->trk_Node.mln_Pred;
	 trk = (struct CacheTrack *)nexttrk) {
	if ((unit->xu_CurrentCache >= unit->xu_MaxCache) &&
	    (trk->trk_Refcount == TRK_DIRTY)) {
	    debug(("NewCachetrack: dump dirty trk %d\n", trk->trk_Number));
	    FreeCacheTrack(unit, trk);	     /* Also writes it to disk */
	    continue;
	}
	if (trk->trk_Refcount == 0) {	/* Implies not TRK_DIRTY */
	    debug(("NewCachetrk: re-use clean trk %d\n", trk->trk_Number));
	    Remove((struct Node *)&trk->trk_Node);
	    goto move;
	}
    }

    trk = AllocMem(SIZE, MEMF_ANY);

    if (trk) {
add:
	trk->trk_Size = SIZE;
	unit->xu_CurrentCache++;
move:
	AddHead((struct List *) &unit->xu_Cache.LRUList,
		(struct Node *) &trk->trk_Node);
    }

    debug(("NewCacheTrack: %lx\n", trk));
    return trk;
#undef SIZE
}

/*
 * Dispose a cached track, even if it has a non-zero refcount. If it is
 * dirty, write it out.
 * If an error occurs, stop.
 */
Prototype int FreeCacheTrack(UNIT *unit, struct CacheTrack *trk);
int
FreeCacheTrack(UNIT *unit, struct CacheTrack *trk)
{
    int 	    error;

    debug(("FreeCachetrk %ld\n", (long)trk->trk_Number));

    if (trk->trk_Refcount & ~TRK_DIRTY) {
	debug(("\n\t*** PANIC!!! Refcount not 0 (%x) !!! ***\n\n", trk->trk_Refcount));
	trk->trk_Refcount &= TRK_DIRTY;
    }

    if (trk->trk_Refcount & TRK_DIRTY) {
	error = WriteTrack(unit, trk);
    } else
	error = 0;

    if (error == 0) {
	Remove((struct Node *)&trk->trk_Node);
	FreeMem(trk, trk->trk_Size);
	unit->xu_CurrentCache--;
    }

    return error;
}

/*
 * Create an empty cache
 */

void
InitCache(UNIT *unit)
{
    NewList((struct List *)&unit->xu_Cache.LRUList);
    unit->xu_MaxCache = MAX_CACHE;
    unit->xu_CurrentCache = 0;
    unit->xu_CacheDirty = 0;
    unit->xu_CacheFlags = CACHE_FLAGS;
    unit->xu_CacheTimeout = CACHE_TIMEOUT;
}

/*
 * Dispose all cached tracks, possibly writing them to disk.
 * If an error occurs, stop.
 */

int
FreeCacheList(UNIT *unit)
{
    struct CacheTrack *trk;
    int 	    error;

    debug(("FreeCacheList, %ld\n", (long)unit->xu_CurrentCache));
    while ((trk = GetHead(&unit->xu_Cache.LRUList)) &&
	   (error = FreeCacheTrack(unit, trk)) == 0) {
   }

    if (error == 0) {
	debug(("Clear WriteErr\n"));
	unit->xu_WriteErr = 0;
    }
    return error;
}

struct CacheTrack *
GetTrack(struct IOStdReq *ioreq, int track)
{
    UNIT	   *unit;
    struct CacheTrack *trk;

    debug(("GetTrack %ld\n", (long)track));
    unit = (UNIT *) ioreq->io_Unit;

    if (trk = FindTrackByNumber(unit, track)) {
	trk->trk_Refcount++;
	return trk;
    }

    if (trk = NewCacheTrack(unit)) {
	trk->trk_Refcount = 1;
	trk->trk_Number = track;
	ReadTrack(unit, trk);
	return trk;
    }

    return NULL;
}

void
MarkTrackDirty(UNIT *unit, struct CacheTrack *trk)
{
    if (trk) {
	trk->trk_Refcount |= TRK_DIRTY;
	unit->xu_CacheDirty = 1;
    }
}

/*
 * Unlock a cached track. When the usage count drops to zero, which
 * implies it is not dirty, and we are over our cache quota, the sector is
 * freed. Otherwise we keep it for re-use.
 */

void
FreeTrack(UNIT *unit, struct CacheTrack *trk)
{
    if (trk) {
	--trk->trk_Refcount;
	/*
	 * If we need to dump cache then dump some long-unused track.
	 */
	while (unit->xu_CurrentCache > unit->xu_MaxCache &&
	    (trk = GetTail(&unit->xu_Cache.LRUList)) &&
	    (trk->trk_Refcount & ~TRK_DIRTY) == 0) {
	    debug(("Freetrk: dump %s trk %d\n",
		    (trk->trk_Refcount & TRK_DIRTY)? "dirty" : "clean",
		    trk->trk_Number));
	    FreeCacheTrack(unit, trk);
	}
    }
}

/*
 * Decide if we must do an update.
 */

int
Internal_Update(UNIT *unit)
{
    struct CacheTrack *trk;
    struct MinNode *nexttrk;
    int 	    error = 0;

    debug(("Internal_Update\n"));
    /* Is the cache dirty? */
    if (unit->xu_CacheDirty == 0) {
	debug(("Cache not dirty\n"));
	goto cleartriggers;
    }

    /* Did we get a CMD_UPDATE, if required? */
    if ((unit->xu_CacheFlags & (CACHEF_CMDUPDATE|CACHEF_GOTCMDUPD)) == CACHEF_CMDUPDATE) {
	debug(("No required UPDATE\n"));
	return 0;
    }

    /* Did we get a timeout, if required? */
    if ((unit->xu_CacheFlags & (CACHEF_DELAY|CACHEF_GOTTIMEOUT)) == CACHEF_DELAY) {
	debug(("No required TIMEOUT\n"));
	return 0;
    }

    for (trk = (struct CacheTrack *)unit->xu_Cache.LRUList.mlh_TailPred;
	 nexttrk = trk->trk_Node.mln_Pred;
	 trk = (struct CacheTrack *)nexttrk) {
	if (trk->trk_Refcount & TRK_DIRTY) {
	    if (error = WriteTrack(unit, trk)) {
		goto end;	/* Don't clear update conditions */
	    }
	}
    }

    unit->xu_CacheDirty = 0;
    if (error == 0) {
	debug(("Clear WriteErr\n"));
	unit->xu_WriteErr = 0;
    }
cleartriggers:
    unit->xu_CacheFlags &= ~(CACHEF_GOTCMDUPD | CACHEF_GOTTIMEOUT);
end:
    return error;
}

/* ------------------------------------------------------------------------- */

/*
 * Read zero or more sectors from the disk and copy them into the user's
 * buffer.
 */

void
CMD_Read(struct IOStdReq *ioreq, UNIT *unit)
{
    int 	    track;
    byte	   *userbuf;
    long	    length;
    long	    offset;
    struct CacheTrack *trk;
    int 	    error;

    debug(("CMD_Read "));
    userbuf = (byte *) ioreq->io_Data;
    length = ioreq->io_Length;
    offset = ioreq->io_Offset;
    debug(("userbuf %08lx off %ld len %ld\n", userbuf, offset, length));

    track = offset / unit->xu_TrackLen;
    offset = offset % unit->xu_TrackLen;
    debug(("Tr=%ld Offset=%ld\n", (long)track, (long)offset));

    ioreq->io_Actual = length;
    error = TDERR_NoError;

    if (length <= 0)
	goto end;

    if (offset != 0) {
	/* Handle non-track  part first */
	ulong		l;

	trk = GetTrack(ioreq, track);
	if (trk == NULL) {
	    error = TDERR_NoSecHdr;
	    goto end;
	}
	l = min(length, unit->xu_TrackLen - offset);

	CopyMem(trk->trk_Data + offset, userbuf, l);
	userbuf += l;
	length -= l;
	track++;
	FreeTrack(unit, trk);
    }

    while (length > 0) {
	ulong		l;

	trk = GetTrack(ioreq, track);
	if (trk == NULL) {
	    error = TDERR_NoSecHdr;
	    goto end;
	}
	l = min(length, unit->xu_TrackLen);

	CopyMem(trk->trk_Data, userbuf, l);
	userbuf += l;
	length -= l;
	track++;
	FreeTrack(unit, trk);
    }

end:
    ioreq->io_Error = error;
    if (error != TDERR_NoError)
	ioreq->io_Actual = 0;
    TermIO(ioreq);
}

void
CMD_Write(struct IOStdReq *ioreq, UNIT *unit)
{
    int 	    track;
    byte	   *userbuf;
    long	    length;
    long	    offset;
    struct CacheTrack *trk;
    int 	    error;

    debug(("CMD_Write "));

    if (unit->xu_ReadOnly) {
	error = TDERR_WriteProt;
	goto end;
    }

    userbuf = (byte *) ioreq->io_Data;
    length = ioreq->io_Length;
    offset = ioreq->io_Offset;
    debug(("userbuf %08lx off %ld len %ld\n", userbuf, offset, length));

    track = offset / unit->xu_TrackLen;
    offset = offset % unit->xu_TrackLen;
    debug(("Tr=%ld Offset=%ld\n", (long)track, (long)offset));

    ioreq->io_Actual = length;
    error = TDERR_NoError;

    if (length <= 0)
	goto end;

    if (offset != 0) {
	/* Handle non-track aligned part first */
	ulong		l;

	trk = GetTrack(ioreq, track);
	if (trk == NULL) {
	    error = TDERR_NoSecHdr;
	    goto end;
	}
	l = min(length, unit->xu_TrackLen - offset);

	CopyMem(userbuf, trk->trk_Data + offset, l);
	userbuf += l;
	length -= l;
	track++;
	MarkTrackDirty(unit, trk);
	FreeTrack(unit, trk);
    }

    while (length > 0) {
	ulong		l;

	trk = GetTrack(ioreq, track);
	if (trk == NULL) {
	    error = TDERR_NoMem;
	    goto end;
	}
	l = min(length, unit->xu_TrackLen);

	CopyMem(userbuf, trk->trk_Data, l);
	userbuf += l;
	length -= l;
	track++;
	MarkTrackDirty(unit, trk);
	FreeTrack(unit, trk);
    }

end:
    if (error == 0 && unit->xu_WriteErr != 0) {
	/* Propagate earlier write error(s) of previous tracks */
	debug(("Use & Clear WriteErr\n"));
	error = unit->xu_WriteErr;
	unit->xu_WriteErr = 0;
    }
    ioreq->io_Error = error;
    if (error != TDERR_NoError)
	ioreq->io_Actual = 0;
    TermIO(ioreq);

    StartTimeout(unit);
}

void
TD_Format(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("CMD_Format "));
    /* keep it simple for now */
    CMD_Write(ioreq, unit);
}

void
CMD_Reset(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("CMD_Reset\n"));
#if 1
    /*
     * Somebody may have changed XPKD: to point somewhere else,
     * so push the cache and then re-establish the current dir.
     */
    FreeCacheList(unit);	/* Just to be sure */
    MakeDirectory(unit);
#endif
    MagicInit(unit);		/* Get mountlist info and user prefs */
    if (!ReadOnly(unit) && (unit->xu_CacheFlags & CACHEF_SAFEWRITE))
	MakeRipcord(unit);
    TermIO(ioreq);
}

void
CMD_Update(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("CMD_Update\n"));
    unit->xu_CacheFlags |= CACHEF_GOTCMDUPD;
    StartTimeout(unit);
    ioreq->io_Error = Internal_Update(unit);
    TermIO(ioreq);
}

void
CMD_Clear(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("CMD_Clear\n"));
    ioreq->io_Error = FreeCacheList(unit);
    TermIO(ioreq);
}

void
TD_Motor(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("TD_Motor\n"));
    ioreq->io_Actual = 1;	/* Motor is running */
    TermIO(ioreq);
}

void
TD_Return0(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("TD_Seek, TD_Changenum, TD_Changestate\n"));
    ioreq->io_Actual = 0;
    TermIO(ioreq);
}

void
TD_Protstatus(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("TD_Protstatus\n"));
    ioreq->io_Actual = ReadOnly(unit);
    TermIO(ioreq);
}

void
TD_Getnumtracks(struct IOStdReq *ioreq, UNIT *unit)
{
    debug(("TD_Getnumtracks\n"));
    ioreq->io_Actual = unit->xu_NumTracks;  /* a guess */
    TermIO(ioreq);
}

/*
 * Handle disk change interrupts. However, our disks never get taken
 * out of their drives.
 */

void
TD_Addchangeint(struct IOStdReq *ioreq, UNIT *unit)
{
    Enqueue((struct List *)&unit->xu_ChangeIntList, &ioreq->io_Message.mn_Node);
    ioreq->io_Flags &= ~IOF_QUICK;	/* So we call ReplyMsg instead of
					 * TermIO */
    /* Note no TermIO */
}

void
TD_Remchangeint(struct IOStdReq *ioreq, UNIT *unit)
{
    struct IOStdReq *intreq;

    intreq = (struct IOStdReq *) ioreq->io_Data;
    Remove(&intreq->io_Message.mn_Node);
    ReplyMsg(&intreq->io_Message);	/* Quick bit always cleared */
    ioreq->io_Error = 0;
    TermIO(ioreq);
}

void
TD_Getgeometry(struct IOStdReq *ioreq, UNIT *unit)
{
#if defined(TD_GETGEOMETRY)
    struct DriveGeometry *dg;
    short numtracks;

    debug(("TD_Getgeometry\n"));
    dg = (struct DriveGeometry *)ioreq->io_Data;

    numtracks = unit->xu_NumTracks;
    dg->dg_SectorSize = XD_BPS;

    dg->dg_Cylinders = numtracks;
    dg->dg_CylSectors = unit->xu_TrackLen / XD_BPS;

    dg->dg_TotalSectors = numtracks * dg->dg_CylSectors;

    dg->dg_Heads = 1;
    dg->dg_TrackSectors = dg->dg_CylSectors;

    dg->dg_BufMemType = MEMF_PUBLIC;
    dg->dg_DeviceType = DG_DIRECT_ACCESS;
    dg->dg_Flags = 0;		/* not DGF_REMOVABLE */
#else
    ioreq->io_Error = IOERR_NOCMD;
#endif
    TermIO(ioreq);
}

/* ------------------------------------------------------------------------- */

#define ROUNDS	    2

void
StartTimer(UNIT *unit)
{
    WaitIO(&unit->xu_TimeReq.tr_node);
    unit->xu_TimeReq.tr_node.io_Command = TR_ADDREQUEST;
    unit->xu_TimeReq.tr_time.tv_secs = unit->xu_CacheTimeout / ROUNDS;
    unit->xu_TimeReq.tr_time.tv_micro =
		    (unit->xu_CacheTimeout % ROUNDS) * (1000000 / ROUNDS);
    SendIO(&unit->xu_TimeReq.tr_node);
}

Prototype void PollTimer(UNIT *unit);
void
PollTimer(UNIT *unit)
{
    if (CheckIO(&unit->xu_TimeReq.tr_node)) {
	if (--unit->xu_TimeoutCounter <= 0) {
	    unit->xu_CacheFlags |= CACHEF_GOTTIMEOUT;
	    (void)Internal_Update(unit);
	} else {
	    StartTimer(unit);
	}
    }
}

Prototype void StartTimeout(UNIT *unit);

void
StartTimeout(UNIT *unit)
{
    unit->xu_TimeoutCounter = ROUNDS;
    if (CheckIO(&unit->xu_TimeReq.tr_node))
	StartTimer(unit);
}

int
DevInit(DEV *dev)
{
    debug(("DevInit: open dos\n"));
    DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0);

    debug(("done DevInit\n"));
    return 1;			/* Initializing succeeded */

abort:
    return DevCloseDown(dev);
}

int
DevCloseDown(dev)
DEV	       *dev;
{
    if (DOSBase) {
	CloseLibrary((struct Library *)DOSBase);
	DOSBase = NULL;
    }
    return 0;			/* Now unitialized */
}

ulong
InitMsgPort(struct MsgPort *p)
{
    p->mp_SigTask = FindTask(NULL);
    p->mp_SigBit = AllocSignal(-1);
    p->mp_Flags = PA_SIGNAL;
    Forbid();
    /*
     * We must Forbid() here to prevent a race condition. That is also
     * sufficient, since interrupts are not allowed to call BeginIO().
     */
    if (p->mp_MsgList.lh_Head == NULL)
	NewList(&p->mp_MsgList);
    Permit();

    return 1L << p->mp_SigBit;
}

UNIT	       *
UnitInit(dev, UnitNr)
DEV	       *dev;
ulong		UnitNr;
{
    UNIT	   *unit;
    struct Task    *task;
    struct MsgPort *p;

    unit = AllocMem((long) sizeof (UNIT), MEMF_PUBLIC | MEMF_CLEAR);
    if (unit == NULL)
	return NULL;

    unit->xu_UnitNr = UnitNr;
    /*
     * Now create the Unit process. Remember that it won't start running
     * since we are Forbid()den. But just to be sure, we Forbid() again.
     */
    debug(("call CreateProc %x (%x)\n",
	    MKBADDR(UnitSeglist), UnitSeglist));
    Forbid();
    p = CreateProc(DevName, TASKPRI, MKBADDR(UnitSeglist), TASKSTACK);
    if (p == NULL) {
	Permit();
	debug(("*** No Unit process!!!\n"));
	goto abort;
    }
    task = (struct Task *)(((char *)p) - offsetof(struct Process, pr_MsgPort));
    task->tc_UserData = (APTR) unit;

    unit->xu_Port.mp_Flags = PA_IGNORE;
    unit->xu_Port.mp_SigTask = task;
    NewList(&unit->xu_Port.mp_MsgList);

    Permit();
    debug(("task: %lx\n", task));

    return unit;

abort:
    UnitCloseDown(NULL, dev, unit);
    return NULL;
}

Prototype ulong UnitInit2(UNIT *unit);
ulong
UnitInit2(UNIT *unit)
{
    ulong	    waitmask;

    waitmask = InitMsgPort(&unit->xu_Port);
    waitmask |= InitMsgPort(&unit->xu_TimerPort);

    unit->xu_TimeReq.tr_node.io_Message.mn_ReplyPort = &unit->xu_TimerPort;
    if (OpenDevice("timer.device", UNIT_VBLANK, &unit->xu_TimeReq.tr_node, 0))
	return 0;
    unit->xu_TimeReq.tr_node.io_Flags = IOF_QUICK;

    NewList((struct List *)&unit->xu_ChangeIntList);
    unit->xu_TrackLen = XD_TRACKLEN;
    strncpy(unit->xu_XPKPackMethod, PACKING_METHOD, 9);
    InitCache(unit);
    MagicInit(unit);		/* Get mountlist info and user prefs */
    MakeDirectory(unit);
    if (!unit->xu_ReadOnly && (unit->xu_CacheFlags & CACHEF_SAFEWRITE))
	MakeRipcord(unit);

#if !NOXPK
    XpkBase = OpenLibrary("xpkmaster.library", 0);
    if (XpkBase == NULL)
	return 0;
#endif
    IntuitionBase = OpenLibrary("intuition.library", 33);
    if (IntuitionBase == NULL)
	return 0;

    return waitmask;
}

Prototype void UnitCloseDown2(UNIT *unit);
void
UnitCloseDown2(UNIT *unit)
{
    /*
     * There may still be dirty tracks due to delayed updates.
     * If an error occurs while writing the tracks out, we currently
     * lose the memory.
     */
    (void)FreeCacheList(unit);
    {
	struct IORequest *ioreq;
	while (ioreq = (struct IORequest *)
		       RemHead((struct List *)&unit->xu_ChangeIntList)) {
	    ReplyMsg(&ioreq->io_Message);
	}
    }

    UnLock(CurrentDir(0));

    AbortIO(&unit->xu_TimeReq.tr_node);
    WaitIO(&unit->xu_TimeReq.tr_node);
    CloseDevice(&unit->xu_TimeReq.tr_node);
    if (IntuitionBase)
	CloseLibrary(IntuitionBase);
    if (XpkBase)
	CloseLibrary(XpkBase);
}

int
UnitCloseDown(struct IOStdReq *ioreq, DEV *dev, UNIT *unit)
{
    /*
     * Get rid of the Unit's task. We know this is safe because the unit
     * has an open count of zero, so it is 'guaranteed' not in use.
     */

    if (unit->xu_Port.mp_SigTask) {
	struct IORequest io;

	io.io_Message.mn_ReplyPort = (struct MsgPort *)FindTask(NULL);
	io.io_Command = CMD_Die;
	unit->xu_Flags &= ~UNITF_STOPPED;
	debug(("Asking task to die\n"));
	PutMsg(&unit->xu_Port, &io.io_Message);
	do {
	    Wait(SIGF_SINGLE);
	    debug(("Someone exhaled...\n"));
	} while (io.io_Message.mn_Node.ln_Type != NT_REPLYMSG);
	debug(("That was the last gasp!\n"));
    }
    FreeMem(unit, (long) sizeof (UNIT));

    return 0;			/* Now unitialized */
}

/* ------------------------------------------------------------------------- */
