/*-
 * $Id: hansec.c,v 1.30 90/06/04 23:17:02 Rhialto Rel $
 * $Log:	hansec.c,v $
 * Revision 1.30  90/06/04  23:17:02  Rhialto
 * Release 1 Patch 3
 * 
 * HANSEC.C
 *
 * The code for the messydos file system handler.
 *
 * Sector-level stuff: read, write, cache, unit conversion.
 * Other interactions (via MyDoIO) with messydisk.device.
 *
 * This code is (C) Copyright 1989,1990 by Olaf Seibert. All rights reserved.
 * May not be used or copied without a licence.
-*/

#include "dos.h"
#include "han.h"

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

struct MsgPort *DiskReplyPort;
struct IOExtTD *DiskIOReq;
struct IOStdReq *DiskChangeReq;

struct DiskParam Disk;
byte	       *Fat;
short		FatDirty;	/* Fat must be written to disk */

short		error;		/* To put the error value; for Result2 */
long		IDDiskState;	/* InfoData.id_DiskState */
long		IDDiskType;	/* InfoData.id_DiskType */
struct timerequest *TimeIOReq;	/* For motor-off delay */
struct MinList	CacheList;	/* Sector cache */
int		CurrentCache;	/* How many cached buffers do we have */
int		MaxCache;	/* Maximum amount of cached buffers */
long		CacheBlockSize; /* Size of disk block + overhead */
ulong		BufMemType;
int		DelayState;
short		CheckBootBlock; /* Do we need to check the bootblock? */

word
Get8086Word(Word8086)
register byte  *Word8086;
{
    return Word8086[0] | Word8086[1] << 8;
}

word
OtherEndianWord(oew)
word		oew;
{
/* INDENT OFF */
#asm
	move.w	8(a5),d0
	rol.w	#8,d0
#endasm
    /* INDENT ON */
    /*
     * return (oew << 8) | ((oew >> 8) & 0xff);
     */
}

ulong
OtherEndianLong(oel)
ulong		oel;
{
/* INDENT OFF */
#asm
	move.l	8(a5),d0
	rol.w	#8,d0
	swap	d0
	rol.w	#8,d0
#endasm
    /* INDENT ON */
    /*
     * return ((oel &       0xff) << 24) | ((oel &     0xff00) <<  8) |
     * ((oel &   0xff0000) >>  8) | ((oel & 0xff000000) >> 24);
     */
}

void
OtherEndianMsd(msd)
register struct MsDirEntry *msd;
{
    msd->msd_Date = OtherEndianWord(msd->msd_Date);
    msd->msd_Time = OtherEndianWord(msd->msd_Time);
    msd->msd_Cluster = OtherEndianWord(msd->msd_Cluster);
    msd->msd_Filesize = OtherEndianLong(msd->msd_Filesize);
}

word
ClusterToSector(cluster)
register word	cluster;
{
    return cluster ? Disk.start + cluster * Disk.spc
	: 0;
}

word
ClusterOffsetToSector(cluster, offset)
register word	cluster;
register word	offset;
{
    return cluster ? Disk.start + cluster * Disk.spc + offset / Disk.bps
	: 0;
}

word
DirClusterToSector(cluster)
register word	cluster;
{
    return cluster ? Disk.start + cluster * Disk.spc
	: Disk.rootdir;
}

word
SectorToCluster(sector)
register word	sector;
{
    return sector ? (sector - Disk.start) / Disk.spc
	: 0;
}

/*
 * Get the next cluster in a chain. Sort-of checks for special entries.
 */

word
NextCluster(cluster)
word cluster;
{
    register word entry;

    return (entry = GetFatEntry(cluster)) >= 0xFFF7 ? FAT_EOF : entry;
}

word
NextClusteredSector(sector)
word		sector;
{
    word	    next = (sector + 1 - Disk.start) % Disk.spc;

    if (next == 0) {
	next = NextCluster(SectorToCluster(sector));
	return next != FAT_EOF ? ClusterToSector(next)
	    : SEC_EOF;
    } else
	return sector + 1;
}

#ifndef READONLY

word
FindFreeSector(prev)
word		prev;
{
    word freecluster = FindFreeCluster(SectorToCluster(prev));

    return freecluster == FAT_EOF ? SEC_EOF : ClusterToSector(freecluster);
}

#endif

/*
 * Find a specific sector. 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 CacheSec *
FindSecByNumber(number)
register int	number;
{
    register struct CacheSec *sec;
    register struct CacheSec *nextsec;

    debug(("FindSecByNumber %d", number));

    for (sec = (void *) CacheList.mlh_Head;
	 nextsec = (void *) sec->sec_Node.mln_Succ; sec = nextsec) {
	if (sec->sec_Number == number) {
	    debug((" (%x) %lx\n", sec->sec_Refcount, sec));
	    Remove(sec);
	    AddHead(&CacheList, &sec->sec_Node);
	    return sec;
	}
    }

    debug(("; "));
    return NULL;
}

struct CacheSec *
FindSecByBuffer(buffer)
byte	       *buffer;
{
    return (struct CacheSec *) (buffer - OFFSETOF(CacheSec, sec_Data));
}

/*
 * 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 sector at a time.
 */

struct CacheSec *
NewCacheSector()
{
    register struct CacheSec *sec;
    register struct CacheSec *nextsec;

    debug(("NewCacheSector\n"));

    if (CurrentCache < MaxCache) {
	if (sec = AllocMem(CacheBlockSize, BufMemType)) {
	    goto add;
	}
    }
    for (sec = (void *) CacheList.mlh_TailPred;
	 nextsec = (void *) sec->sec_Node.mln_Pred; sec = nextsec) {
	if ((CurrentCache >= MaxCache) && (sec->sec_Refcount == SEC_DIRTY)) {
	    FreeCacheSector(sec);       /* Also writes it to disk */
	    continue;
	}
	if (sec->sec_Refcount == 0)     /* Implies not SEC_DIRTY */
	    return sec;
    }

    sec = AllocMem(CacheBlockSize, BufMemType);

    if (sec) {
add:
	CurrentCache++;
	AddHead(&CacheList, &sec->sec_Node);
    } else
	error = ERROR_NO_FREE_STORE;

    return sec;
}

/*
 * Dispose a cached sector, even if it has a non-zero refcount. If it is
 * dirty, write it out.
 */

void
FreeCacheSector(sec)
register struct CacheSec *sec;
{
    debug(("FreeCacheSector %d\n", sec->sec_Number));
    Remove(sec);
#ifndef READONLY
    if (sec->sec_Refcount & SEC_DIRTY) {
	PutSec(sec->sec_Number, sec->sec_Data);
    }
#endif
    FreeMem(sec, CacheBlockSize);
    CurrentCache--;
}

/*
 * Create an empty cache list
 */

void
InitCacheList()
{
    extern struct CacheSec *sec;    /* Of course this does not exist... */

    NewList(&CacheList);
    CurrentCache = 0;
    CacheBlockSize = Disk.bps + sizeof (*sec) - sizeof (sec->sec_Data);
}

/*
 * Dispose all cached sectors, possibly writing them to disk.
 */

void
FreeCacheList()
{
    register struct CacheSec *sec;

    debug(("FreeCacheList, %d\n", CurrentCache));
    while (sec = GetHead(&CacheList)) {
	FreeCacheSector(sec);
    }
}

/*
 * Do an insertion sort on tosort in the CacheList. Since it changes the
 * location in the list, you must fetch it before calling this routine.
 * The list will become ascending.
 */

void
SortSec(tosort)
register struct CacheSec *tosort;
{
    register struct CacheSec *sec;
    struct CacheSec *nextsec;
    register word   secno;

    secno = tosort->sec_Number;
    debug(("SortSec %d: ", secno));

    for (sec = (void *) CacheList.mlh_Head;
	 nextsec = (void *) sec->sec_Node.mln_Succ; sec = nextsec) {
	debug(("%d, ", sec->sec_Number));
	if (sec == tosort) {
	    debug(("\n"));
	    return;			/* No need to move it away */
	}
	if (sec->sec_Number > secno)
	    break;
    }
    /* Insert before sec */
    Remove(tosort);
    Insert(&CacheList, tosort, sec->sec_Node.mln_Pred);
    debug(("\n"));
}

/*
 * Write all dirty cache buffers to disk. They are written from highest to
 * lowest, and then the FAT is written out.
 */

void
MSUpdate(immediate)
int		immediate;
{
    register struct CacheSec *sec;
    register struct CacheSec *nextsec;

    debug(("MSUpdate\n"));

#ifndef READONLY
    if (DelayState & DELAY_DIRTY) {
	/*
	 * First sort all dirty sectors on block number
	 */
	for (sec = (void *) CacheList.mlh_Head;
	     nextsec = (void *) sec->sec_Node.mln_Succ; sec = nextsec) {
	    if (sec->sec_Refcount & SEC_DIRTY) {
		SortSec(sec);
	    }
	}
	/*
	 * Then do a second (backward) scan to write them out.
	 */
	for (sec = (void *) CacheList.mlh_TailPred;
	     nextsec = (void *) sec->sec_Node.mln_Pred; sec = nextsec) {
	    if (sec->sec_Refcount & SEC_DIRTY) {
		PutSec(sec->sec_Number, sec->sec_Data);
		sec->sec_Refcount &= ~SEC_DIRTY;
	    }
	}
	DelayState &= ~DELAY_DIRTY;
    }
    if (FatDirty) {
	WriteFat();
    }
#endif

    if (immediate)
	DelayState = DELAY_RUNNING1;

    if (DelayState & DELAY_RUNNING2) {
	StartTimer();
	DelayState &= ~DELAY_RUNNING2;
    } else {			/* DELAY_RUNNING1 */
#ifndef READONLY
	while (TDUpdate() != 0 && RetryRwError(DiskIOReq))
	    ;
#endif
	TDMotorOff();
	DelayState = DELAY_OFF;
    }
}

/*
 * Start the timer which triggers cache writing and stopping the disk
 * motor.
 */

void
StartTimer()
{
    DelayState |= DELAY_RUNNING1 | DELAY_RUNNING2;

    if (CheckIO(TimeIOReq)) {
	WaitIO(TimeIOReq);
	TimeIOReq->tr_node.io_Command = TR_ADDREQUEST;
	TimeIOReq->tr_time.tv_secs = 3;
	TimeIOReq->tr_time.tv_micro = 0;
	SendIO(TimeIOReq);
    }
}

/*
 * Get a pointer to a logical sector { 0, ..., MS_SECTCNT - 1}. We
 * allocate a buffer and copy the data in, and lock the buffer until
 * FreeSec() is called.
 */

byte	       *
GetSec(sector)
int		sector;
{
    struct CacheSec *sec;

#ifdef HDEBUG
    if (sector == 0) {
	debug(("************ GetSec(0) ***************\n"));
    }
#endif

    if (sec = FindSecByNumber(sector)) {
	sec->sec_Refcount++;

	return sec->sec_Data;
    }
    if (sec = NewCacheSector()) {
	register struct IOExtTD *req;

	sec->sec_Number = sector;
	sec->sec_Refcount = 1;

	debug(("GetSec %d\n", sector));

	req = DiskIOReq;
	do {
	    req->iotd_Req.io_Command = ETD_READ;
	    req->iotd_Req.io_Data = (APTR)sec->sec_Data;
	    req->iotd_Req.io_Offset = Disk.lowcyl + (long) sector * Disk.bps;
	    req->iotd_Req.io_Length = Disk.bps;
	    MyDoIO(req);
	} while (req->iotd_Req.io_Error != 0 && RetryRwError(req));

	StartTimer();

	if (req->iotd_Req.io_Error == 0) {
	    return sec->sec_Data;
	}
	error = ERROR_NOT_A_DOS_DISK;
	FreeCacheSector(sec);
    }
    return NULL;
}

#ifndef READONLY

byte	       *
EmptySec(sector)
int		sector;
{
    byte	   *buffer;
    register struct CacheSec *sec;

#ifdef HDEBUG
    if (sector == 0) {
	debug(("************ EmptySec(0) ***************\n"));
    }
#endif
    if (sec = FindSecByNumber(sector)) {
	sec->sec_Refcount++;

	return sec->sec_Data;
    }
    if (sec = NewCacheSector()) {
	sec->sec_Number = sector;
	sec->sec_Refcount = 1;

	return sec->sec_Data;
    }

    return NULL;
}

void
PutSec(sector, data)
int		sector;
byte	       *data;
{
    register struct IOExtTD *req;

    debug(("PutSec %d\n", sector));

    req = DiskIOReq;
    do {
	req->iotd_Req.io_Command = ETD_WRITE;
	req->iotd_Req.io_Data = (APTR) data;
	req->iotd_Req.io_Offset = Disk.lowcyl + (long) sector * Disk.bps;
	req->iotd_Req.io_Length = Disk.bps;
	MyDoIO(req);
    } while (req->iotd_Req.io_Error != 0 && RetryRwError(req));

    StartTimer();
}

#endif

/*
 * Unlock a cached sector. 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
FreeSec(buffer)
byte	       *buffer;
{
    register struct CacheSec *sec;

    if (sec = FindSecByBuffer(buffer)) {
#ifdef HDEBUG
	if (sec->sec_Number == 0) {
	    debug(("************ FreeSec(0) ***************\n"));
	}
#endif
	if (--sec->sec_Refcount == 0) { /* Implies not SEC_DIRTY */
	    if (CurrentCache > MaxCache) {
		FreeCacheSector(sec);
	    }
	}
    }
}

#ifndef READONLY

void
MarkSecDirty(buffer)
byte	       *buffer;
{
    register struct CacheSec *sec;

    if (sec = FindSecByBuffer(buffer)) {
	sec->sec_Refcount |= SEC_DIRTY;
	DelayState |= DELAY_DIRTY;
	StartTimer();
    }
}

/*
 * Write out the FAT. Called from MSUpdate(), so don't call it again from
 * here. Don't use precious cache space for it; you could say it has its
 * own private cache already.
 */

void
WriteFat()
{
    register int    fat,
		    sec;
    int 	    disksec = Disk.res;      /* First FAT, first sector */

    /* Write all FATs */
    for (fat = 0; fat < Disk.nfats; fat++) {
	for (sec = 0; sec < Disk.spf; sec++) {
	    PutSec(disksec++, Fat + sec * Disk.bps);
	    /* return;	       /* Fat STILL dirty! */
	}
    }
    FatDirty = FALSE;
}

#endif

int
AwaitDFx()
{
    debug(("AwaitDFx\n"));
    if (DosType) {
	static char	dfx[] = "DFx:";
	void	       *dfxProc,
		       *DeviceProc();
	char		xinfodata[sizeof(struct InfoData) + 3];
	struct InfoData *infoData;
	int		triesleft;

	dfx[2] = '0' + UnitNr;
	infoData = (struct InfoData *)(((long)&xinfodata[3]) & ~3L);

	for (triesleft = 10; triesleft; triesleft--) {
	    debug(("AwaitDFx %d\n", triesleft));
	    if ((dfxProc = DeviceProc(dfx)) == NULL)
		break;

	    dos_packet(dfxProc, ACTION_DISK_INFO, CTOB(infoData));
	    debug(("AwaitDFx %lx\n", infoData->id_DiskType));
	    if (infoData->id_DiskType == ID_NO_DISK_PRESENT) {
		/* DFx has not noticed yet. Wait a bit. */
		WaitIO(TimeIOReq);
		TimeIOReq->tr_node.io_Command = TR_ADDREQUEST;
		TimeIOReq->tr_time.tv_secs = 0;
		TimeIOReq->tr_time.tv_micro = 750000L;	/* .75 s */
		SendIO(TimeIOReq);
		continue;
	    }
	    if (infoData->id_DiskType == ID_DOS_DISK) {
		/* DFx: understands it, so it is not for us. */
		return 1;
	    }
	    /*
	     * All (well, most) other values mean that DFx: does not
	     * understand it, so we can give it a try.
	     */
	    break;
	}
    }
    return 0;
}

int
ReadBootBlock()
{
    int protstatus;

    debug(("ReadBootBlock\n"));
    FreeFat();                  /* before disk parameters change */
    TDClear();

    if (TDProtStatus() >= 0) {
	register byte *bootblock;
	short	    oldCancel;

	oldCancel = Cancel;

	if (AwaitDFx())
	    goto bad_disk;
	if ((protstatus = TDProtStatus()) < 0)
	    goto no_disk;

	TDChangeNum();
	debug(("Changenumber = %ld\n", DiskIOReq->iotd_Count));

	Cancel = 1;
	if (bootblock = GetSec(0)) {
	    word bps;

	    if (CheckBootBlock &&
				/* Atari: empty or 68000 JMP */
		/*bootblock[0] != 0x00 && bootblock[0] != 0x4E &&*/
				/* 8086 ml for a jump */
		bootblock[0] != 0xE9 && bootblock[0] != 0xEB) {
		goto bad_disk;
	    }
	    bps = Get8086Word(bootblock + 0x0b);
	    Disk.spc = bootblock[0x0d];
	    Disk.res = Get8086Word(bootblock + 0x0e);
	    Disk.nfats = bootblock[0x10];
	    Disk.ndirs = Get8086Word(bootblock + 0x11);
	    Disk.nsects = Get8086Word(bootblock + 0x13);
	    Disk.media = bootblock[0x15];
	    Disk.spf = Get8086Word(bootblock + 0x16);
	    Disk.spt = Get8086Word(bootblock + 0x18);
	    Disk.nsides = Get8086Word(bootblock + 0x1a);
	    Disk.nhid = Get8086Word(bootblock + 0x1c);
	    FreeSec(bootblock);

	    /*
	     *	Maybe the sector size just changed. Who knows?
	     */
	    if (Disk.bps != bps) {
		FreeCacheList();
		Disk.bps = bps;
		InitCacheList();
	    }

	    Disk.ndirsects = (Disk.ndirs * MS_DIRENTSIZE) / Disk.bps;
	    Disk.rootdir = Disk.res + Disk.spf * Disk.nfats;
	    Disk.datablock = Disk.rootdir + Disk.ndirsects;
	    Disk.start = Disk.datablock - MS_FIRSTCLUST * Disk.spc;
	    /* Available clusters are 2..maxclust in secs start..nsects-1 */
	    Disk.maxclst = (Disk.nsects - Disk.start) / Disk.spc - 1;
	    Disk.bpc = Disk.bps * Disk.spc;
	    Disk.vollabel = FakeRootDirEntry;
/*	    Disk.fat16bits = Disk.nsects > 20740;   /* DOS3.2 magic value */
	    Disk.fat16bits = Disk.maxclst >= 0xFF7; /* DOS3.0 magic value */

	    debug(("%x\tbytes per sector\n", Disk.bps));
	    debug(("%x\tsectors per cluster\n", Disk.spc));
	    debug(("%x\treserved blocks\n", Disk.res));
	    debug(("%x\tfats\n", Disk.nfats));
	    debug(("%x\tdirectory entries\n", Disk.ndirs));
	    debug(("%x\tsectors\n", Disk.nsects));
	    debug(("%x\tmedia byte\n", Disk.media));
	    debug(("%x\tsectors per FAT\n", Disk.spf));
	    debug(("%x\tsectors per track\n", Disk.spt));
	    debug(("%x\tsides\n", Disk.nsides));
	    debug(("%x\thidden sectors\n", Disk.nhid));

	    debug(("%x\tdirectory sectors\n", Disk.ndirsects));
	    debug(("%x\troot dir block\n", Disk.rootdir));
	    debug(("%x\tblock for (imaginary) cluster 0\n", Disk.start));
	    debug(("%x\tfirst data block\n", Disk.datablock));
	    debug(("%x\tclusters total\n", Disk.maxclst));
	    debug(("%x\tbytes per cluster\n", Disk.bpc));
	    debug(("%x\t16-bits FAT?\n", Disk.fat16bits));

	    IDDiskType = ID_DOS_DISK;
#ifdef READONLY
	    IDDiskState = ID_WRITE_PROTECTED;
#else
	    if (protstatus > 0)
		IDDiskState = ID_WRITE_PROTECTED;
	    else
		IDDiskState = ID_VALIDATED;
#endif

	    if (Disk.nsects / (MS_SPT * Disk.nsides) <= 40)
		DiskIOReq->iotd_Req.io_Flags |= IOMDF_40TRACKS;
	    else
		DiskIOReq->iotd_Req.io_Flags &= ~IOMDF_40TRACKS;

	    GetFat();
	} else {
	    debug(("Can't read %d.\n", DiskIOReq->iotd_Req.io_Error));
	bad_disk:
	    FreeCacheList();
	    error = ERROR_NO_DISK;
	    IDDiskType = ID_UNREADABLE_DISK;
	    IDDiskState = ID_WRITE_PROTECTED;
	}
	Cancel = oldCancel;
    }
#ifdef HDEBUG
    else debug(("No disk inserted %d.\n", DiskIOReq->iotd_Req.io_Error));
#endif
no_disk:
    return 1;
}

/*
 * We try to identify the disk currently in the drive, trying to find the
 * volume label in the first directory block.
 */

int
IdentifyDisk(name, date)
char	       *name;		/* Should be at least 32 characters */
struct DateStamp *date;
{
    debug(("IdentifyDisk\n"));
    ReadBootBlock();            /* Also sets default vollabel */

    if (IDDiskType == ID_DOS_DISK) {
	byte	       *dirblock;
	register struct MsDirEntry *dirent;

	if (dirblock = GetSec(Disk.rootdir)) {
	    dirent = (struct MsDirEntry *) dirblock;

	    while ((byte *) dirent < &dirblock[Disk.bps]) {
		if (dirent->msd_Attributes & ATTR_VOLUMELABEL) {
		    Disk.vollabel.de_Msd = *dirent;
		    Disk.vollabel.de_Sector = Disk.rootdir;
		    Disk.vollabel.de_Offset = (byte *) dirent - dirblock;
		    OtherEndianMsd(&Disk.vollabel.de_Msd);
		    Disk.vollabel.de_Msd.msd_Cluster = 0;	/* to be sure */
		    break;
		}
		dirent++;
	    }
	    strncpy(name, Disk.vollabel.de_Msd.msd_Name, 8 + 3);
	    name[8 + 3] = '\0';
	    ZapSpaces(name, name + 8 + 3);
	    ToDateStamp(date, Disk.vollabel.de_Msd.msd_Date,
			Disk.vollabel.de_Msd.msd_Time);
	    debug(("Disk is called '%s'\n", name));

	    FreeSec(dirblock);

	    return 0;
	}
    }
    return 1;
}

/*
 * Remove the disk change SoftInt. The V1.2 / V1.3 version is broken, so
 * we use a workaround. The correct thing to do is shown but not used.
 */

void
TDRemChangeInt()
{
    if (DiskChangeReq) {
	register struct IOExtTD *req = DiskIOReq;

#if 0				/* V1.2 and V1.3 have a broken
				 * TD_REMCHANGEINT */
	req->iotd_Req.io_Command = TD_REMCHANGEINT;
	req->iotd_Req.io_Data = (void *) DiskChangeReq;
	MyDoIO(req);
	WaitIO(DiskChangeReq);
#else
	Forbid();
	Remove(DiskChangeReq);
	Permit();
#endif
	DeleteExtIO(DiskChangeReq);
	DiskChangeReq = NULL;
    }
}

/*
 * Set the disk change SoftInt. Return nonzero on failure.
 */

int
TDAddChangeInt(interrupt)
struct Interrupt *interrupt;
{
    register struct IOExtTD *req = DiskIOReq;

    if (DiskChangeReq) {
	TDRemChangeInt();
    }
    DiskChangeReq = (void *)CreateExtIO(DiskReplyPort,
					 (long) sizeof (*DiskChangeReq));
    if (DiskChangeReq) {
	/* Clone IO request part */
	DiskChangeReq->io_Device = req->iotd_Req.io_Device;
	DiskChangeReq->io_Unit = req->iotd_Req.io_Unit;
	DiskChangeReq->io_Command = TD_ADDCHANGEINT;
	DiskChangeReq->io_Data = (void *) interrupt;
	SendIO(DiskChangeReq);

	return 0;
    }
    return 1;
}

/*
 * Get the current disk change number. Necessary for ETD_ commands. Makes
 * absolutely sure nobody can change the disk without us noticing it.
 */

int
TDChangeNum()
{
    register struct IOExtTD *req = DiskIOReq;

    req->iotd_Req.io_Command = TD_CHANGENUM;
    MyDoIO(req);
    req->iotd_Count = req->iotd_Req.io_Actual;

    return req->iotd_Req.io_Actual;
}

/*
 * Get the current write protection state.
 *
 * Zero means writable, one means write protected, minus one means
 * no disk in drive.
 */

int
TDProtStatus()
{
    register struct IOExtTD *req = DiskIOReq;

    req->iotd_Req.io_Command = TD_PROTSTATUS;
    MyDoIO(req);

    if (req->iotd_Req.io_Error)
	return -1;

    return req->iotd_Req.io_Actual != 0;
}

/*
 * Switch the drive motor off. Return previous state. Don't use this when
 * you have allocated the disk via GetDrive().
 */

int
TDMotorOff()
{
    register struct IOExtTD *req = DiskIOReq;

    req->iotd_Req.io_Command = TD_MOTOR;
    req->iotd_Req.io_Length = 0;
    MyDoIO(req);

    return req->iotd_Req.io_Actual;
}

/*
 * Clear all internal messydisk buffers.
 */

int
TDClear()
{
    register struct IOExtTD *req = DiskIOReq;

    req->iotd_Req.io_Command = CMD_CLEAR;

    return MyDoIO(req);
}

#ifndef READONLY
/*
 * Write out all internal messydisk buffers to the disk.
 */

int
TDUpdate()
{
    register struct IOExtTD *req = DiskIOReq;

    req->iotd_Req.io_Command = ETD_UPDATE;

    return MyDoIO(req);
}
#endif

int
MyDoIO(ioreq)
register struct IOStdReq *ioreq;
{
    ioreq->io_Flags |= IOF_QUICK;	/* Preserve IOMDF_40TRACKS */
    BeginIO(ioreq);
    return WaitIO(ioreq);
}
