/*-
 * $Id: devio.c,v 1.4 90/02/10 21:42:17 Rhialto Exp $
 * $Log:	devio.c,v $
 * Revision 1.4  90/02/10  21:42:17  Rhialto
 * Small changes
 * 
 * Revision 1.3  90/01/27  20:36:04  Rhialto
 * Variable #sectors/track!
 *
 * Revision 1.2  90/01/23  00:41:39  Rhialto
 * Remove C version of DecodeTrack.
 *
 * Revision 1.1  89/12/17  20:04:11  Rhialto
 *
 * DEVIO.C
 *
 * The messydisk.device code that does the real work.
 *
 * 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

struct DiskResource *DRResource;/* Argh! A global variable! */
void *CiaBResource;		/* And yet another! */

void		Internal_Update();
word		DataCRC();
word		CalculateGapLength();

/*-
 *  The high clock bit in this table is still 0, but it could become
 *  a 1 if the two adjecent data bits are both 0.
 *  In fact, that is the principle of MFM clock bits: make sure that no
 *  two 1 bits are adjecent, but not too many (more than 3) 0 bits either.
 *  So, 0 c 0 -> 0 1 0	    (where c is a clock bit to be determined)
 *	0 c 1 -> 0 0 1
 *	1 c 0 -> 1 0 0
 *	1 c 1 -> 1 0 1
 *  The sync pattern, $4489, is %0100 0100 1000 1001
 *				  ~ ~  ~ ~  ~ ~  ~ ~ -> %1010 0001 -> $A1
 *  also follows the rules, but won't be formed by encoding $A1...
 *  Since the bytes are written high bit first, the unknown clock bit
 *  (for encoded nybbles 0-7, high bit 0) will become a 1 if the preceding
 *  byte was even (with low bit 0).
 *  So, the clock bit is the NOR of the two data bits.
-*/

byte		MfmEncode[16] = {
    0x2a, 0x29, 0x24, 0x25, 0x12, 0x11, 0x14, 0x15,
    0x4a, 0x49, 0x44, 0x45, 0x52, 0x51, 0x54, 0x55
};

#define SYNC	0x4489
#define TLEN	12500	    /* In BYTES */
#define RLEN	(TLEN+1324) /* 1 sector extra */
#define WLEN	(TLEN+20)   /* 20 bytes more than the theoretical track size */

#define INDEXGAP	60  /* All these values are in WORDS */

#define IDGAP2		12  /* Sector header: 22 words */
#define IDSYNC		 3
#define IDMARK		 1
#define IDDATA		 4
#define IDCRC		 2
#define IDLEN		(IDGAP2+IDSYNC+IDMARK+IDDATA+IDCRC)

#define DATAGAP1	22  /* Sector itself: 552 words */
#define DATAGAP2	12
#define DATASYNC	 3
#define DATAMARK	 1
#define DATACRC 	 2
#define DATAGAP3_9	78  /* for 9 or less sectors/track */
#define DATAGAP3_10	40  /* for 10 sectors/track */
#define DATALEN 	(DATAGAP1+DATAGAP2+DATASYNC+DATAMARK+MS_BPS+DATACRC)

#define BLOCKLEN	(IDLEN+DATALEN)     /* Total: 574 words */

#define TAILGAP 	50

/* INDENT OFF */
#asm

; Some hardware data:

SYNC		equ $4489
TLEN		equ 12500	; 2 miscrosecs/bit, 200 ms/track -> 100000 bits
WLEN		equ TLEN+20

;;;;
;
;   The following lengths are all in unencoded bytes (or encoded words)

INDEXGAP	equ 60

IDGAP2		equ 12
IDSYNC		equ  3
IDMARK		equ  1
IDDATA		equ  4
IDCRC		equ  2

DATAGAP1	equ 22
DATAGAP2	equ 12
DATASYNC	equ  3
DATAMARK	equ  1
DATACRC 	equ  2

custom		equ $DFF000

Dsklen		equ $24
Intena		equ $9a 	; Interrupt enable  register (write)
Intreq		equ $9c 	; Interrupt request register (write)

; Flags in DSKLEN:

dskdmaoff	equ $4000

; Flags in INTENA/INTREQ:

intf_setclr	equ 1<<15
intf_dskblk	equ 1<<1

; CIA interrupt control register bits/flags:

ciaicrf_flg	equ    1<<4	 ; flg interrupt (disk index)

; some cia.resource library functions

		public	_LVOSignal
		public	_LVOAbleICR
		public	_LVOSetICR

_SafeEnableICR: move.l	_CiaBResource,a6
		move.b	4+1(sp),d0
		jsr	_LVOSetICR(a6)      ; clear pending interrupt
		move.b	4+1(sp),d0
		or.b	#$80,d0 	    ; then enable it
		jsr	_LVOAbleICR(a6)
		rts
;;;;
;
;   Disk index interrupt code.
;   is_Data (A1) is the value to stuff into the DSKLEN register.
;   A0 points to the custom chips already.
;   It then enables the disk block interrupt and disables the
;   index interrupt.

_IndexIntCode:
;	 movem.l A2-A4/D2-D7,-(sp)
	move.w	#dskdmaoff,Dsklen(a0)
	move.w	a1,Dsklen(a0)
	move.w	a1,Dsklen(a0)       ; this enables the DMA
	move.w	#intf_setclr|intf_dskblk,Intena(a0)
	move.l	_CiaBResource,a6
	move.b	#ciaicrf_flg,d0
	jsr	_LVOAbleICR(a6)     ; disable index interrupt
;	 movem.l (sp)+,A2-A4/D2-D7
	rts
;;;;
;
;   Disk DMA finished interrupt code.
;   (a1) is the task to Signal, 4(a1) is the signal mask to use.
;   Disables the disk block finished interrupt.

_DskBlkIntCode:
	move.w	#dskdmaoff,Dsklen(a0)   ; disable disk DMA
	move.w	#intf_dskblk,Intena(a0) ; disable the interrupt
	move.w	#intf_dskblk,Intreq(a0) ; clear 'diskblock finished' flag
	move.l	4(a1),d0            ; signal mask
	move.l	(a1),a1             ; task to signal
	jsr	_LVOSignal(a6)
	rts

#endasm

#define DSKDMAEN	(1<<15)
#define DSKWRITE	(1<<14)

void IndexIntCode(), DskBlkIntCode();

/* INDENT ON */

int
HardwareIO(dev, unit, dskwrite)
DEV	       *dev;
register UNIT  *unit;
int		dskwrite;
{
    struct {
	struct Task *task;
	ulong signal;
    } tasksig;

    debug(("Disk buffer is at %lx\n", dev->md_Rawbuffer));

    tasksig.task = FindTask(NULL);
    tasksig.signal = 1L << unit->mu_DmaSignal;

    unit->mu_DRUnit.dru_Index.is_Data = (APTR) ((WLEN >> 1)|DSKDMAEN| dskwrite);
    unit->mu_DRUnit.dru_DiscBlock.is_Data = (APTR) &tasksig;

    /* Clear signal bit */
    SetSignal(0L, tasksig.signal);

    /* Allocate drive and install index and block interrupts */
    GetDrive(&unit->mu_DRUnit);

    /* Select correct drive and side */
    ciab.ciaprb = 0xff & ~CIAF_DSKMOTOR;    /* See hardware manual p229 */
    ciab.ciaprb = 0xff & ~CIAF_DSKMOTOR
		       & ~(CIAF_DSKSEL0 << unit->mu_UnitNr)
		       & ~(unit->mu_CurrentSide << CIAB_DSKSIDE);

    /* Set up disk parameters */

/*
 * This is the adkcon setup: MFM mode, wordsync, no MSBsync, fast mode.
 * The precomp is 0 nanoseconds for the outer half of the disk, 120 for
 * the rest.
 */
    {
	register word adk;

	custom.adkcon = ADKF_PRECOMP1|ADKF_PRECOMP0|ADKF_MSBSYNC;

	adk = ADKF_SETCLR|ADKF_MFMPREC|ADKF_FAST|ADKF_WORDSYNC;

	/* Are we on the inner half ? */
	if (unit->mu_CurrentTrack > unit->mu_NumCyls >> 1) {
	    adk |= ADKF_PRECOMP0;
	}
	custom.adkcon = adk;
    }

    /* Set up disk buffer address */
    custom.dskpt = (APTR) dev->md_Rawbuffer;

    /* Enable disk DMA */
    custom.dmacon = DMAF_SETCLR | DMAF_MASTER | DMAF_DISK;

    if (dskwrite) {
	/* Enable disk index interrupt to start the whole thing. */
	SafeEnableICR((int) CIAICRF_FLG);
    } else {
	/* Set the sync word */
	custom.dsksync = SYNC;

	/* Do the same as the disk index interrupt would */
	custom.dsklen = DSKDMAOFF;
	custom.dsklen = (RLEN >> 1) | DSKDMAEN;
	custom.dsklen = (RLEN >> 1) | DSKDMAEN;

	custom.intena = INTF_SETCLR | INTF_DSKBLK;
    }

    Wait(tasksig.signal);

    FreeDrive();
}

#if 0
#define ID_ADDRESS_MARK     0xFE
#define MFM_ID		    0x5554
#define DATA_ADDRESS_MARK   0xFB
#define MFM_DATA	    0x5545

/* INDENT OFF
byte
DecodeByte(mfmdecode, mfm)
byte *mfmdecode;
word mfm;
{
    return mfmdecode[(byte)mfm & 0x7F] |
	   mfmdecode[(byte)(mfm >> 8) & 0x7F] << 4;
} */

#asm
mfmdecode   set     4
mfm	    set     8

_DecodeByte:
	move.l	mfmdecode(sp),a0
	move.b	mfm(sp),d1      ; high nybble
	and.w	#$7f,d1 	; strip clock bit (and garbage)
	move.b	(a0,d1.w),d0    ; decode 4 data bits
	lsl.b	#4,d0		; make room for the rest

	move.b	mfm+1(sp),d1    ; low nybble
	and.b	#$7f,d1 	; strip clock bit again
	or.b	(a0,d1.w),d0    ; insert 4 decoded bits

	rts

#endasm

/* INDENT ON */
byte DecodeByte();

int
DecodeTrack(dev, unit)
DEV	       *dev;
UNIT	       *unit;
{
    register word  *rawbuf = (word *)dev->md_Rawbuffer; /*  a2 */
    byte	   *rawend = (byte *)rawbuf + RLEN - (MS_BPS+2)*sizeof(word);
    byte	   *trackbuf = unit->mu_TrackBuffer;
    register byte  *decode = dev->md_MfmDecode; 	/*  a3 */
    word	   *oldcrc = unit->mu_CrcBuffer;
    register byte  *secptr;				/*  a4 */
    long	    sector;
    word	    numsecs;
    register long   numbytes;				/*  d3 */
    word	    maxsec;

#define Len	((byte *)rawbuf - dev->md_Rawbuffer)
    maxsec = 0;

    for (numsecs = 0; numsecs < MS_SPT_MAX; numsecs++) {
	/*
	 *  First try to find a sector id.
	 */
find_id:
	while (*rawbuf != SYNC) {
	    if (++rawbuf >= rawend) {
		debug(("id start, EOT %4x\n", Len));
		goto end;
	    }
	}
	while (*rawbuf == SYNC) {
	    rawbuf++;
	}
	if (*rawbuf++ != MFM_ID) {
	    debug(("No ID (%4x), %4x\n", rawbuf[-1], Len));
	    goto find_id;
	}

	sector = DecodeByte(decode, *rawbuf++);
	if (sector != unit->mu_CurrentTrack) {
	    debug(("Track error?? %d\n", (int)sector));
	    goto find_id;
	}
	sector = DecodeByte(decode, *rawbuf++);
	if (sector != unit->mu_CurrentSide) {
	    debug(("Side error?? %d\n", (int)sector));
	    goto find_id;
	}
	if (rawbuf >= rawend) {
	    debug(("id end, EOT %4x\n", Len));
	    goto end;
	}
	sector = DecodeByte(decode, *rawbuf++);
	debug(("#%2d %4x, ", (int)sector, Len-0xC));
	if (sector > MS_SPT_MAX) {
	    debug(("Bogus sector number) "));
	    goto find_id;
	}
	if (sector > maxsec)
	    maxsec = sector;
	sector--;		/* Normalize sector number */

	/*
	 *  Then find the data block.
	 */
find_data:
	while (*rawbuf != SYNC) {
	    if (++rawbuf >= rawend) {
		debug(("data start, EOT %4x\n", Len));
		return 0; /* TDERR_TooFewSecs; */
	    }
	}
	while (*rawbuf == SYNC) {
	    rawbuf++;
	}
	if (*rawbuf++ != MFM_DATA) {
	    debug(("No Data (%4x), %4x\n", rawbuf[-1], Len));
	    goto find_id;
	}
	debug(("%4x, ", Len-8));

	if (rawbuf >= rawend) {
	    debug(("short data, EOT %4x\n", Len));
	    goto end;
	}
	secptr = trackbuf + MS_BPS * sector;
	for (numbytes = 0; numbytes < MS_BPS; numbytes++) {
	    *secptr++ = DecodeByte(decode, *rawbuf++);
	}
	debug(("%4x\n", Len));
	oldcrc[sector]	= DecodeByte(decode, *rawbuf++) << 8;
	oldcrc[sector] |= DecodeByte(decode, *rawbuf++);
	unit->mu_SectorStatus[sector] = unit->mu_InitSectorStatus;
    }

end:
    if (numsecs == 0)
	return TDERR_TooFewSecs;

#ifndef READONLY
    /*
     * If we read the very first track, we adjust our notion about the
     * number of sectors on each track. This is the only track we can
     * accurately find if this number is unknown. Let's hope that the first
     * user of this disk starts reading it here.
     */
    if (unit->mu_CurrentTrack == 0 && unit->mu_CurrentSide == 0) {
	unit->mu_SectorsPerTrack = maxsec;
    }
    unit->mu_CurrentSectors = maxsec;
    debug(("%d sectors\n", unit->mu_SectorsPerTrack));
#endif

    return 0;

#undef Len
}
#else	/* Use assembly */

int
DecodeTrack(dev, unit)
DEV	       *dev;
UNIT	       *unit;
{
    register word  *rawbuf = (word *)dev->md_Rawbuffer; /*  a2 */
    byte	   *rawend = (byte *)rawbuf + RLEN - (MS_BPS+2)*sizeof(word);
    byte	   *trackbuf = unit->mu_TrackBuffer;
    register byte  *decode = dev->md_MfmDecode; 	/*  a3 */
    word	   *oldcrc = unit->mu_CrcBuffer;
    register byte  *secptr;				/*  a4 */
    long	    sector;
    word	    numsecs;
    register long   numbytes;				/*  d3 */
    word	    maxsec;

#asm

MFM_ID	    equ     $5554
MFM_DATA    equ     $5545

rawbuf	    equr    a2
decode	    equr    a3
secptr	    equr    a4
numbytes    equr    d3

rawend	    set     -4
trackbuf    set     -8
oldcrc	    set     -12
sector	    set     -16
numsecs     set     -18
maxsec	    set     -20

	move.w	#0,numsecs(a5)      ; no sectors found yet
	move.w	#0,maxsec(a5)       ; and no highest sector number

;;;;	First we will try to find a sector id.
find_id:
	cmp	#SYNC,(rawbuf)+
	beq.s	fid_gotsync
	cmpa.l	rawend(a5),rawbuf
	blt	find_id
	bra	return		    ; We ran off the end of the buffer.

fid_gotsync:			    ; Skip the other syncs.
	cmp.w	#SYNC,(rawbuf)
	bne	fid_endsync
	lea	2(rawbuf),rawbuf
	bra	fid_gotsync

fid_endsync:
	cmp.w	#MFM_ID,(rawbuf)+
	bne	find_id

	bsr	DecodeByte	    ; track #
	bsr	DecodeByte	    ; side #
	moveq.l #0,d0		    ; clear high part
	bsr	DecodeByte	    ; sector #
	cmp.w	#MS_SPT_MAX,d0	    ; sector number too large?
	bgt	find_id
	cmp.w	maxsec(a5),d0       ; what is the highest sector number?
	ble	nomax
	move.w	d0,maxsec(a5)       ; record the highest sector number
nomax:
	subq.w	#1,d0		    ; normalize sector number
	move.l	d0,sector(a5)

find_data:			    ; Then find the data block.
	cmp	#SYNC,(rawbuf)+
	beq.s	fda_gotsync
	cmpa.l	rawend(a5),rawbuf
	blt	find_data
	bra	return		    ; we ran off the end of the buffer.

fda_gotsync:			    ; skip the other syncs.
	cmp.w	#SYNC,(rawbuf)
	bne	fda_endsync
	lea	2(rawbuf),rawbuf
	bra	fda_gotsync

fda_endsync:
	cmp.w	#MFM_DATA,(rawbuf)+ ; do we really have a data block?
	bne	find_id

	cmpa.l	rawend(a5),rawbuf   ; will we still be inside the mfm data?
	bge	return

	move.l	sector(a5),d0       ; calculate the location to
	moveq.l #LOG2_MS_BPS,d1     ;  store this sector.
	asl.l	d1,d0
	move.l	trackbuf(a5),secptr
	add.l	d0,secptr

	move.w	#MS_BPS-1,numbytes
data_copy:
	bsr	DecodeByte
	move.b	d0,(secptr)+
	dbra	numbytes,data_copy

	move.l	sector(a5),d3       ; get pointer to crc location
	add.l	d3,d3		    ; 2 bytes of crc per sector
	move.l	oldcrc(a5),a0
	add.l	d3,a0

	bsr	DecodeByte	    ; get high byte
	move.b	d0,(a0)+
	bsr	DecodeByte	    ; and low byte of crc
	move.b	d0,(a0)+

#endasm
	unit->mu_SectorStatus[sector] = unit->mu_InitSectorStatus;
#asm
	addq.w	#1,numsecs(a5)
	cmp.w	#MS_SPT_MAX,numsecs(a5)
	blt	find_id
return:
#endasm

    if (numsecs == 0)
	return TDERR_TooFewSecs;

#ifndef READONLY
    /*
     * If we read the very first track, we adjust our notion about the
     * number of sectors on each track. This is the only track we can
     * accurately find if this number is unknown. Let's hope that the first
     * user of this disk starts reading it here.
     */
    if (unit->mu_CurrentTrack == 0 && unit->mu_CurrentSide == 0) {
	unit->mu_SectorsPerTrack = maxsec;
    }
    unit->mu_CurrentSectors = maxsec;
    debug(("%d sectors\n", unit->mu_SectorsPerTrack));
#endif

    return 0;

}

#asm
;;;;
;
;   Decode a single MFM word to a byte.
;   Auto-increments the rawbuffer pointer.

DecodeByte:
	move.b	(rawbuf)+,d1    ; high nybble
	and.w	#$7f,d1 	; strip clock bit (and garbage)
	move.b	(decode,d1.w),d0; decode 4 data bits
	lsl.b	#4,d0		; make room for the rest

	move.b	(rawbuf)+,d1    ; low nybble
	and.b	#$7f,d1 	; strip clock bit again
	or.b	(decode,d1.w),d0; insert 4 decoded bits

	rts

#endasm
#endif	/* using assembly */

/*
 * Initialize the ibm mfm decoding table
 */

void
InitDecoding(decode)
register byte  *decode;
{
    register int    i;

    i = 0;
    do {
	decode[i] = 0xff;
    } while (++i < 128);

    i = 0;
    do {
	decode[MfmEncode[i]] = i;
    } while (++i < 0x10);
}

#ifdef notdef
long
MyDoIO(req)
register struct IORequest *req;
{
    req->io_Flags |= IOF_QUICK;
    BeginIO(req);
    return WaitIO(req);
}
#endif

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

int
TDMotorOn(tdreq)
register struct IOExtTD *tdreq;
{
    debug(("TDMotorOn "));
    tdreq->iotd_Req.io_Command = TD_MOTOR;
    tdreq->iotd_Req.io_Length = 1;
    DoIO(tdreq);
    debug(("was %ld\n", tdreq->iotd_Req.io_Actual));

    return tdreq->iotd_Req.io_Actual;
}

/*
 * Get the number of cylinders the drive is capable of using.
 */

int
TDGetNumCyls(tdreq)
register struct IOExtTD *tdreq;
{
    tdreq->iotd_Req.io_Command = TD_GETNUMTRACKS;
    DoIO(tdreq);

    return tdreq->iotd_Req.io_Actual / NUMHEADS;
}

/*
 * Seek the drive to the indicated cylinder. Use the trackdisk.device for
 * ease. Don't use this when you have allocated the disk via GetDrive().
 */

int
TDSeek(unit, ioreq, cylinder)
UNIT	       *unit;
struct IOStdReq *ioreq;
int		cylinder;
{
    register struct IOExtTD *tdreq = unit->mu_DiskIOReq;

    debug(("TDSeek %d\n", cylinder));

    tdreq->iotd_Req.io_Command = TD_SEEK;
    tdreq->iotd_Req.io_Offset = cylinder * (TD_SECTOR * NUMSECS * NUMHEADS);
    if ((ioreq->io_Flags & IOMDF_40TRACKS) && (unit->mu_NumCyls == 80))
	tdreq->iotd_Req.io_Offset *= 2;
    DoIO(tdreq);

    return tdreq->iotd_Req.io_Error;
}

void	       *
GetDrive(drunit)
register struct DiskResourceUnit *drunit;
{
    register void  *LastDriver;

    debug(("GetDrive: "));
    for (;;) {
	drunit->dru_Message.mn_Node.ln_Type = NT_MESSAGE;
	LastDriver = GetUnit(drunit);

	debug(("LastDriver %08lx\n", LastDriver));
	if (LastDriver) {
	    return LastDriver;
	} else {
	    while (drunit->dru_Message.mn_Node.ln_Type != NT_REPLYMSG)
		Wait(1L << drunit->dru_Message.mn_ReplyPort->mp_SigBit);
	    Remove(drunit);
	    debug(("GetDrive: Retry\n"));
	}
    }
}

void
FreeDrive()
{
    GiveUnit();
}

int
GetTrack(ioreq, side, track)
struct IOStdReq *ioreq;
int		side;
int		track;
{
    register int    i;
    DEV 	   *dev;
    register UNIT  *unit;

    debug(("GetTrack %d %d\n", track, side));
    dev = (DEV *) ioreq->io_Device;
    unit = (UNIT *) ioreq->io_Unit;

    if (track != unit->mu_CurrentTrack || side != unit->mu_CurrentSide) {
#ifndef READONLY
	Internal_Update(ioreq, unit);
#endif
	for (i = MS_SPT_MAX-1; i >= 0; i--) {
	    unit->mu_SectorStatus[i] = TDERR_NoSecHdr;
	}

	TDMotorOn(unit->mu_DiskIOReq);
	if (TDSeek(unit, ioreq, track)) {
	    debug(("Seek error\n"));
	    return ioreq->io_Error = IOERR_BADLENGTH;
	}
	unit->mu_CurrentTrack = track;
	unit->mu_CurrentSide = side;
	ObtainSemaphore(&dev->md_HardwareUse);
	HardwareIO(dev, unit, 0);
	i = DecodeTrack(dev, unit);
	ReleaseSemaphore(&dev->md_HardwareUse);
	debug(("DecodeTrack returns %d\n", i));

	if (i != 0) {
	    unit->mu_CurrentTrack = -1;
	    return i;
	}
    }

    return 0;
}

/*
 * Test if it is changed
 */

int
CheckChanged(ioreq, unit)
struct IOExtTD *ioreq;
register UNIT  *unit;
{
    register struct IOExtTD *tdreq;

    if ((ioreq->iotd_Req.io_Command & TDF_EXTCOM) &&
	ioreq->iotd_Count < unit->mu_ChangeNum) {
diskchanged:
	ioreq->iotd_Req.io_Error = TDERR_DiskChanged;
error:
	return 1;
    }
    return 0;
}

/*
 * Test if we can read or write the disk. Is it inserted and writable?
 */

int
CheckRequest(ioreq, unit)
struct IOExtTD *ioreq;
register UNIT  *unit;
{
    register struct IOExtTD *tdreq;

    if ((ioreq->iotd_Req.io_Command & TDF_EXTCOM) &&
	ioreq->iotd_Count < unit->mu_ChangeNum) {
diskchanged:
	ioreq->iotd_Req.io_Error = TDERR_DiskChanged;
error:
	return 1;
    }
    /*
     * if (ioreq->iotd_Req.io_Offset + ioreq->iotd_Req.io_Length >
     * (unit->mu_NumCyls * MS_NSIDES * MS_SPT * MS_BPS)) {
     * ioreq->iotd_Req.io_Error = IOERR_BADLENGTH; goto error; }
     */

    tdreq = unit->mu_DiskIOReq;

    if (unit->mu_DiskState == STATEF_UNKNOWN) {
	tdreq->iotd_Req.io_Command = TD_PROTSTATUS;
	DoIO(tdreq);
	if (tdreq->iotd_Req.io_Error == 0) {
	    if (tdreq->iotd_Req.io_Actual == 0) {
		unit->mu_DiskState = STATEF_PRESENT | STATEF_WRITABLE;
	    } else
		unit->mu_DiskState = STATEF_PRESENT;
	} else
	    unit->mu_DiskState = 0;
    }
    if (!(unit->mu_DiskState & STATEF_PRESENT))
	goto diskchanged;

    /*
     * Check _WRITE, _UPDATE, _FORMAT
     */
    if (STRIP(ioreq->iotd_Req.io_Command) != CMD_READ) {
	if (!(unit->mu_DiskState & STATEF_WRITABLE)) {
	    ioreq->iotd_Req.io_Error = TDERR_WriteProt;
	    goto error;
	}
    }
    return 0;
}


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

void
CMD_Read(ioreq, unit)
register struct IOExtTD *ioreq;
register UNIT  *unit;
{
    int 	    side;
    int 	    cylinder;
    int 	    sector;
    byte	   *userbuf;
    long	    length;
    long	    offset;
    byte	   *diskbuf;
    int 	    retrycount;

    debug(("CMD_Read "));
    userbuf = (byte *) ioreq->iotd_Req.io_Data;
    length = ioreq->iotd_Req.io_Length / MS_BPS;	/* Sector count */
    offset = ioreq->iotd_Req.io_Offset / MS_BPS;	/* Sector number */
    debug(("userbuf %08lx off %ld len %ld ", userbuf, offset, length));

    cylinder = offset / unit->mu_SectorsPerTrack;
    side = cylinder % MS_NSIDES;
    cylinder /= MS_NSIDES;
    sector = offset % unit->mu_SectorsPerTrack;       /* 0..8 or 9 */
    debug(("Tr=%d Si=%d Se=%d\n", cylinder, side, sector));

    ioreq->iotd_Req.io_Actual = 0;

    if (length <= 0 || CheckRequest(ioreq, unit))
	goto end;

    retrycount = 0;
    diskbuf = unit->mu_TrackBuffer + MS_BPS * sector;
gettrack:
    GetTrack(ioreq, side, cylinder);

    for (;;) {
	/*
	 * Have we ever checked this CRC?
	 */
	if (unit->mu_SectorStatus[sector] == CRC_UNCHECKED) {
	    /*
	     * Do it now. If it mismatches, remember that for later.
	     */
	    if (unit->mu_CrcBuffer[sector] != DataCRC(diskbuf)) {
		debug(("%d: %04x, now %04x\n", sector, unit->mu_CrcBuffer[sector], DataCRC(diskbuf)));
		unit->mu_SectorStatus[sector] = TDERR_BadSecSum;
	    } else
		unit->mu_SectorStatus[sector] = TDERR_NoError;
	}
	if (unit->mu_SectorStatus[sector] > TDERR_NoError) {
	    if (++retrycount < 3) {
		unit->mu_CurrentTrack = -1;
		goto gettrack;
	    }
	    ioreq->iotd_Req.io_Error = unit->mu_SectorStatus[sector];
	    goto end;	    /* Don't use this sector anymore... */
	}
	retrycount = 0;
	CopyMem(diskbuf, userbuf, (long) MS_BPS);
	ioreq->iotd_Req.io_Actual += MS_BPS;
	if (--length <= 0)
	    break;
	userbuf += MS_BPS;
	diskbuf += MS_BPS;
	if (++sector >= unit->mu_SectorsPerTrack) {
	    sector = 0;
	    diskbuf = unit->mu_TrackBuffer;
	    if (++side >= MS_NSIDES) {
		side = 0;
		if (++cylinder >= unit->mu_NumCyls) {
		    /* ioreq->iotd_Req.io_Error = IOERR_BADLENGTH; */
		    goto end;
		}
	    }
	    GetTrack(ioreq, side, cylinder);
	}
    }

end:
    TermIO(ioreq);
}

#ifdef READONLY

void
CMD_Write(ioreq, unit)
register struct IOExtTD *ioreq;
UNIT	       *unit;
{
    ioreq->iotd_Req.io_Error = TDERR_NotSpecified;
    TermIO(ioreq);
}

void
TD_Format(ioreq, unit)
register struct IOExtTD *ioreq;
UNIT	       *unit;
{
    ioreq->iotd_Req.io_Error = TDERR_NotSpecified;
    TermIO(ioreq);
}

#endif

void
CMD_Reset(ioreq, unit)
struct IOExtTD *ioreq;
UNIT	       *unit;
{
    unit->mu_CurrentSide = -1;
    unit->mu_TrackChanged = 0;
    TermIO(ioreq);
}

void
CMD_Update(ioreq, unit)
struct IOExtTD *ioreq;
register UNIT  *unit;
{
#ifndef READONLY
    if (unit->mu_TrackChanged && !CheckRequest(ioreq, unit))
	Internal_Update(ioreq, unit);
#endif
    TermIO(ioreq);
}

void
CMD_Clear(ioreq, unit)
struct IOExtTD *ioreq;
UNIT	       *unit;
{
    if (!CheckChanged(ioreq, unit)) {
	unit->mu_CurrentSide = -1;
	unit->mu_TrackChanged = 0;
    }
    TermIO(ioreq);
}

void
TD_Seek(ioreq, unit)
struct IOExtTD *ioreq;
UNIT	       *unit;
{
    if (!CheckChanged(ioreq, unit)) {
	word		cylinder;

	cylinder = (ioreq->iotd_Req.io_Offset / unit->mu_SectorsPerTrack) /
		    (MS_BPS * MS_NSIDES);
	TDSeek(unit, ioreq, cylinder);
    }
    TermIO(ioreq);
}

/*
 * Ask the trackdisk.device for the answer, but keep a local copy.
 */

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

    req = &unit->mu_DiskIOReq->iotd_Req;
    req->io_Command = TD_CHANGENUM;
    DoIO(req);

    unit->mu_ChangeNum = req->io_Actual;
    ioreq->iotd_Req.io_Actual = req->io_Actual;
    TermIO(ioreq);
}

int
DevInit(dev)
register DEV   *dev;
{
    if (!(DRResource = OpenResource(DISKNAME)))
	goto abort;

    if (!(CiaBResource = OpenResource(CIABNAME)))
	goto abort;

#ifndef READONLY
    if (!InitWrite(dev))
	goto abort;
#endif

    InitDecoding(dev->md_MfmDecode);
    InitSemaphore(&dev->md_HardwareUse);
    return 1;			/* Initializing succeeded */

abort:
    return DevCloseDown(dev);
}

int
DevCloseDown(dev)
DEV	       *dev;
{
#ifndef READONLY
    FreeBuffer(dev);
#endif
    return 0;			/* Now unitialized */
}

#ifndef READONLY
/*
 * Calculate the length between the sectors, given the length of the track
 * and the number of sectors that must fit on it.
 * The proper formula would be
 * (((TLEN/2) - INDEXGAP - TAILGAP) / unit->mu_SectorsPerTrack) - BLOCKLEN;
 */

word
CalculateGapLength(sectors)
int		sectors;
{
    return (sectors == 10) ? DATAGAP3_10 : DATAGAP3_9;
}
#endif

UNIT	       *
UnitInit(dev, UnitNr)
DEV	       *dev;
ulong		UnitNr;
{
    register UNIT  *unit;
    struct Task    *task;
    struct IOStdReq *dcr;
    struct IOExtTD *tdreq;

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

    if (!(tdreq = CreateExtIO(&unit->mu_DiskReplyPort, (long) sizeof (*tdreq)))) {
	goto abort;
    }
    unit->mu_DiskIOReq = tdreq;
    if (OpenDevice(TD_NAME, UnitNr, tdreq, TDF_ALLOW_NON_3_5)) {
	tdreq->iotd_Req.io_Device = NULL;
	goto abort;
    }
    dcr = (void *) CreateExtIO(&unit->mu_DiskReplyPort, (long) sizeof (*dcr));
    if (dcr) {
	unit->mu_DiskChangeReq = dcr;
	unit->mu_DiskChangeInt.is_Node.ln_Pri = 32;
	unit->mu_DiskChangeInt.is_Data = (APTR) unit;
	unit->mu_DiskChangeInt.is_Code = DiskChangeHandler;
	/* Clone IO request part */
	dcr->io_Device = tdreq->iotd_Req.io_Device;
	dcr->io_Unit = tdreq->iotd_Req.io_Unit;
	dcr->io_Command = TD_ADDCHANGEINT;
	dcr->io_Data = (void *) &unit->mu_DiskChangeInt;
	SendIO(dcr);
    }
    NewList(&unit->mu_ChangeIntList);

    unit->mu_NumCyls = TDGetNumCyls(tdreq);
    unit->mu_UnitNr = UnitNr;
    unit->mu_DiskState = STATEF_UNKNOWN;
    unit->mu_TrackChanged = 0;
    unit->mu_CurrentSide = -1;
    unit->mu_InitSectorStatus = CRC_UNCHECKED;
    unit->mu_SectorsPerTrack = MS_SPT;

    unit->mu_DRUnit.dru_Message.mn_ReplyPort = &unit->mu_DiskReplyPort;
    unit->mu_DRUnit.dru_Index.is_Node.ln_Pri = 32; /* high pri for index int */
    unit->mu_DRUnit.dru_Index.is_Code = IndexIntCode;
    unit->mu_DRUnit.dru_DiscBlock.is_Code = DskBlkIntCode;

    /*
     * Now create the Unit task. Remember that it won't start running
     * since we are Forbid()den. But just to be sure, we Forbid() again.
     */
    Forbid();
    task = CreateTask(DevName, TASKPRI, UnitTask, TASKSTACK);
    task->tc_UserData = (APTR) unit;

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

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

    return unit;

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

int
UnitCloseDown(ioreq, dev, unit)
struct IOExtTD *ioreq;
DEV	       *dev;
register UNIT  *unit;
{
#ifndef READONLY
    if (ioreq && unit->mu_TrackChanged)
	Internal_Update(ioreq, unit);
#endif

    /*
     * 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->mu_Port.mp_SigTask) {
#ifdef DEBUG
	extern struct SignalSemaphore PortUse;

	/*
	 * Make sure that the unit task does not get removed when it has
	 * the semaphore.
	 */
	ObtainSemaphore(&PortUse);
#endif
	RemTask(unit->mu_Port.mp_SigTask);
#ifdef DEBUG
	ReleaseSemaphore(&PortUse);
#endif
    }
    if (unit->mu_DiskChangeReq) {
#if 0				/* V1.2 and V1.3 have a broken
				 * TD_REMCHANGEINT */
	register struct IOExtTD *req = unit->mu_DiskIOReq;

	req->iotd_Req.io_Command = TD_REMCHANGEINT;
	req->iotd_Req.io_Data = (void *) unit->mu_DiskChangeReq;
	DoIO(req);
	WaitIO(unit->mu_DiskChangeReq);
#else
	Disable();
	Remove(unit->mu_DiskChangeReq);
	Enable();
#endif
	DeleteExtIO(unit->mu_DiskChangeReq);
	unit->mu_DiskChangeReq = NULL;
    }
    if (unit->mu_DiskIOReq) {
	if (unit->mu_DiskIOReq->iotd_Req.io_Device)
	    CloseDevice(unit->mu_DiskIOReq);
	DeleteExtIO(unit->mu_DiskIOReq);
	unit->mu_DiskIOReq = NULL;
    }
    FreeMem(unit, (long) sizeof (UNIT));

    return 0;			/* Now unitialized */
}

/*
 * Create missing bindings
 */
/* INDENT OFF */

#asm

lib_vectsize	equ	6
lib_base	equ	-lib_vectsize

_RVOAllocUnit	equ	lib_base-(0*lib_vectsize)
_RVOFreeUnit	equ	lib_base-(1*lib_vectsize)
_RVOGetUnit	equ	lib_base-(2*lib_vectsize)
_RVOGiveUnit	equ	lib_base-(3*lib_vectsize)
_RVOGetUnitID	equ	lib_base-(4*lib_vectsize)

;_AllocUnit:
;		move.l	_DRResource,a6
;		move.l	4(sp),d0
;		jmp	_RVOAllocUnit(a6)
;_FreeUnit:
;		move.l	_DRResource,a6
;		move.l	4(sp),d0
;		jmp	_RVOFreeUnit(a6)
_GetUnit:
		move.l	_DRResource,a6
		move.l	4(sp),a1
		jmp	_RVOGetUnit(a6)
;_GetUnitID:
;		move.l	_DRResource,a6
;		move.l	4(sp),d0
;		jmp	_RVOGetUnitID(a6)
_GiveUnit:
		move.l	_DRResource,a6
		jmp	_RVOGiveUnit(a6)

#endasm
/* INDENT ON */

/*
 * We handle disk change interrupts internally, since the io request is
 * held by the device. Since SoftInts caused by the trackdisk.device are
 * broadcast to our clients, our own softint must have the highest
 * priority possible.
 *
 * TD_Addchangeint is an IMMEDIATE command, so no exclusive use of the list
 * is acquired (nor released). The list is accessed by (software)
 * interrupt code.
 */

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

    unit = (UNIT *) ioreq->io_Unit;
    Disable();
    AddTail(&unit->mu_ChangeIntList, ioreq);
    Enable();
    ioreq->io_Flags &= ~IOF_QUICK;	/* So we call ReplyMsg instead of
					 * TermIO */
    /* Note no TermIO */
}

void
TD_Remchangeint(ioreq)
register struct IOStdReq *ioreq;
{
    register struct IOStdReq *intreq;

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

void
DiskChangeHandler()
{
    auto UNIT	   *unit;
    register struct IOStdReq *ioreq;
    register struct IOStdReq *next;
/* INDENT OFF */
#asm
    movem.l d2-d7/a2-a4,-(sp)
    move.l  a1,-4(a5)        ;unit
#endasm
    /* INDENT ON */
    unit->mu_DiskState = STATEF_UNKNOWN;
    unit->mu_ChangeNum++;
    unit->mu_SectorsPerTrack = MS_SPT;
    for (ioreq = (struct IOStdReq *) unit->mu_ChangeIntList.mlh_Head;
	 next = (struct IOStdReq *) ioreq->io_Message.mn_Node.ln_Succ;
	 ioreq = next) {
	Cause((struct Interrupt *) ioreq->io_Data);
    }
/* INDENT OFF */
#asm
    movem.l (sp)+,d2-d7/a2-a4
#endasm
    /* INDENT ON */
}

#ifndef READONLY

/*
 * Parts of the following code were written by Werner Guenther.
 * Used with permission.
 */

/* mu_TrackChanged is a flag. When a sector has changed it changes to 1 */

/*
 * InitWrite() has to be called once at startup. It allocates the space
 * for one raw track, and writes the low level stuff between sectors
 * (gaps, syncs etc.)
 */

int
InitWrite(dev)
DEV	       *dev;
{
    if ((dev->md_Rawbuffer =
	    AllocMem((long)RLEN+2, MEMF_CHIP | MEMF_PUBLIC)) == 0)
	return 0;

    return 1;
}

/*
 * FreeBuffer has to be called when msh: closes down, it just frees the
 * memory InitWrite has allocated
 */

void
FreeBuffer(dev)
DEV	       *dev;
{
    if (dev->md_Rawbuffer) {    /* OIS */
	FreeMem(dev->md_Rawbuffer, (long) RLEN + 2);
    }
}

/*
 * This routine doesn't write to the disk, but updates the TrackBuffer to
 * respect the new sector. We have to be sure the TrackBuffer is filled
 * with the current Track. As GetSTS calls Internal_Update if the track
 * changes we don't have to bother about actually writing any data to the
 * disk. GetSTS has to be changed in the following way:
 *
 * if (track != mu_CurrentTrack || side != mu_CurrentSide) { Internal_Update(); for
 * (i = 0; i < MS_SPT; i++) ..... etc.
 */

void
CMD_Write(ioreq, unit)
register struct IOExtTD *ioreq;
UNIT	       *unit;
{
    int 	    side;
    int 	    cylinder;
    int 	    sector;
    byte	   *userbuf;
    long	    length;
    long	    offset;
    word	    spt;

    debug(("CMD_Write "));
    userbuf = (byte *) ioreq->iotd_Req.io_Data;
    length = ioreq->iotd_Req.io_Length / MS_BPS;	/* Sector count */
    offset = ioreq->iotd_Req.io_Offset / MS_BPS;	/* Sector number */
    debug(("userbuf %08lx off %ld len %ld ", userbuf, offset, length));

    spt = unit->mu_SectorsPerTrack;
    cylinder = offset / spt;
    side = cylinder % MS_NSIDES;
    cylinder /= MS_NSIDES;
    sector = offset % spt;
    debug(("T=%d Si=%d Se=%d\n", cylinder, side, sector));

    ioreq->iotd_Req.io_Actual = 0;

    if (length <= 0 || CheckRequest(ioreq, unit))
	goto end;

    GetTrack(ioreq, side, cylinder);
    for (;;) {
	CopyMem(userbuf, unit->mu_TrackBuffer + MS_BPS * sector, (long) MS_BPS);
	unit->mu_TrackChanged = 1;
	unit->mu_SectorStatus[sector] = CRC_CHANGED;

	ioreq->iotd_Req.io_Actual += MS_BPS;
	if (--length <= 0)
	    break;
	userbuf += MS_BPS;
	/*
	 * Get next sequential sector/side/track
	 */
	if (++sector >= spt) {
	    sector = 0;
	    if (++side >= MS_NSIDES) {
		side = 0;
		if (++cylinder >= unit->mu_NumCyls)
		    goto end;
	    }
	    GetTrack(ioreq, side, cylinder);
	}
    }

    if (length)
	ioreq->iotd_Req.io_Error = TDERR_NotSpecified;

end:
    TermIO(ioreq);
}

/*
 * This is called by your GetSTS() routine if the Track has changed. It
 * writes the changes back to the disk (a whole track at a time). It has
 * to be called if your device gets a CLOSE instruction too.
 */

void
Internal_Update(ioreq, unit)
struct IOExtTD *ioreq;
register UNIT  *unit;
{
    debug(("Internal_Update "));
    /* did we have a changed sector at all	 */
    if (unit->mu_TrackChanged != 0) {
	debug(("needs to write "));

	if (unit->mu_SectorsPerTrack > unit->mu_CurrentSectors)
	    unit->mu_CurrentSectors = unit->mu_SectorsPerTrack;

	/*
	 * Only recalculate the CRC on changed sectors. This way, a
	 * sector with a bad CRC won't suddenly be ``repaired''.
	 */
	{
	    register int i;

	    for (i = unit->mu_CurrentSectors - 1; i >= 0; i--) {
		if (unit->mu_SectorStatus[i] == CRC_CHANGED) {
		    unit->mu_CrcBuffer[i] = DataCRC(unit->mu_TrackBuffer + i * MS_BPS);
		    debug(("%d: %04x\n", i, unit->mu_CrcBuffer[i]));
		}
	    }
	}
	{
	    DEV 	   *dev;
	    register struct IOExtTD *tdreq;
	    word	    SectorGap;

	    dev = (DEV *) ioreq->iotd_Req.io_Device;
	    tdreq = unit->mu_DiskIOReq;
	    SectorGap = CalculateGapLength(unit->mu_CurrentSectors);

	    ObtainSemaphore(&dev->md_HardwareUse);
	    EncodeTrack(unit->mu_TrackBuffer, dev->md_Rawbuffer,
			unit->mu_CrcBuffer,
			unit->mu_CurrentTrack, unit->mu_CurrentSide,
			SectorGap, unit->mu_CurrentSectors);

	    TDMotorOn(tdreq);
	    if (TDSeek(unit, ioreq, unit->mu_CurrentTrack)) {
		debug(("Seek error\n"));
		ioreq->iotd_Req.io_Error = TDERR_SeekError;
		goto end;
	    }
	    HardwareIO(dev, unit, DSKWRITE);

	    ReleaseSemaphore(&dev->md_HardwareUse);
	    unit->mu_TrackChanged = 0;
	}
    }
end:
    debug(("done\n"));
}

/*
 * TD_Format writes one or more whole tracks without reading them first.
 */

void
TD_Format(ioreq, unit)
register struct IOExtTD *ioreq;
UNIT	       *unit;
{
    register struct IOExtTD *tdreq = unit->mu_DiskIOReq;
    DEV 	   *dev;
    short	    side;
    int 	    cylinder;
    byte	   *userbuf;
    int 	    length;
    word	    spt;
    word	    gaplen;

    debug(("CMD_Format "));

    if (CheckRequest(ioreq, unit))
	goto end;

    userbuf = (byte *) ioreq->iotd_Req.io_Data;
    length = ioreq->iotd_Req.io_Length / MS_BPS;	    /* Sector count */
    cylinder = ioreq->iotd_Req.io_Offset / MS_BPS;	    /* Sector number */
    /*
     * Now try to guess the number of sectors the user wants per track.
     * 40 sectors is the first ambuiguous length.
     */
    if (length < 40) {
	if (length > 0) {
	    for (spt = 8; spt <= MS_SPT_MAX; spt++) {
		if ((length % spt) == 0)
		    goto found_spt;
	    }
	}
	/*
	 * Not 8, 16, 24, 32, 9, 18, 27, 36, 10, 20, or 30? That is an error.
	 */
	ioreq->iotd_Req.io_Error = IOERR_BADLENGTH;
	goto end;
    } else  /* assume previous number */
	spt = unit->mu_SectorsPerTrack;

found_spt:
    gaplen = CalculateGapLength(spt);

    /*
     * Assume the whole disk will have this layout.
     */
    unit->mu_SectorsPerTrack = spt;

    length /= spt;
    cylinder /= spt;

    side = cylinder % MS_NSIDES;
    cylinder /= MS_NSIDES;
    debug(("userbuf %08lx cylinder %d len %d\n", userbuf, cylinder, length));

    ioreq->iotd_Req.io_Actual = 0;

    /*
     * Write out the current track if we are not going to overwrite it.
     * After the format operation, the buffer is invalidated.
     */
    if (cylinder <= unit->mu_CurrentTrack &&
	unit->mu_CurrentTrack < cylinder + length)
	Internal_Update(ioreq, unit);

    dev = (DEV *) ioreq->iotd_Req.io_Device;

    while (length > 0) {
	{
	    register int i;

	    for (i = spt - 1; i >= 0; i--) {
		unit->mu_CrcBuffer[i] = DataCRC(userbuf + i * MS_BPS);
		debug(("%d: %04x\n", i, unit->mu_CrcBuffer[i]));
	    }
	}
	ObtainSemaphore(&dev->md_HardwareUse);
	EncodeTrack(userbuf, dev->md_Rawbuffer, unit->mu_CrcBuffer,
		    cylinder, side,
		    gaplen, spt);

	TDMotorOn(tdreq);
	if (TDSeek(unit, ioreq, cylinder)) {
	    debug(("Seek error\n"));
	    ioreq->iotd_Req.io_Error = IOERR_BADLENGTH;
	    break;
	}
	unit->mu_CurrentSide = side;
	HardwareIO(dev, unit, DSKWRITE);

	ReleaseSemaphore(&dev->md_HardwareUse);

	length--;
	userbuf += MS_BPS * spt;
	ioreq->iotd_Req.io_Actual += MS_BPS * spt;

	if (++side >= MS_NSIDES) {
	    side = 0;
	    if (++cylinder >= unit->mu_NumCyls)
		goto end;
	}
    }
end:
    unit->mu_CurrentSide = -1;
    TermIO(ioreq);
}
/* INDENT OFF */

#asm

; we need a buffer for the Sector-ID field to calculate its checksum
;SectorHeader:
;	 dc.b	 0		   ; track
;	 dc.b	 0		   ; side
;	 dc.b	 0		   ; sector
;	 dc.b	 2		   ; length (2=512 bytes)
;	 dc.w	 0		   ; CRC

	public _EncodeTrack

;   EncodeTrack(TrackBuffer, Rawbuffer, Crcs, Track, Side, GapLen, NumSecs)
;		4	     4		4     2      2	   2	   2

_EncodeTrack:
	movem.l d2-d7/a2-a6,-(sp)  ; save registers

fp	set	(4*(6+5))+4        ; 4 for return address
trackbf set	0
rawbf	set	4
crcs	set	8
track	set	12
side	set	14
gaplen	set	16
numsecs set	18

; a0	ptr in encoded data (also putmfmbyte)
; a2	ptr to mfm encoding table (putmfmbyte)
; a3	ptr to data to be crc'd (HeaderCRC)
; a4	ptr to table with calculated CRC's
; a5	ptr to unencoded data

; d0	byte to be encoded (putmfmbyte)
; d1	trashed by putmfmbyte
; d3	used by putmfmbyte
; d5	sector number
; d6	general counter of byte spans
; d7	sector countdown

	sub.w	#2,fp+gaplen(sp)   ; gap length between sectors
	move.l	fp+rawbf(sp),a0    ; pointer to mfmencoded buffer
	move.l	fp+crcs(sp),a4     ; pointer to precalculated CRCs
	move.l	fp+trackbf(sp),a5  ; pointer to unencoded data
	lea	_MfmEncode,a2	   ; pointer to MFM lookup table

	move.w	#$9254,d0	   ; a track starts with a gap
	moveq	#INDEXGAP-1,d6	   ; (60 * $4e)
ingl	move.w	d0,(a0)+           ; mfmencoded = $9254
	dbf	d6,ingl

	lea	-6(sp),sp          ; Reserve room for SectorHeader
fp	set	fp+6
	move.w	fp+numsecs(sp),d7  ; number of sectors to encode
	subq.w	#1,d7		   ; minus 1 for dbra
	moveq	#0,d5		   ; start with first sector

secloop:
	move.w	#$aaaa,d0	   ; a sector starts with a gap containing
	moveq	#IDGAP2-1,d6	   ; 12 * 0 (mfm = $aaaa)
id2gl	move.w	d0,(a0)+
	dbf	d6,id2gl

	move.w	#SYNC,d0	   ; The ID field begins here, starting
	move.w	d0,(a0)+           ; with 3 syncs (3 * $a1) with a missing
	move.w	d0,(a0)+           ; clock bit
	move.w	d0,(a0)+

	move.w	#$5554,(a0)+       ; ID-Address mark ($fe)

	move.l	sp,a3		   ; pointer to Sector-ID buffer

	moveq	#$5554&1,d3	   ; preload d3 for the putmfmbyte routine
	move.b	fp+track+1(sp),0(a3)  ; insert current track number
	move.b	fp+side+1(sp),1(a3)   ; side number
	addq.w	#1,d5		   ; sectors start with 1 instead of 0
	move.b	d5,2(a3)           ; sector number
	move.b	#MS_BPScode,3(a3)  ; sector length 512 bytes
	bsr	HeaderCRC	   ; calculate checksum
	move.w	d0,IDDATA(a3)      ; put it past the data

	moveq	#IDDATA+IDCRC-1,d6 ; 6 bytes Sector-ID
sidl	move.b	(a3)+,d0           ; get one byte
	bsr	putmfmbyte	   ; encode it
	dbf	d6,sidl 	   ; end of buffer ?

	moveq	#$4e,d0 	   ; recalculate the MFM value of the
	bsr	putmfmbyte	   ; first gap byte

	moveq	#DATAGAP1-1-1,d6   ; GAP consisting of
	move.w	#$9254,d0	   ; 22 * $4e
dg1l	move.w	d0,(a0)+
	dbf	d6,dg1l

	moveq	#DATAGAP2-1,d6	   ; GAP consisting of
	move.w	#$aaaa,d0	   ; 12 * 0 (mfm = $aaaa)
dg2l	move.w	d0,(a0)+
	dbf	d6,dg2l

	move.w	#SYNC,d0	   ; Sector data
	move.w	d0,(a0)+           ; starts with 3 syncs
	move.w	d0,(a0)+
	move.w	d0,(a0)+

	move.w	#$5545,(a0)+       ; Data Address Mark ($fb)

	moveq	#$5545&1,d3	   ; preload d3
	move	#MS_BPS-1,d6	   ; a sector has 512 bytes
dblockl move.b	(a5)+,d0           ; get one byte from the buffer
	bsr	putmfmbyte	   ; encode it
	dbf	d6,dblockl	   ; end of sector ?

	move.b	(a4)+,d0           ; get first byte of CRC
	bsr	putmfmbyte	   ; encode it
	move.b	(a4)+,d0           ; get second byte
	bsr	putmfmbyte	   ; encode it

	moveq	#$4e,d0 	   ; recalculate the MFM value of the
	bsr	putmfmbyte	   ; first gap byte -> -1 in following loop

;	moveq	#DATAGAP3-1-1,d6   ; sector ends with a gap
	move.w	fp+gaplen(sp),d6   ; sector ends with a gap, -1 for dbf
	move.w	#$9254,d0	   ; 80 * $4e
dg3l	move.w	d0,(a0)+
	dbf	d6,dg3l

	dbf	d7,secloop	   ; next sector. d5 has been incremented

	lea	6(sp),sp           ; Free room for SectorHeader
fp	set	fp-6

	move.l	fp+rawbf(sp),d6    ; pointer to mfmencoded buffer
	add.l	#WLEN,d6	   ; end of encoded buffer
	move.l	a0,d0		   ; (I really want to   sub.l a0,d6 )
	sub.l	d0,d6		   ; length of the remains
	lsr.l	#1,d6		   ; turn into words

	move.w	#$9254,d0	   ; Fill the end of the track with $4e
endgl	move.w	d0,(a0)+           ; $9254 mfm encoded
	dbf	d6,endgl

	movem.l (sp)+,d2-d7/a2-a6
	rts

; putmfmbyte encodes one byte (in D0) into MSDOS MFM format to the location
; pointed by A0. D3 has to be preserved between calls !
; A2 must contain the pointer to the encoding table.
; Destroys D0, D1. Updates A0 and D3. Requires A0, D0, D3.

putmfmbyte
	moveq	#16-4,d1
	lsl.l	d1,d0		; split the byte into two nibbles
	lsr.w	d1,d0		; low nibble is in bits 0..15
				; high nibble in bits 16..31
	swap	d0		; process high nibble first
	and.w	#$0f,d0 	; mask out unwanted bits
	move.b	0(a2,d0.w),d1   ; get mfmencoded nibble from table
	btst	#6,d1		; we now have to work out if
	bne.s	1$		; the high bit of the unencoded data
	btst	#0,d3		; byte and the low bit of the last
	bne.s	1$		; encoded data are both 0. if this is the
	bset	#7,d1		; case the first clock bit has to be '1'
1$	move.b	d1,(a0)+        ; write high (encoded) nibble
	swap	d0		; process low nibble
	move.b	0(a2,d0.w),d3   ; ....same as above
	btst	#6,d3
	bne.s	2$
	btst	#0,d1
	bne.s	2$
	bset	#7,d3
2$	move.b	d3,(a0)+
	rts

#endasm
#endif				/* READONLY */
#asm

; The CRC is computed not only over the actual data, but including
; the SYNC mark (3 * $a1) and the 'ID/DATA - Address Mark' ($fe/$fb).
; As we don't read or encode these fields into our buffers, we have to
; preload the registers containing the CRC with the values they would have
; after stepping over these fields.
;
; How CRCs "really" work:
;
; First, you should regard a bitstring as a series of coefficients of
; polymomials. We calculate with these polynomials in modulo-2
; arithmetic, in which both add and subtract are done the same as
; exclusive-or. Now, we modify our data (a very long polynomial) in
; such a way that it becomes divisible by the CCITT-standard 16-bit
;		 16   12   5
; polynomial:	x  + x	+ x + 1, represented by $11021. The easiest
; way to do this would be to multiply (using proper arithmetic) our
; datablock with $11021. So we have:
;   data * $11021		 =
;   data * ($10000 + $1021)      =
;   data * $10000 + data * $1021
; The left part of this is simple: Just add two 0 bytes. But then
; the right part (data $1021) remains difficult and even could have
; a carry into the left part. The solution is to use a modified
; multiplication, which has a result that is not correct, but with
; a difference of any multiple of $11021. We then only need to keep
; the 16 least significant bits of the result.
;
; The following algorithm does this for us:
;
;   unsigned char *data, c, crclo, crchi;
;   while (not done) {
;	c = *data++ + crchi;
;	crchi = (@ c) >> 8 + crclo;
;	crclo = @ c;
;   }
;
; Remember, + is done with EOR, the @ operator is in two tables (high
; and low byte separately), which is calculated as
;
;      $1021 * (c & $F0)
;  xor $1021 * (c & $0F)
;  xor $1021 * (c >> 4)         (* is regular multiplication)
;
;
; Anyway, the end result is the same as the remainder of the division of
; the data by $11021. I am afraid I need to study theory a bit more...


; This is the entry to calculate the checksum for the sector-id field
; requires:  a3 = pointer to the unencoded data
; returns:   d0 = CRC

HeaderCRC:
	movem.l  d1-d3/a3-a5,-(sp) ; save registers
	move.w	 #$b2,d0	   ; preload registers
	moveq	 #$30,d1	   ; (CRC for $a1,$a1,$a1,$fb)
	moveq	 #3,d3		   ; calculate checksum for 4 bytes
	bra.s	 getCRC 	   ; (=track,side,sector,sectorlength)

; This is the entry to calculate the checksum for the data field
; requires:  a3 = pointer to the unencoded data
; returns:   d0 = CRC

DataCRC:
	movem.l  d1-d3/a3-a5,-(sp) ; save registers
	bra.s	DataCRC1

; C entry point for DataCRC(byte *data)

_DataCRC:
	movem.l d1-d3/a3-a5,-(sp) ; save registers
fp	set	(4*(3+3))+4
data	set	0
	move.l	fp+data(sp),a3    ; get parameter
DataCRC1:
	move.w	 #$e2,d0	   ; preload the CRC registers
	move.w	 #$95,d1	   ; (CRC for $a1,$a1,$a1,$fe)
	move.w	 #MS_BPS-1,d3	   ; a sector 512 bytes

getCRC	lea	 CRCTable1,a4
	lea	 CRCTable2,a5
	moveq	 #0,d2

1$	move.b	 (a3)+,d2
	eor.b	 d0,d2
	move.b	 0(a4,d2.w),d0
	eor.b	 d1,d0
	move.b	 0(a5,d2.w),d1
	dbf	 d3,1$

	lsl.w	 #8,d0
	move.b	 d1,d0
	movem.l  (sp)+,d1-d3/a3-a5
	rts


CRCTable1:
	dc.b $00,$10,$20,$30,$40,$50,$60,$70,$81,$91,$a1,$b1,$c1,$d1,$e1,$f1
	dc.b $12,$02,$32,$22,$52,$42,$72,$62,$93,$83,$b3,$a3,$d3,$c3,$f3,$e3
	dc.b $24,$34,$04,$14,$64,$74,$44,$54,$a5,$b5,$85,$95,$e5,$f5,$c5,$d5
	dc.b $36,$26,$16,$06,$76,$66,$56,$46,$b7,$a7,$97,$87,$f7,$e7,$d7,$c7
	dc.b $48,$58,$68,$78,$08,$18,$28,$38,$c9,$d9,$e9,$f9,$89,$99,$a9,$b9
	dc.b $5a,$4a,$7a,$6a,$1a,$0a,$3a,$2a,$db,$cb,$fb,$eb,$9b,$8b,$bb,$ab
	dc.b $6c,$7c,$4c,$5c,$2c,$3c,$0c,$1c,$ed,$fd,$cd,$dd,$ad,$bd,$8d,$9d
	dc.b $7e,$6e,$5e,$4e,$3e,$2e,$1e,$0e,$ff,$ef,$df,$cf,$bf,$af,$9f,$8f
	dc.b $91,$81,$b1,$a1,$d1,$c1,$f1,$e1,$10,$00,$30,$20,$50,$40,$70,$60
	dc.b $83,$93,$a3,$b3,$c3,$d3,$e3,$f3,$02,$12,$22,$32,$42,$52,$62,$72
	dc.b $b5,$a5,$95,$85,$f5,$e5,$d5,$c5,$34,$24,$14,$04,$74,$64,$54,$44
	dc.b $a7,$b7,$87,$97,$e7,$f7,$c7,$d7,$26,$36,$06,$16,$66,$76,$46,$56
	dc.b $d9,$c9,$f9,$e9,$99,$89,$b9,$a9,$58,$48,$78,$68,$18,$08,$38,$28
	dc.b $cb,$db,$eb,$fb,$8b,$9b,$ab,$bb,$4a,$5a,$6a,$7a,$0a,$1a,$2a,$3a
	dc.b $fd,$ed,$dd,$cd,$bd,$ad,$9d,$8d,$7c,$6c,$5c,$4c,$3c,$2c,$1c,$0c
	dc.b $ef,$ff,$cf,$df,$af,$bf,$8f,$9f,$6e,$7e,$4e,$5e,$2e,$3e,$0e,$1e

CRCTable2:
	dc.b $00,$21,$42,$63,$84,$a5,$c6,$e7,$08,$29,$4a,$6b,$8c,$ad,$ce,$ef
	dc.b $31,$10,$73,$52,$b5,$94,$f7,$d6,$39,$18,$7b,$5a,$bd,$9c,$ff,$de
	dc.b $62,$43,$20,$01,$e6,$c7,$a4,$85,$6a,$4b,$28,$09,$ee,$cf,$ac,$8d
	dc.b $53,$72,$11,$30,$d7,$f6,$95,$b4,$5b,$7a,$19,$38,$df,$fe,$9d,$bc
	dc.b $c4,$e5,$86,$a7,$40,$61,$02,$23,$cc,$ed,$8e,$af,$48,$69,$0a,$2b
	dc.b $f5,$d4,$b7,$96,$71,$50,$33,$12,$fd,$dc,$bf,$9e,$79,$58,$3b,$1a
	dc.b $a6,$87,$e4,$c5,$22,$03,$60,$41,$ae,$8f,$ec,$cd,$2a,$0b,$68,$49
	dc.b $97,$b6,$d5,$f4,$13,$32,$51,$70,$9f,$be,$dd,$fc,$1b,$3a,$59,$78
	dc.b $88,$a9,$ca,$eb,$0c,$2d,$4e,$6f,$80,$a1,$c2,$e3,$04,$25,$46,$67
	dc.b $b9,$98,$fb,$da,$3d,$1c,$7f,$5e,$b1,$90,$f3,$d2,$35,$14,$77,$56
	dc.b $ea,$cb,$a8,$89,$6e,$4f,$2c,$0d,$e2,$c3,$a0,$81,$66,$47,$24,$05
	dc.b $db,$fa,$99,$b8,$5f,$7e,$1d,$3c,$d3,$f2,$91,$b0,$57,$76,$15,$34
	dc.b $4c,$6d,$0e,$2f,$c8,$e9,$8a,$ab,$44,$65,$06,$27,$c0,$e1,$82,$a3
	dc.b $7d,$5c,$3f,$1e,$f9,$d8,$bb,$9a,$75,$54,$37,$16,$f1,$d0,$b3,$92
	dc.b $2e,$0f,$6c,$4d,$aa,$8b,$e8,$c9,$26,$07,$64,$45,$a2,$83,$e0,$c1
	dc.b $1f,$3e,$5d,$7c,$9b,$ba,$d9,$f8,$17,$36,$55,$74,$93,$b2,$d1,$f0

#endasm

/* INDENT ON */
