********************************************************************************
* hackdisk.s -- my very own version of trackdisk.device
* Copyright (C) 1992 by Dan Babcock
*
* Version history:
* V1.00 04/25/92 First version. Supports 880K drives only.
* V1.01 04/28/92 Optimized CopyMem
* V1.02 04/30/92 More intelligent write algorithm fixes performance problem
* V1.03 05/03/92 Sets IO_ACTUAL
* V1.04 06/24/92 Fixes a couple bugs: RemChangeInt works now, and disk change
*                errors are only reported when it makes sense.
*                CrossDOSV5/CrossPC flushed out these bugs.
* V1.10 07/04/92 Compatible with Kickstart 1.2 and higher.
*                Fixed Seek (IO_OFFSET is in bytes, not tracks).
*                Released on Fish 697!
* V1.11 08/03/92 Fixed bug in readtrack routine - buffer not cleared if error
*                in some cases (no sync).
*                Fills TC_Userdata with bogus value to fool CrossDOS under Kick
*                1.2/1.3.
*                Puts -1 in IO_DEVICE on open error. Some (though not many)
*                applications depend on this.
*                Using a slightly more creative "invalid sync word" (was 0).
*                This should fix many drive incompatibility problems.
*                Glitch in disk check code fixed (only affected non-NoClick mode).
* V1.12 08/16/92 CMD_STOP/CMD_START should work now.
********************************************************************************

;This source was assembled with Macro68 release 3.

	exeobj
	objfile	'devs:hackdisk.device'
	MC68000
	multipass

;Standard register usage:
;A6            - ExecBase or $dff000
;A5 (A_DEVICE) - device ptr
;A4 (A_UNIT)   - unit ptr
;A3 (A_IO)     - IORequest

	IFND	SYS
SYS	macro
	jsr	_LVO\1(a6)
	endm
	ENDC

	IFND	_custom
_custom	equ	$dff000
	ENDC

;There are supposed to be two modes of operation, the RamKick (ROM) mode
;and the DEVS:/mountlist (DISK) mode. However, the disk mode is only
;half-implemented, because I decided at some point it was useless.
ROM	equ	1
DISK	equ	2	;NOTE: Not currently functioning!
TYPE	equ	ROM

;Set INFO_LEVEL to 1 for full debugging output over the internal serial port.
INFO_LEVEL	equ	0

A_DEVICE	equr	a5
A_UNIT	equr	a4
A_IO	equr	a3


VERSION	equ	127
REVISION	equ	12
MYPRI	equ	0

MD_NUMUNITS	equ	4
STACKSIZE	equ	4000
TASKPRI	equ	5

RETRYCNT	equ	3

;Put a message to the serial port.  Used like so:
;
;PUTDEBUG	<'Init: called'>
;
;Parameters can be printed out by pushing them on the stack and
;adding the appropriate C printf-style % formatting commands.

PUTDEBUG 	macro	;<msg>
	ifne	INFO_LEVEL
	movem.l	d0-d1/a0-a1,-(sp)
	lea	.msg\@(pc),a0	;Point to static format string
	lea	16(sp),a1	;Point to args
	bsr	KPutFmt
	movem.l	(sp)+,d0-d1/a0-a1
	bra	.end\@
.msg\@:
	dc.b	\1,$a,0
	even
.end\@:
	endc
	endm

*--------------------------------------------------------------------
*
* Driver error defines
*
*--------------------------------------------------------------------

;TDERR_NotSpecified	EQU	20	; general catchall
;TDERR_NoSecHdr		EQU	21	; couldn't even find a sector
;TDERR_BadSecPreamble	EQU	22	; sector looked wrong
;TDERR_BadSecID		EQU	23	; ditto
;TDERR_BadHdrSum		EQU	24	; header had incorrect checksum
;TDERR_BadSecSum		EQU	25	; data had incorrect checksum
;TDERR_TooFewSecs	EQU	26	; couldn't find enough sectors
;TDERR_BadSecHdr		EQU	27	; another "sector looked wrong"
;TDERR_WriteProt		EQU	28	; can't write to a protected disk
;TDERR_DiskChanged	EQU	29	; no disk in the drive
;TDERR_SeekError		EQU	30	; couldn't find track 0
;TDERR_NoMem		EQU	31	; ran out of memory
;TDERR_BadUnitNum	EQU	32	; asked for a unit > NUMUNITS
;TDERR_BadDriveType	EQU	33	; not a drive that trackdisk groks
;TDERR_DriveInUse	EQU	34	; someone else allocated the drive
;TDERR_PostReset		EQU	35	; user hit reset; awaiting doom

; STRUCTURE TIMEVAL,0
;	ULONG	TV_SECS
;	ULONG	TV_MICRO
;	LABEL	TV_SIZE

;Unit structure

*--------------------------------------------------------------------
*
* Public portion of unit structure
*
*--------------------------------------------------------------------

;*------ UNIT_FLAG definitions:

;These are bogus, but I won't re-define them.
;    BITDEF  UNIT,ACTIVE,0		; driver is active
;    BITDEF  UNIT,INTASK,1		; running in driver's task
	BITDEF	UNIT,DiskInDrive,2
	BITDEF	UNIT,WriteProtected,3

; STRUCTURE TDU_PUBLICUNIT,UNIT_SIZE
;	UWORD	TDU_COMP01TRACK		; track for first precomp
;	UWORD	TDU_COMP10TRACK		; track for second precomp
;	UWORD	TDU_COMP11TRACK		; track for third precomp
;	ULONG	TDU_STEPDELAY		; time to wait after stepping
;	ULONG	TDU_SETTLEDELAY		; time to wait after seeking
;	UBYTE	TDU_RETRYCNT		; # of times to retry
;	UBYTE	TDU_PUBFLAGS		; public flags, see below
;	UWORD	TDU_CURRTRK		; track heads are over
					;  (ONLY ACCESS WHILE UNIT IS STOPPED!)
;	ULONG	TDU_CALIBRATEDELAY	; time to wait after stepping
					; for recalibrate
;	ULONG	TDU_COUNTER		; counter for disk changes
					;  (ONLY ACCESS WHILE UNIT IS STOPPED!)
;	LABEL	TDU_PUBLICUNITSIZE
;*--------------------------------------------------------------------

	STRUCTURE	MyUnit,TDU_PUBLICUNITSIZE
	STRUCT	Drive_LastCheck,TV_SIZE	;last time diskchange was checked
	STRUCT	ChangeIntList,MLH_SIZE	;must be initialized!!!
	LONG	TDRemoveInt

	IFEQ	TYPE-DISK
;not used by ROM driver
	STRUCT	ChangeInt,IS_SIZE
	STRUCT	TDPort,MP_SIZE
	STRUCT	TDIORequest,IOTD_SIZE
	ENDC

	BYTE	UnitNum
	BYTE	MyUnit_Pad
;MUST be word-aligned!!!
	LABEL	MyUnit_Sizeof

;*
;* Flags for TDU_PUBFLAGS:
;*
;	BITDEF	TDP,NOCLICK,0		; set to enable noclickstart
	BITDEF	TDP,VERIFY,1

;Device global structure -- accessed as positive offsets from device base
;Note: The stuff in LIB_SIZE is *required*. Anything else is up to you...
	STRUCTURE	DeviceGlobals,LIB_SIZE
	STRUCT	TaskPort,MP_SIZE	;MsgPort for task
	STRUCT	Task,TC_SIZE
	STRUCT	TaskStack,STACKSIZE
	STRUCT	TempTimeVal,TV_SIZE
	LONG	SegList	;not used for ROM version
	STRUCT	TimerIORequest,IOTV_SIZE
	STRUCT	TimerPort,MP_SIZE
	STRUCT	LastCheck,TV_SIZE	;last time diskchange was checked
	STRUCT	DiskResourceUnit,DRU_SIZE	;must be initialized!!!
	STRUCT	DiskResourcePort,MP_SIZE	;must be initialized!!!

;Allocated memory pointers
	LONG	RawBuffer
	LONG	DecodedBuffer
	LONG	VerifyBuffer

	STRUCT	SectorLabels,11*16
	STRUCT	Unit0,MyUnit_Sizeof
	STRUCT	Unit1,MyUnit_Sizeof
	STRUCT	Unit2,MyUnit_Sizeof
	STRUCT	Unit3,MyUnit_Sizeof
	LONG	GraphBase
	LONG	IntBase
	LONG	DiskResourceBase
	LONG	CIABase
	WORD	SyncCount
	WORD	IndexDskLen
	LONG	WriteMap

	LABEL	StartSigs
	LONG	SyncSig
	LONG	BlockSig
	LABEL	EndSigs

	BYTE	BufferTrack
	BYTE	BufferDrive
	BYTE	DEV_FLAGS
	BYTE	MotorState
	BYTE	InquireBits
	LABEL	MyDev_Sizeof

NumSigs	equ	(EndSigs-StartSigs)/4

;Bit definitions for DEV_FLAGS:
	BITDEF	DEV,Dirty,0	;buffered track has been changed
	BITDEF	DEV,Stopped,1	;device is stopped
	BITDEF	DEV,Verify,2	;used by BlockInt for verify

;	BITDEF	DEV,Test,3	;for debugging only


	IFEQ	TYPE-DISK
;The first executable location.  This should return an error in case someone
;tried to run us as a program (instead of loading us as a device).
	moveq	#-1,d0
	rts
	ENDC

;A romtag structure.  After your driver is brought in from disk, the
;disk image will be scanned for this structure to discover magic constants
;about you (such as where to start running you from...).

RomTag:
	dc.w	RTC_MATCHWORD	;$4AFC ('illegal' opcode)
	dc.l	RomTag
	dc.l	EndCode	;pointer to end of code
	dc.b	RTF_AUTOINIT+RTF_COLDSTART	;set things up automatically
	dc.b	VERSION
	dc.b	NT_DEVICE	;module type (either device or library)
	dc.b	MYPRI	;usually not important
	dc.l	Name	;name used in OpenDevice
	dc.l	IDString	;optional
	dc.l	Init	;more init info

	IFEQ	TYPE-ROM
Name:	dc.b	'trackdisk.device',0
	even
	ENDC
	IFEQ	TYPE-DISK
Name:	dc.b	'hackdisk.device',0		;this MUST be the same as the
	even				;disk-resident name!
	ENDC

IDString:	dc.b	'Hackdisk V1.12 - Copyright (C) 1992 by Dan Babcock',$a,0
	even
DiskResourceName:
	dc.b	'disk.resource',0
TimerName:
	dc.b	'timer.device',0
GraphName:
	dc.b	'graphics.library',0
CIAName:	dc.b	'ciab.resource',0
IntName:	dc.b	'intuition.library',0
	IFEQ	TYPE-DISK
TDName:	dc.b	'trackdisk.device',0
	ENDC
	even

;The romtag specified that we were "RTF_AUTOINIT".  This means that the
;RT_INIT structure member points to one of these tables below.  If the
;AUTOINIT bit was not set then RT_INIT would point to a routine to run.

Init:	dc.l	MyDev_Sizeof	;data space size (at least
				;LIB_SIZE!!)
	dc.l	FuncTable	;pointer to function initializers
	dc.l	DataTable	;pointer to data initializers
	dc.l	InitRoutine	;routine to run

FuncTable:
	dc.w	-1	;this indicates that the following are offsets,
			;rather than absolute addresses
	dc.w	Open-FuncTable	;standard system routines
	dc.w	_Close-FuncTable
	dc.w	Expunge-FuncTable
	dc.w	.Null-FuncTable	;Reserved for future use!
	dc.w	BeginIO-FuncTable	;device definitions
	dc.w	AbortIO-FuncTable
	dc.w	-1	;function table end marker
.Null:	moveq	#0,d0
	rts

;The data table initializes static data structures. The format is
;specified in exec/InitStruct routine's manual pages.  The
;INITBYTE/INITWORD/INITLONG macros are in the file "exec/initializers.i".
;The first argument is the offset from the device base for this
;byte/word/long. The second argument is the value to put in that cell.
;The table is null terminated

DataTable:
	INITBYTE	LN_TYPE,NT_DEVICE
	INITLONG	LN_NAME,Name
	INITBYTE	LIB_FLAGS,LIBF_SUMUSED+LIBF_CHANGED
	INITWORD	LIB_VERSION,VERSION
	INITWORD	LIB_REVISION,REVISION
	INITLONG	LIB_IDSTRING,IDString
	dc.w	0	;terminate list (only one byte needed)

CmdTable:	dc.w	Invalid-CmdTable		;0	CMD_INVALID
	dc.w	Invalid-CmdTable		;1	CMD_RESET
	dc.w	Read-CmdTable		;2	CMD_READ
	dc.w	Write-CmdTable		;3	CMD_WRITE / ETD_
	dc.w	Update-CmdTable		;4	CMD_UPDATE / ETD_
	dc.w	Clear-CmdTable		;5	CMD_CLEAR / ETD_
	dc.w	MyStop-CmdTable		;6	CMD_STOP / ETD_
	dc.w	Start-CmdTable		;7	CMD_START
	dc.w	Invalid-CmdTable		;8	CMD_FLUSH
	dc.w	Motor-CmdTable		;9	TD_MOTOR / ETD_
	dc.w	TDSeek-CmdTable		;10	TD_SEEK / ETD_
	dc.w	Format-CmdTable		;11	TD_FORMAT
	dc.w	TDRemove-CmdTable		;12	TD_REMOVE
	dc.w	ChangeNum-CmdTable		;13	TD_CHANGENUM
	dc.w	ChangeState-CmdTable	;14	TD_CHANGESTATE
	dc.w	ProtStatus-CmdTable		;15	TD_PROTSTATUS
	dc.w	TDRawRead-CmdTable		;16	TD_RAWREAD
	dc.w	RawWrite-CmdTable		;17	TD_RAWWRITE
	dc.w	GetDriveType-CmdTable	;18	TD_GETDRIVETYPE
	dc.w	GetNumTracks-CmdTable	;19	TD_GETNUMTRACKS
	dc.w	AddChangeInt-CmdTable	;20	TD_ADDCHANGEINT
	dc.w	RemChangeInt-CmdTable	;21	TD_REMCHANGEINT
	dc.w	GetGeometry-CmdTable	;22	TD_GETGEOMETRY
	dc.w	Invalid-CmdTable		;23	TD_EJECT
EndCmdTable:
HighestCommand	equ	((EndCmdTable-CmdTable)/2)-1
Invalid:	move.b	#IOERR_NOCMD,IO_ERROR(A_IO)
	rts

InitRoutine:
;A0 - segment
;D0 - device ptr

;Returns with the device ptr still in D0 if successful, otherwise zero.

	PUTDEBUG	<'InitRoutine: Entered'>
	movem.l	d1-d7/a0-a6,-(sp)
	move.l	d0,A_DEVICE
	move.l	a0,SegList(A_DEVICE)
	move.l	4,a6

;Initialize ports
	lea	TaskPort(A_DEVICE),a0
	bsr	InitPort
	lea	TimerPort(A_DEVICE),a0
	bsr	InitPort
 
;Invalidate track buffer
	bsr	Clear

;Open libraries/resources

	lea	DiskResourceName(pc),a1	;'disk.resource'
	SYS	OpenResource
	move.l	d0,DiskResourceBase(A_DEVICE)
	beq	.Failed
	lea	CIAName(pc),a1	;'ciab.resource'
	SYS	OpenResource
	move.l	d0,CIABase(A_DEVICE)
	beq	.Failed

	lea	GraphName(pc),a1
	SYS	OldOpenLibrary
	move.l	d0,GraphBase(A_DEVICE)
	beq	.Failed
	lea	IntName(pc),a1
	SYS	OldOpenLibrary
	move.l	d0,IntBase(A_DEVICE)
	beq	.Failed

;Allocate CHIP memory. (Note: This will change when I support other drive
;types).
	move.l	#DISK_RawBufSize,d0
	move.l	#MEMF_CHIP,d1
	SYS	AllocMem
	move.l	d0,RawBuffer(A_DEVICE)
	beq	.Failed

	move.l	#512*11,d0
	move.l	#MEMF_CHIP,d1
	SYS	AllocMem
	move.l	d0,DecodedBuffer(A_DEVICE)
	beq	.Failed

	move.l	#(1088*11)+2,d0
	move.l	#MEMF_CHIP,d1
	SYS	AllocMem
	move.l	d0,VerifyBuffer(A_DEVICE)
	beq	.Failed

;Set up disk.resource structure.
	lea	DiskResourcePort(A_DEVICE),a0
	bsr	InitPort
	move.l	a0,DiskResourceUnit+MN_REPLYPORT(A_DEVICE)
;	move.b	#PA_SIGNAL,MP_FLAGS(a0)	;not needed (0)
	lea	Name(pc),a0
	move.l	a0,DiskResourceUnit+LN_NAME(A_DEVICE)

	move.l	A_DEVICE,DiskResourceUnit+DRU_DISCBLOCK+IS_DATA(A_DEVICE)
	move.l	A_DEVICE,DiskResourceUnit+DRU_DISCSYNC+IS_DATA(A_DEVICE)
	move.l	A_DEVICE,DiskResourceUnit+DRU_INDEX+IS_DATA(A_DEVICE)
	lea	BlockInt(pc),a0
	move.l	a0,DiskResourceUnit+DRU_DISCBLOCK+IS_CODE(A_DEVICE)
	lea	SyncInt(pc),a0
	move.l	a0,DiskResourceUnit+DRU_DISCSYNC+IS_CODE(A_DEVICE)
	lea	IndexInt(pc),a0
	move.l	a0,DiskResourceUnit+DRU_INDEX+IS_CODE(A_DEVICE)
	moveq	#NT_INTERRUPT,d0
	move.b	d0,DiskResourceUnit+DRU_DISCBLOCK+LN_TYPE(A_DEVICE)
	move.b	d0,DiskResourceUnit+DRU_DISCSYNC+LN_TYPE(A_DEVICE)
	move.b	d0,DiskResourceUnit+DRU_INDEX+LN_TYPE(A_DEVICE)

;Initialize timer IORequest (except signal)
	lea	TimerPort(A_DEVICE),a0
;	move.b	#PA_SIGNAL,MP_FLAGS(a0)	;not needed (0)
	move.l	a0,TimerIORequest+MN_REPLYPORT(A_DEVICE)
	lea	TimerName(pc),a0
	moveq	#UNIT_MICROHZ,d0
	lea	TimerIORequest(A_DEVICE),a1
	moveq	#0,d1
	SYS	OpenDevice
	tst.l	d0
	bne	.Failed

;*** Set up unit structures
	moveq	#0,d2
	lea	Unit0(A_DEVICE),A_UNIT
.UnitLoop:

	IFEQ	TYPE-DISK
	lea	TDPort(A_UNIT),a0
	bsr	InitPort
	move.l	a0,TDIORequest+MN_REPLYPORT(A_UNIT)
	ENDC

	move.b	d2,UnitNum(A_UNIT)
	lea	ChangeIntList(A_UNIT),a0
	NEWLIST	a0

	bset	#1,TDU_PUBFLAGS(A_UNIT)	;verify ON!
	moveq	#-1,d0
	move.w	#80,TDU_COMP01TRACK(A_UNIT)
	move.w	d0,TDU_COMP10TRACK(A_UNIT)
	move.w	d0,TDU_COMP11TRACK(A_UNIT)
	move.l	#3*1000,TDU_STEPDELAY(A_UNIT)
	move.l	#15*1000,TDU_SETTLEDELAY(A_UNIT)
	move.b	#RETRYCNT,TDU_RETRYCNT(A_UNIT)
	move.l	#4*1000,TDU_CALIBRATEDELAY(A_UNIT)

	lea	MyUnit_Sizeof(A_UNIT),A_UNIT
	addq.b	#1,d2
	cmp.b	#MD_NUMUNITS,d2
	blo	.UnitLoop

;Set up task
	move.b	#PA_IGNORE,TaskPort+MP_FLAGS(A_DEVICE)
	move.b	#NT_MSGPORT,TaskPort+LN_TYPE(A_DEVICE)
	lea	Task(A_DEVICE),a1	;Task Control Block

;To make CrossDOS happy under 1.2/1.3
	st	TC_Userdata(a1)

	lea	Name(pc),a0
	move.l	a0,LN_NAME(a1)
	move.b	#TASKPRI,LN_PRI(a1)
	move.b	#NT_TASK,LN_TYPE(a1)
	lea	TaskCode(pc),a2	;initial PC
	sub.l	a3,a3	;final PC
	lea	TaskStack(A_DEVICE),a0
	move.l	a0,TC_SPLOWER(a1)
	lea	STACKSIZE(a0),a0
	move.l	a0,TC_SPUPPER(a1)

	move.l	A_DEVICE,-(a0)	;pass device pointer to task
	move.l	a0,TC_SPREG(a1)
	move.l	a1,TaskPort+MP_SIGTASK(A_DEVICE)
	SYS	AddTask

	move.l	A_DEVICE,d0
	PUTDEBUG	<'InitRoutine: Done'>
.End:	movem.l	(sp)+,d1-d7/a0-a6
	rts
.Failed:
	PUTDEBUG	<'InitRoutine: Failed'>
	bsr	CloseEverything
	moveq	#0,d0
	bra	.End

InitPort:
;Enter with pointer to port in A0. All registers preserved.
	push	a0
	lea	MP_MSGLIST(a0),a0
	NEWLIST	a0
	pop	a0
	rts

CloseEverything:
;This routines cleans up device stuff. Enter with A_DEVICE.

	movem.l	d0-d1/a0-a1/a6,-(sp)
	move.l	4,a6
;Close timer
	tst.b	TimerIORequest+IO_ERROR(A_DEVICE)
	beq	.SkipTimer
	lea	TimerIORequest(A_DEVICE),a1
	SYS	CloseDevice
.SkipTimer:

;Remove task
	lea	Name(pc),a1
	SYS	FindTask
	tst.l	d0
	beq	.SkipTask
	move.l	d0,a1
	SYS	RemTask
.SkipTask:

;Close libraries
	move.l	GraphBase(A_DEVICE),d0
	beq	.SkipGfx
	move.l	d0,a1
	SYS	CloseLibrary
.SkipGfx:
	move.l	IntBase(A_DEVICE),d0
	beq	.SkipInt
	move.l	d0,a1
	SYS	CloseLibrary
.SkipInt:

;Free CHIP RAM. (Note: This will change when I support other drive types).
	move.l	RawBuffer(A_DEVICE),d0
	beq	.SkipRaw
	move.l	d0,a1
	move.l	#DISK_RawBufSize,d0
	SYS	FreeMem
.SkipRaw:
	move.l	DecodedBuffer(A_DEVICE),d0
	beq	.SkipDecoded
	move.l	d0,a1
	move.l	#512*11,d0
	SYS	FreeMem
.SkipDecoded:
	move.l	VerifyBuffer(A_DEVICE),d0
	beq	.SkipVerify
	move.l	d0,a1
	move.l	#(1088*11)+2,d0
	SYS	FreeMem
.SkipVerify:
.End:	movem.l	(sp)+,d0-d1/a0-a1/a6
	rts

;******************************* Task code ******************************

TaskCode:
	PUTDEBUG	<'Task: Entered'>
	move.l	4,a6

;Grab the arguments passed down from our parent
	move.l	4(sp),A_DEVICE	;Device pointer

;General initialization

	lea	StartSigs(A_DEVICE),a2
	moveq	#NumSigs-1,d2	;Number of signals to allocate-1
.SigLoop:
	moveq	#-1,d0	;-1 is any signal at all
	SYS	AllocSignal	;Allocate signals for I/O interrupts
	moveq	#0,d1	;Convert bit number signal mask
	bset	d0,d1
	move.l	d1,(a2)+	;Save in unit structure
	dbra	d2,.SigLoop

	moveq	#-1,d0	;-1 is any signal at all
	SYS	AllocSignal	;Allocate a signal
	move.b	d0,TaskPort+MP_SIGBIT(A_DEVICE)
	move.b	#PA_SIGNAL,TaskPort+MP_FLAGS(A_DEVICE)	;Make message port "live"

	moveq	#-1,d0	;-1 is any signal at all
	SYS	AllocSignal	;Allocate a signal
	move.b	d0,TimerPort+MP_SIGBIT(A_DEVICE)
	move.l	ThisTask(a6),TimerPort+MP_SIGTASK(A_DEVICE)

	moveq	#-1,d0	;-1 is any signal at all
	SYS	AllocSignal	;Allocate a signal
	move.b	d0,DiskResourcePort+MP_SIGBIT(A_DEVICE)
	move.l	ThisTask(a6),DiskResourcePort+MP_SIGTASK(A_DEVICE)

	bsr	GetDrive
	bsr	Inquire

;Disk version only:
;Open trackdisk.device for all valid units and install diskchange handler.
	IFEQ	TYPE-DISK
	moveq	#MD_NUMUNITS-1,d2
	move.b	InquireBits(A_DEVICE),d3
	lea	Unit0(A_DEVICE),A_UNIT
.TDLoop:
	moveq	#-1,d0	;-1 is any signal at all
	SYS	AllocSignal	;Allocate a signal
	move.b	d0,TDPort+MP_SIGBIT(A_UNIT)
	move.l	ThisTask(a6),TDPort+MP_SIGTASK(A_UNIT)
	moveq	#0,d0
	move.b	UnitNum(A_UNIT),d0
	btst	d0,d3
	beq	.NextTD
	moveq	#0,d1
	lea	TDName(pc),a0
	lea	TDIORequest(A_DEVICE),a1
	SYS	OpenDevice
	tst.l	d0
	beq	.TDOK
;Should never get here
..	move.w	#$0f00,$dff180
	bra	..
.TDOK:

;Install a diskchange handler via TD_ADDCHANGEINT.
	lea	TDIORequest(A_DEVICE),a1
	lea	ChangeInt(A_UNIT),a0
	lea	ChangeIntCode(pc),a2
	move.l	a2,IS_CODE(a0)
	move.l	A_UNIT,IS_DATA(a0)
	move.l	a0,IO_DATA(a1)
	move.l	#IS_SIZE,IO_LENGTH(a1)	;this is dumb
	move.w	#TD_ADDCHANGEINT,IO_COMMAND(a1)
	SYS	SendIO

.NextTD:	lea	MyUnit_Sizeof(A_UNIT),A_UNIT
	dbra	d2,.TDLoop
	ENDC

	IFNE	INFO_LEVEL
	push	d0
	moveq	#0,d0
	move.b	InquireBits(A_DEVICE),d0
	move.l	d0,-(sp)
	PUTDEBUG	<'InquireBits=%lx'>
	addq.l	#4,sp
	pop	d0
	ENDC

	bsr	SeekZeroAll	;Send all drives to track zero.
	bsr	FreeDrive

	bra	.NextMessage

;Main loop: Wait for a new message and handle disk changes.

.MainLoop:

;	PUTDEBUG	<'Task: Waiting'>
	moveq	#0,d0
	move.b	MP_SIGBIT+TaskPort(A_DEVICE),d1
	bset	d1,d0
	move.l	#500000,d1
	bsr	TimeOutWait
	tst.l	d0
	bne	.NextMessage

;0.5 seconds have passed without receiving any work, so we check for disk
;changes, then drop into .NextMessage.
;Note: A6 undefined.

.CheckDiskChange:
	moveq	#MD_NUMUNITS-1,d2
	move.b	InquireBits(A_DEVICE),d3
	lea	Unit0(A_DEVICE),A_UNIT
	bsr	GetDrive
	move.l	TimerIORequest+IO_DEVICE(A_DEVICE),a6

;Algorithm (for each unit):
;Check hardware diskchange bit. If disk was ejected, seek to track zero,
;clear DiskInDrive bit. End.
;If no disk in drive, check NoClick bit. If set, immediately step toward
;track zero (using custom step routine). If clear, check the
;Drive_LastChecked time against the current time. If less than 2.5 seconds,
;End. If >2.5 seconds, step the head toward track zero, unless already on
;track zero. Check the hardware diskchange bit. If a disk is present in the
;drive set the DiskInDrive bit and check the hardware write protect status,
;setting WriteProtected as needed. End.
;Note: We also increment the diskchange counter in the public part of the
;unit structure, if a disk was inserted or removed.

.UnitLoop:
	move.b	UnitNum(A_UNIT),d4
	btst	d4,d3
	beq	.NoCheck

	lea	TempTimeVal(A_DEVICE),a0
	bsr	GetSysTime

	btst	#UNITB_DiskInDrive,UNIT_FLAGS(A_UNIT)
	beq	.ThinkNoDisk
	bsr	SelectDriveSameMotor
	btst	#CIAB_DSKCHANGE,ciaapra
	bne	.NoChange
;Disk was removed.
	bclr	#UNITB_DiskInDrive,UNIT_FLAGS(A_UNIT)
	bsr	SeekZero
	bsr	MotorOff
	bsr	Clear
	bra	.Change
.ThinkNoDisk:
	btst	#TDPB_NOCLICK,TDU_PUBFLAGS(A_UNIT)
	beq	.Click
	bsr	SelectDriveSameMotor
	bset	#CIAB_DSKDIREC,ciabprb	;set to "out" (lower tracks)
	bset	#CIAB_DSKSTEP,ciabprb
	bclr	#CIAB_DSKSTEP,ciabprb	;step head
	bset	#CIAB_DSKSTEP,ciabprb
	bsr	Deselect
	move.l	TDU_SETTLEDELAY(A_UNIT),d0
	bsr	delay
	bsr	SelectDriveSameMotor
	bra	.ChkChg
.Click:
	lea	Drive_LastCheck(A_UNIT),a1
	SYS	SubTime
	cmp.l	#2,TV_SECS(a0)
	blo	.NoCheck
	bhi	.SkipMicro
	cmp.l	#500000,TV_MICRO(a0)
	blo	.NoCheck
.SkipMicro:
	lea	Drive_LastCheck(A_UNIT),a0
	bsr	GetSysTime
	move.b	UnitNum(A_UNIT),d2	;for Seek
	move.w	TDU_CURRTRK(A_UNIT),d3
	subq.w	#2,d3
	bpl	.NotZero
	moveq	#2,d3
.NotZero:	bsr	SelectDriveSameMotor
	bsr	SeekNoUpdate
.ChkChg:	btst	#CIAB_DSKCHANGE,ciaapra
	beq	.NoChange	;no disk in drive
	bset	#UNITB_DiskInDrive,UNIT_FLAGS(A_UNIT)
	bclr	#UNITB_WriteProtected,UNIT_FLAGS(A_UNIT)
	btst	#CIAB_DSKPROT,ciaapra
	bne	.Change	;not write protected
	bset	#UNITB_WriteProtected,UNIT_FLAGS(A_UNIT)
.Change:
	bsr	Deselect
	addq.l	#1,TDU_COUNTER(A_UNIT)

;This code notifies users of TD_REMOVE and TD_ADDCHANGEINT via Cause.

	push	a6
	move.l	4,a6
	SYS	Forbid
	move.l	TDRemoveInt(A_UNIT),d0
	beq	.NoRemoveInt
	move.l	d0,a1
	SYS	Cause
.NoRemoveInt:
	move.l	ChangeIntList+LH_HEAD(A_UNIT),a2
.IntLoop:	move.l	(a2),d0
	beq	.DoneInt
	move.l	IO_DATA(a2),a1
	move.l	d0,a2
	SYS	Cause
	bra	.IntLoop
.DoneInt:	SYS	Permit
	pop	a6
.NoChange:
	lea	LastCheck(A_DEVICE),a0
	bsr	GetSysTime
.NoCheck:
	lea	MyUnit_Sizeof(A_UNIT),A_UNIT
	dbra	d2,.UnitLoop
	move.l	4,a6
	bsr	FreeDrive

.NextMessage:
	btst	#DEVB_Stopped,DEV_FLAGS(A_DEVICE)	;See if we are stopped
	bne	.MainLoop	;device is stopped, so ignore messages

	lea	TaskPort(A_DEVICE),a0
	SYS	GetMsg	;Get the next request
	tst.l	d0
	beq	.MainLoop	;no message?
	move.l	d0,A_IO	;Do this request
	move.l	IO_UNIT(A_IO),A_UNIT

;Handle TDB_EXTCOM and dispatch command
	move.w	IO_COMMAND(A_IO),d0

	IFNE	INFO_LEVEL
	swap	d0
	clr.w	d0
	swap	d0
	move.l	d0,-(sp)
	PUTDEBUG	<'Command=%ld'>
	addq.l	#4,sp
	ENDC

	bclr	#TDB_EXTCOM,d0
	beq	.NoExt
	move.l	TDU_COUNTER(A_UNIT),d1
	cmp.l	IOTD_COUNT(A_IO),d1
	bls	.NoExt
.ChgErr:	PUTDEBUG	<'Change error!'>
	move.b	#TDERR_DiskChanged,IO_ERROR(A_IO)
	bra	.Reply
.NoExt:	move.l	#%000000111000111000011100,d1	;specifies which commands should check for disk
	btst	d0,d1
	beq	.NoDiskNeeded
	btst	#UNITB_DiskInDrive,UNIT_FLAGS(A_UNIT)
	beq	.ChgErr
.NoDiskNeeded:
	bsr	GetDrive
	lea	CmdTable(pc),a0
	add.w	d0,d0
	add.w	(a0,d0.w),a0
	moveq	#0,d7	;clear error flag
	jsr	(a0)
	bsr	FreeDrive
	cmp.w	#TD_ADDCHANGEINT,IO_COMMAND(A_IO)
	beq	.SkipReply
.Reply:	move.l	A_IO,a1
	SYS	ReplyMsg
.SkipReply:

;If at least 0.5 seconds has passed since last checking diskchange, do so
;now.
	move.l	TimerIORequest+IO_DEVICE(A_DEVICE),a6
	lea	TempTimeVal(A_DEVICE),a0
	bsr	GetSysTime
	lea	LastCheck(A_DEVICE),a1
	SYS	SubTime
	tst.l	TV_SECS(a0)
	bne	.CheckDiskChange
	cmp.l	#500000,TV_MICRO(a0)
	bhs	.CheckDiskChange
	lea	LastCheck(A_DEVICE),a0
	bsr	GetSysTime
	move.l	4,a6
	bra	.NextMessage

GetSysTime:
	cmp.w	#36,LIB_VERSION(a6)
	bls	.OldKS
	jmp	_LVOGetSysTime(a6)
.OldKS:
;Compatibility routine for 1.2/1.3
	movem.l	d0-d1/a0-a2/a6,-(sp)
	move.l	a0,a2
	lea	TimerIORequest(A_DEVICE),a1
	move.l	4,a6
	move.w	#TR_GETSYSTIME,IO_COMMAND(a1)
	SYS	DoIO
	lea	TimerIORequest(A_DEVICE),a1
	move.l	IOTV_TIME+TV_SECS(a1),TV_SECS(a2)
	move.l	IOTV_TIME+TV_MICRO(a1),TV_MICRO(a2)
	movem.l	(sp)+,d0-d1/a0-a2/a6
	rts


	IFEQ	TYPE-DISK
ChangeIntCode:
	rts
	ENDC

;******************** External device routines ***********************

Open:

;A6 - device ptr
;A1 - IORequest
;D0 - unit number
;D1 - flags (not used)

;Important: On error, MUST put -1 in IO_DEVICE. This routine has no return
;value in d0 (return code is in IO_ERROR).

	PUTDEBUG	<'Open: Entered'>
	moveq	#MD_NUMUNITS,d1
	cmp.l	d1,d0
	bhs	.Error
	btst	d0,InquireBits(a6)
	beq	.Error
	lea	Unit0(a6),a0
	mulu.w	#MyUnit_Sizeof,d0
	add.l	d0,a0
	move.l	a0,IO_UNIT(a1)
	addq.w	#1,LIB_OPENCNT(a6)
	clr.b	IO_ERROR(a1)	;no error
	move.b	#NT_REPLYMSG,LN_TYPE(a1)	;Mark IORequest as "complete"
	PUTDEBUG	<'Open: Done'>
	rts
.Error:
	PUTDEBUG	<'Open: Failed'>

;VirusX didn't like this.
;	move.b	#IOERR_OPENFAIL,IO_ERROR(a1)

;NOTE: Trackdisk will return TDERR_BadDriveType if TDB_ALLOW_NON_3_5 flag set
;in "flags" and the drive is not present. (This is just trivia).

	move.b	#TDERR_BadUnitNum,IO_ERROR(a1)
	moveq	#-1,d0
	move.l	d0,IO_DEVICE(a1)
	rts

_Close:

;A6 - device ptr
;A1 - IORequest

;Must return either 0 or, if the device wishes to be unloaded, the segment
;list.

	PUTDEBUG	<'Close: Called'>
	IFEQ	TYPE-ROM
	moveq	#0,d0
	rts
	ENDC

	IFEQ	TYPE-DISK
	moveq	#0,d0
	subq.w	#1,LIB_OPENCNT(a6)	;Mark us as having one fewer openers
	bne	.End	;See if there is anyone left with us open
	btst	#LIBB_DELEXP,LIB_FLAGS(a6)	;See if we have a delayed expunge pending
	beq	.End
	bsr	Expunge	;Expunge will return with SegList in D0
.End:	rts
	ENDC

Expunge:

;A6 - device ptr

;Must return either 0 or SegList ptr in D0.

	IFEQ	TYPE-ROM
	moveq	#0,d0
	rts
	ENDC

	IFEQ	TYPE-DISK
	movem.l	d2/a2/a6,-(sp)
	tst.w	LIB_OPENCNT(a6)	;See if anyone has us open
	bne	.Delay
	bsr	CloseEverything	;clean up
	move.l	SegList(a6),d2	;Store our seglist in d2
	move.l	a6,a1	;Unlink from device list
	move.l	a6,a2	;save
	move.l	4,a6
	SYS	Remove	 ;Remove first (before FreeMem)
	move.l	a2,a1	 ;device ptr
	moveq	#0,d0
	move.w	LIB_NEGSIZE(a2),d0
	sub.l	d0,a1	;Calculate base of functions
	add.w	LIB_POSSIZE(a2),d0	;Calculate size of functions + data area
	SYS	FreeMem
	move.l	d2,d0	;Set up our return value
.End:	movem.l	(sp)+,d2/a2/a6
	rts
.Delay:	bset	#LIBB_DELEXP,LIB_FLAGS(a6)	;Set the delayed expunge flag
	moveq	#0,d0
	bra	.End
	ENDC

BeginIO:
;A1 - IORequest
;A6 - device ptr

	movem.l	d7/a3-a6,-(sp)
	move.l	a1,A_IO
	move.l	IO_UNIT(A_IO),A_UNIT
	move.l	a6,A_DEVICE
	move.l	4,a6

	clr.b	IO_ERROR(A_IO)
	move.b	#NT_MESSAGE,LN_TYPE(A_IO)	;So WaitIO() is guaranteed to work

;Decide whether the command is immediate or queued
	move.w	IO_COMMAND(A_IO),d1
	bclr	#15,d1
	cmp.w	#HighestCommand,d1
	bhi	.BadCmd
	move.l	#%111011001111000110000011,d0	;specifies what commands are immediate
	btst	d1,d0
	beq	.Queue
	add.w	d1,d1
	lea	CmdTable(pc),a0
	add.w	(a0,d1.w),a0
	moveq	#0,d7	;clear error flag
	jsr	(a0)
.Reply:	btst	#IOB_QUICK,IO_FLAGS(A_IO)
	bne	.End
	move.l	A_IO,a1
	SYS	ReplyMsg
.End:	movem.l	(sp)+,d7/a3-a6
	rts
.Queue:	bclr	#IOB_QUICK,IO_FLAGS(A_IO)	;We did NOT complete this quickly
	lea	TaskPort(A_DEVICE),a0
	move.l	A_IO,a1
	SYS	PutMsg
	bra	.End
.BadCmd:	move.b	#IOERR_NOCMD,IO_ERROR(a1)
	bra	.Reply

AbortIO:

;A6 - device ptr
;A1 - IORequest

	move.b	#IOERR_ABORTED,IO_ERROR(a1)	;We always say we succeed(ed)
	moveq	#0,d0	;another success code
	rts

;**************** Device command (IO_COMMAND) routines *************************

;Note: A6 = ExecBase upon entering a command routine.
;The low-level routines load $DFF000 into A6 when needed.

SaveRegs	setrl	d0-d3/d6-d7/a0-a2/a6

Read:
	PUTDEBUG	<'Read: Called'>
	movem.l	SaveRegs,-(sp)
	bsr	RWSetup
	tst.l	d7
	bne	FinishRW

	bsr	DISK_Update	;required because of complex write scheme
;Check for sector label nonsense
	btst	#7,IO_COMMAND(A_IO)
	beq	.NoLabel
	move.l	IOTD_SECLABEL(A_IO),d2
	bne	ReadSecLabel
.NoLabel:

	bsr	DISK_Read
	PUTDEBUG	<'Read: Done'>
	bra	FinishRW
Format:
Write:	movem.l	SaveRegs,-(sp)
	bsr	RWSetup
	tst.l	d7
	bne	FinishRW

;Check for sector label nonsense
	tst.b	IO_COMMAND(A_IO)
	bpl	.NoLabel
	move.l	IOTD_SECLABEL(A_IO),d2
	bne	WriteSecLabel
.NoLabel:

	bsr	DISK_Write
FinishRW:	clr.l	IO_ACTUAL(A_IO)
	move.b	d7,IO_ERROR(A_IO)

;temporary for debugging
;	tst.b	IO_ERROR(A_IO)
;	beq	.OK
;	move.b	IO_ERROR(A_IO),$100
;.OK:
;


	bne	.Skip
	move.l	IO_LENGTH(A_IO),IO_ACTUAL(A_IO)
.Skip:	movem.l	(sp)+,SaveRegs
	rts
RWSetup:
	move.l	#512*11,d6
	move.l	#_custom,a6
	move.l	IO_LENGTH(A_IO),d0
	move.l	IO_OFFSET(A_IO),d1
	move.l	IO_DATA(A_IO),a0
	move.l	d1,d2	;offset
	add.l	d0,d2	;length
	cmp.l	#901120,d2
	bhi	.Error
	bsr	SelectDrive
	bra	SelectSide
.Error:	moveq	#DISK_BadParameter,d7
	rts

;Here are the routines for dealing with read/write requests that involve
;the sector label. Note that, unlike the usual read/write routines of my
;trackdisk, the normal parameter restrictions (e.g. IO_OFFSET and IO_LENGTH
;must be a multiple of 512) MUST be observed. And I don't check for illegal
;parameters, either. These routines are optimized for compactness rather
;than performance because they are rarely (if ever) used.
;Enter with:
;IO_LENGTH: D0
;IO_OFFSET: D1
;IO_DATA:   A0

ReadSecLabel:
	PUTDEBUG	<'READSECLABEL!'>

	move.l	d2,a1
	move.l	d0,d2
	move.l	#512,d0
.Read:	bsr	DISK_Read
	move.l	d1,d3
	add.l	d0,d1
	add.l	d0,a0
	divu.w	d6,d3	;offset/tracksize
	swap	d3	;remainder=sector# (in bytes)
	lsr.w	#5,d3	;divide by 32 to get label offset
	lea	SectorLabels(A_DEVICE),a2
	lea	(a2,d3.w),a2	;get pointer to label
	moveq	#3,d3
..	move.l	(a2)+,(a1)+	;copy label
	dbra	d3,..
	sub.l	d0,d2
	bne	.Read
	bra	FinishRW

WriteSecLabel:
	PUTDEBUG	<'WRITESECLABEL!'>

	move.l	d2,a1
	move.l	d0,d2
.Write:
	move.l	d1,d3
	divu.w	d6,d3	;offset/tracksize
	swap	d3	;remainder=sector# (in bytes)
	lsr.w	#5,d3	;divide by 32 to get label offset

;We check to see whether we can write a full track. (Somehow, I can't
;ignore performance completely :-)).
	tst.w	d3
	bne	.Read
	cmp.l	d6,d0
	bhs	.FullTrack
.Read:	moveq	#0,d0
	bsr	DISK_Read	;force a track read
	lea	SectorLabels(A_DEVICE),a2
	lea	(a2,d3.w),a2	;get pointer to label
	moveq	#3,d3
..	move.l	(a1)+,(a2)+	;copy label
	dbra	d3,..
	move.l	#512,d0
.L1:	bsr	DISK_Write
	add.l	d0,d1
	add.l	d0,a0
	sub.l	d0,d2
	bne	.Write
	bra	FinishRW
.FullTrack:
	lea	SectorLabels(A_DEVICE),a2
	moveq	#((16*11)/4)-1,d3
..	move.l	(a1)+,(a2)+	;copy all sector labels
	dbra	d3,..
	move.l	d6,d0
	bra	.L1

Update:	move.l	d7,-(sp)
	moveq	#0,d7
	bsr	DISK_Update
	move.b	d7,IO_ERROR(A_IO)
	move.l	(sp)+,d7
	rts

Clear:
;This routine marks the track buffer as invalid.
	st	BufferDrive(A_DEVICE)
	bclr	#DEVB_Dirty,DEV_FLAGS(A_DEVICE)
	rts

Motor:
;Controls the drive motor
;If IO_LENGTH=1, motor on
;   IO_LENGTH=0, motor off
;Old motor state (0=off, anything else means on) stored in IO_ACTUAL.

	push	d2
	clr.l	IO_ACTUAL(A_IO)
	move.b	UnitNum(A_UNIT),d2
	btst	d2,MotorState(A_DEVICE)
	beq	.WasOff
	move.l	#1,IO_ACTUAL(A_IO)
.WasOff:	tst.l	IO_LENGTH(A_IO)
	beq	.Off
;Turn motor on
	bsr	SelectDrive
	bra	.End
.Off:
;Turn motor off
	bclr	d2,MotorState(A_DEVICE)
	bsr	Deselect
	bset	#CIAB_DSKMOTOR,ciabprb	;motor off
	addq.b	#3,d2
	bclr	d2,ciabprb	;select drive X
.End:	pop	d2
	rts

ChangeState:
;Returns whether there is a disk currently in the drive
;IO_ACTUAL=0 if disk in drive
;* EXECUTED IMMEDIATELY, POSSIBILY WITHIN AN INTERRUPT *

	clr.l	IO_ACTUAL(A_IO)
	btst	#UNITB_DiskInDrive,UNIT_FLAGS(A_UNIT)
	bne	.End
	move.l	#1,IO_ACTUAL(A_IO)	;no disk in drive
.End:	rts

ChangeNum:
;* EXECUTED IMMEDIATELY, POSSIBILY WITHIN AN INTERRUPT *
	move.l	TDU_COUNTER(A_UNIT),IO_ACTUAL(A_IO)
	rts

ProtStatus:
;IO_ACTUAL=0 if disk is not write protected
;* EXECUTED IMMEDIATELY, POSSIBILY WITHIN AN INTERRUPT *

;Very important for compatibility: we must return an error (disk changed)
;if there's no disk in the drive.
	btst	#UNITB_DiskInDrive,UNIT_FLAGS(A_UNIT)
	bne	.OK
	move.b	#TDERR_DiskChanged,IO_ERROR(A_IO)
	rts

.OK:	clr.l	IO_ACTUAL(A_IO)
	btst	#UNITB_WriteProtected,UNIT_FLAGS(A_UNIT)
	beq	.End	;not write protected
	move.l	#1,IO_ACTUAL(A_IO)	;no disk in drive
.End:	rts

GetDriveType:
;Returns type of drive in IO_ACTUAL
;* EXECUTED IMMEDIATELY, POSSIBILY WITHIN AN INTERRUPT *
	move.l	#DRIVE3_5,IO_ACTUAL(A_IO)
	rts

GetNumTracks:
;Returns number of tracks (note: not cylinders) in IO_ACTUAL
;* EXECUTED IMMEDIATELY, POSSIBILY WITHIN AN INTERRUPT *
	move.l	#160,IO_ACTUAL(A_IO)
	rts

GetGeometry:
;Fills in DriveGeometry structure pointed to by IO_DATA
	push	a0
	move.l	IO_DATA(A_IO),a0
	move.l	#512,dg_SectorSize(a0)	;in bytes
	move.l	#1760,dg_TotalSectors(a0)	;total # of sectors on drive
	move.l	#80,dg_Cylinders(a0)	;number of cylinders
	move.l	#22,dg_CylSectors(a0)	;number of sectors/cylinder
	move.l	#2,dg_Heads(a0)	;number of surfaces
	move.l	#11,dg_TrackSectors(a0)	;number of sectors/track
	clr.l	dg_BufMemType(a0)	;preferred buffer memory type
	clr.b	dg_DeviceType(a0)	;codes as defined in the SCSI-2 spec
	move.b	#DGF_REMOVABLE,dg_Flags(a0)	;flags, including removable
	clr.w	dg_Reserved(a0)
	pop	a0
	rts

MyStop:	bset	#DEVB_Stopped,DEV_FLAGS(A_DEVICE)
	bsr	Update
	bra	Clear
Start:	movem.l	d0-d1/a0-a1,-(sp)
	bclr	#DEVB_Stopped,DEV_FLAGS(A_DEVICE)
	moveq	#0,d0
	move.b	MP_SIGBIT+TaskPort(A_DEVICE),d1
	bset	d1,d0
	lea	Task(A_DEVICE),a1
	SYS	Signal
	movem.l	(sp)+,d0-d1/a0-a1
	rts

;Note: This seek routine can't really be depended on by user programs,
;because the disk.resource will corrupt the side select bit.
TDSeek:	push	d3
	bsr	SelectDriveSameMotor
	move.l	IO_OFFSET(A_IO),d3
	divu.w	#512*11,d3
	cmp.w	#159,d3
	bhi	.Error
	bsr	Seek
.End:	pop	d3
	rts
.Error:	move.b	#TDERR_NotSpecified,IO_ERROR(A_IO)
	bra	.End

TDRawRead:
	movem.l	d0-d1/d3/a6,-(sp)
	move.l	#_custom,a6
	moveq	#0,d1
	bsr	DoRaw
	movem.l	(sp)+,d0-d1/d3/a6
	rts

RawWrite:
	movem.l	d0-d1/d3/a6,-(sp)
	move.l	#_custom,a6
	btst	#UNITB_WriteProtected,UNIT_FLAGS(A_UNIT)
	bne	.WPErr
	move.w	#1<<14,d1
	bsr	DoRaw
	move.l	#3*1000,d0
	bsr	delay	;post-write delay
.End:	movem.l	(sp)+,d0-d1/d3/a6
	rts
.WPErr:	move.b	#TDERR_WriteProt,IO_ERROR(A_IO)
	bra	.End

DoRaw:
;Enter with bit 14 set in d1 according to read/write (for dsklen)
;Uses d0-d1/d3 (not saved)
;Assumes $dff000 in A6

	clr.l	IO_ACTUAL(A_IO)
	bsr	SelectDrive
	move.l	IO_OFFSET(A_IO),d3
	cmp.l	#159,d3
	bhi	.Error
	bsr	Seek
	tst.l	d7
	bne	.Error

	move.l	IO_DATA(A_IO),dskpt(a6)
	move.w	#ADKF_WORDSYNC,adkcon(a6)
	btst	#IOTDB_WORDSYNC,IO_FLAGS(A_IO)
	beq	.SkipSync
	move.w	#ADKF_SETCLR+ADKF_WORDSYNC,adkcon(a6)
.SkipSync:
	move.w	#ADKF_MSBSYNC,adkcon(a6)
	move.w	#ADKF_SETCLR+ADKF_FAST+ADKF_MFMPREC,adkcon(a6)
	bsr	PreComp
	move.w	#DMAF_SETCLR+DMAF_MASTER+DMAF_DISK,dmacon(a6)	;enable disk DMA
	move.w	#$4489,dsksync(a6)	;set magic sync word
	bsr	ClearSigs
	move.w	#INTF_SETCLR+INTF_DSKBLK,intena(a6)	;enable int
	move.l	IO_LENGTH(A_IO),d0

	cmp.l	#32766,d0
	bhi	.Error
	lsr.l	#1,d0
	bset	#15,d0
	or.w	d1,d0

	btst	#IOTDB_INDEXSYNC,IO_FLAGS(A_IO)
	beq	.NoIndex
	move.w	d0,IndexDskLen(A_DEVICE)
	bsr	EnableIndex
	bra	.Wait
.NoIndex:

	move.w	d0,dsklen(a6)
	move.w	d0,dsklen(a6)
.Wait:	move.l	BlockSig(A_DEVICE),d0
	move.l	#900*1000,d1
	bsr	TimeOutWait
	beq	.NoSync
	move.w	#0,dsklen(a6)
	move.w	#INTF_DSKBLK,intena(a6)	;disable int
.EndIO:	bsr	DisableIndex
	bsr	ClearSigs
	tst.b	IO_ERROR(A_IO)
	bne	.EndRTS
	move.l	IO_LENGTH(A_IO),IO_ACTUAL(A_IO)
.EndRTS:	rts
.Error:	move.b	#TDERR_NotSpecified,IO_ERROR(A_IO)
	rts
.NoSync:	bsr	StopDMA
	move.b	#TDERR_NoSecHdr,IO_ERROR(A_IO)
	bra	.EndIO


AddChangeInt:
;This command uses the linkage fields of the IORequest to add a ChangeInt
;request to a list maintained in the unit structure. This must NOT be
;ReplyMsg'ed to avoid destroying the linkage fields - a special compare in
;the task code handles this.
	movem.l	d0-d1/a0-a1,-(sp)
	move.l	A_IO,a1
	lea	ChangeIntList(A_UNIT),a0
	SYS	AddHead	;could use any list add routine
	movem.l	(sp)+,d0-d1/a0-a1
	rts

RemChangeInt:
;This command unlinks the AddChangeInt request from the task's port.
;This is (and must be) an immediate command.
;Note that because this command occurs asyncronously to the task code, the
;task code that scans the ChangeInt list must be protected with a Forbid.

	movem.l	d0-d1/a0-a1,-(sp)
	SYS	Forbid
	move.l	A_IO,a1
	SYS	Remove
	SYS	Permit
	movem.l	(sp)+,d0-d1/a0-a1
	rts

TDRemove:
;This command is obsolete, but is unfortuntely used by the ROM filesystem.
;It accepts an Interrupt structure (for Cause) in IO_DATA. Only one user per
;unit is permitted.

	tst.l	TDRemoveInt(A_UNIT)
	beq	.Ok
	move.b	#TDERR_DriveInUse,IO_ERROR(A_IO)
	rts
.Ok:	move.l	IO_DATA(A_IO),TDRemoveInt(A_UNIT)
	rts

GetDrive:
;Obtain the use of this unit via disk.resource.

;	PUTDEBUG	<'GetDrive: Called'>
	movem.l	d0-d1/a0-a2/a6,-(sp)
	lea	DiskResourceUnit(A_DEVICE),a2
.GetUnit:
	move.l	DiskResourceBase(A_DEVICE),a6
	move.l	a2,a1
	jsr	DR_GETUNIT(a6)
	tst.l	d0
	bne	.End
	lea	DiskResourcePort(A_DEVICE),a0
	move.l	4,a6
	SYS	WaitPort
	bra	.GetUnit
.End:

	IFEQ	TYPE-DISK
	move.l	TDUnit(A_UNIT),d0
	beq	.EndEnd
	move.l	d0,a0
	move.w	TDU_CURRTRK(a0),TDU_CURRTRK(A_UNIT)
	ENDC

.EndEnd:	movem.l	(sp)+,d0-d1/a0-a2/a6
;	PUTDEBUG	<'GetDrive: Done'>
	rts

FreeDrive:
;Release this unit to other users of disk.resource.

	movem.l	d0-d1/a0-a1/a6,-(sp)

	IFEQ	TYPE-DISK
	move.l	TDUnit(A_UNIT),d0
	beq	.Skip
	move.l	d0,a0
	move.w	TDU_CURRTRK(A_UNIT),TDU_CURRTRK(a0)
.Skip:
	ENDC

	move.l	DiskResourceBase(A_DEVICE),a6
	jsr	DR_GIVEUNIT(a6)
	movem.l	(sp)+,d0-d1/a0-a1/a6
	rts

;***************************** Low-level disk code **************************

;Standard register usage:
;A6            - $dff000
;A5 (A_DEVICE) - device ptr
;A4 (A_UNIT)   - unit ptr
;A3 (A_IO)     - pointer to IO request

;For reading/writing:
;D0.L - length (bytes)
;D1.L - offset (bytes)
;A0.L - buffer

;NOTE: Error code returned in D7.L - 0 if ok, else error

;General errors
DISK_BadParameter	equ	TDERR_NotSpecified

;Read errors
DISK_NoSync	equ	TDERR_NoSecHdr
DISK_BadHeader	equ	TDERR_BadHdrSum
DISK_BadData	equ	TDERR_BadSecSum

;Write errors
DISK_WriteProtected	equ	TDERR_WriteProt
DISK_VerifyError	equ	TDERR_NotSpecified

	IFND	dskpt
dskpt	equ	dskpth
	ENDC

;Note: This is a public value, but it is NOT meant to be changed!!!
DISK_RawBufSize	equ	13630

ciabprb	equ	$bfd100
ciaapra	equ	$bfe001
ciabddrb	equ	$bfd300
ciaaddra	equ	$bfe201

***************************************************************
;Important disk parameters, summary:

;Step rate: 3ms. 4ms when looking for track zero.
;Settle time: 15ms
;Post-write delay: 3ms (officially 1.3ms)
;Side select delay: 2ms (officially 1.3ms)
***************************************************************

DISK_Read:
	movem.l	d0-d5/a0/a1,-(sp)

	move.l	a0,a1	;destination ptr in A1
	move.l	d0,d5

;The meat of the read routine...

.ReadLoop:
	move.l	d1,d3
	divu.w	d6,d3	;d3.w is track #
	move.l	d3,d4
	clr.w	d4
	swap	d4	;d4.l is byte offset into track
	bsr	MinSeek
	tst.l	d4	;any offset?
	bne	.Complex	;yes
	cmp.l	d6,d5	;check remaining length
	blo	.Complex	;if not a track, forget it
	move.l	a1,d0
	btst	#0,d0	;word aligned?
	bne	.Complex	;no, do it the long way
	move.l	a1,a0
	bsr	ReadTrackAndDecodeNoBuffer
	tst.l	d7
	bne	.End
	sub.l	d6,d5	;update length
	beq	.End
	add.l	d6,a1	;update destination pointer
	add.l	d6,d1	;update offset
	bra	.ReadLoop

.Complex:
	move.l	DecodedBuffer(A_DEVICE),a0
	bsr	ReadTrackAndDecodeBuffered
	tst.l	d7
	bne	.End
	add.l	d4,a0
	sub.l	d6,d4
	neg.l	d4
	add.l	d4,d1	;update offset

;D4.L - number of bytes that could be transferred from this track
;D5.L - number of bytes left in the entire Read request

	cmp.l	d5,d4
	bhs	.FinishUp
	sub.l	d4,d5	;update length
	move.l	d4,d0
	bsr	CopyMem
	add.l	d0,a1	;update destination pointer
	bra	.ReadLoop
.FinishUp:
	move.l	d5,d0
	bsr	CopyMem	;use number of bytes left in entire request
.End:	movem.l	(sp)+,d0-d5/a0/a1
	rts

SelectDrive:
;Selects drive AND turns on motor
;Selects UnitNum(A_UNIT).

	movem.l	d0/d2,-(sp)
	move.b	UnitNum(A_UNIT),d2
	bsr	Deselect
	bclr	#CIAB_DSKMOTOR,ciabprb	;motor on
	move.l	d2,d0
	addq.b	#3,d0
	bclr	d0,ciabprb	;select drive X
	bset	d2,MotorState(A_DEVICE)

	moveq	#4,d2
.Wait:	btst	#CIAB_DSKRDY,ciaapra
	beq	.End
	move.l	#100*1000,d0
	bsr	delay
	dbra	d2,.Wait	

.End:	movem.l	(sp)+,d0/d2
	rts

Deselect:	or.b	#CIAF_DSKSEL0+CIAF_DSKSEL1+CIAF_DSKSEL2+CIAF_DSKSEL3,ciabprb	;Deselect all drives
	rts

SelectDriveSameMotor:
;Select a drive WITHOUT changing the current motor state
;Selects UnitNum(A_UNIT).

	push	d2
	move.b	UnitNum(A_UNIT),d2
	bsr	Deselect
	bclr	#CIAB_DSKMOTOR,ciabprb	;motor on
	btst	d2,MotorState(A_DEVICE)
	bne	.WasOn
	bset	#CIAB_DSKMOTOR,ciabprb	;motor off
.WasOn:	addq.b	#3,d2
	bclr	d2,ciabprb	;select drive X
	pop	d2
_RTS:	rts

DISK_Update:
;Flush track buffer if "dirty"

	bclr	#DEVB_Dirty,DEV_FLAGS(A_DEVICE)
	beq	_RTS
	movem.l	d0-d2/a0/a2/a6,-(sp)
	move.l	#_custom,a6

;This is somewhat tricky, because the currently selected drive may not be
;the drive that we want to write to!
	move.b	UnitNum(A_UNIT),d0
	move.w	TDU_CURRTRK(A_UNIT),d1
	move.b	BufferDrive(A_DEVICE),UnitNum(A_UNIT)	;fake drive
	move.b	BufferTrack(A_DEVICE),TDU_CURRTRK+1(A_UNIT)	;fake track
	bsr	SelectDrive
	bsr	SelectSide

;The logic of this routine is complicated by the need to support the write
;optimization scheme (see DISK_Write for details). What we need to do is
;check the WriteMap to determine whether a ReadTrackAndDecode is required
;before finally writing...
	move.l	WriteMap(A_DEVICE),d2
	and.l	#%11111111111,d2	;changes for different drive types!
	cmp.l	#%11111111111,d2	;changes for different drive types!
	beq	.SkipRead
	move.l	DecodedBuffer(A_DEVICE),a0
	bsr	ReadTrackAndDecode
	tst.l	d7
	bne	.End
.SkipRead:
	move.l	d7,WriteMap(A_DEVICE)

	move.l	DecodedBuffer(A_DEVICE),a2
	bsr	EncodeAndWriteTrack
	move.b	d0,UnitNum(A_UNIT)
	move.w	d1,TDU_CURRTRK(A_UNIT)
	bsr	SelectDrive
.End:	movem.l	(sp)+,d0-d2/a0/a2/a6
	rts

SelectSide:
;Select the correct side based on current track. This routine MUST be
;called by certain routines (e.g. Update, Write) because the side select is
;lost after a GiveUnit!
	bclr	#CIAB_DSKSIDE,ciabprb	;set to upper
	btst	#0,TDU_CURRTRK+1(A_UNIT)
	bne	.skip
	bset	#CIAB_DSKSIDE,ciabprb	;set to lower
.skip:	push	d0
	move.l	#2*1000,d0
	bsr	delay
	pop	d0
	rts

;Enter with destination track in d3.b

;Note: This routine checks for valid track numbers -- if either the
;current track number or the destination is invalid, PANIC.

SaveRegs	setrl	d0/d3-d4

SeekNoUpdate:
;Perform a seek but don't call DISK_Update -- used by main routine when
;checking for disk changes.

	movem.l	SaveRegs,-(sp)
	bra	SeekAfterUpdate

MinSeek:
;Only call MinSeek if it is certain that the side select has not been
;changed!
	cmp.b	TDU_CURRTRK+1(A_UNIT),d3
	bne	Seek
	move.w	d0,-(sp)
	move.b	BufferDrive(A_DEVICE),d0
	cmp.b	UnitNum(A_UNIT),d0
	bne	.Seek
	move.w	(sp)+,d0
	rts
.Seek:	move.w	(sp)+,d0

Seek:	movem.l	SaveRegs,-(sp)

	bsr	DISK_Update
SeekAfterUpdate:
	tst.l	d7
	bne	.End

	move.l	TDU_STEPDELAY(A_UNIT),d0
	cmp.b	#160,d3
	bhs	.Error

;Set head
	bclr	#CIAB_DSKSIDE,ciabprb	;set to upper
	btst	#0,d3
	bne	.skip
	bset	#CIAB_DSKSIDE,ciabprb	;set to lower
.skip:
	move.w	TDU_CURRTRK(A_UNIT),d4
	lsr.b	#1,d4
	cmp.b	#80,d4
	bhs	.Error
	move.b	d3,TDU_CURRTRK+1(A_UNIT)
	lsr.b	#1,d3
	bset	#CIAB_DSKDIREC,ciabprb	;set to "out" (lower tracks)
	sub.b	d3,d4
	beq	.SideSelectOnly
	bhi	.StepIn	;Go if DISK_CurrentTrack > Destination
	bclr	#CIAB_DSKDIREC,ciabprb	;set to "in" (higher tracks)
	neg.b	d4
.StepIn:
	bsr	SelectDriveSameMotor
	bset	#CIAB_DSKSTEP,ciabprb
	bclr	#CIAB_DSKSTEP,ciabprb	;step head
	bset	#CIAB_DSKSTEP,ciabprb
	bsr	Deselect
	bsr	delay
	subq.b	#1,d4
	bne	.StepIn
	move.l	TDU_SETTLEDELAY(A_UNIT),d0
	bsr	delay
	bsr	SelectDriveSameMotor
.End:	movem.l	(sp)+,SaveRegs
	rts
.SideSelectOnly:
	move.l	#2*1000,d0
	bsr	delay
	bra	.End
.Error:
;	move.w	#$f00,$dff180
	bra	.Error

;*************************** Delay code ****************************

delay:

;Enter with microseconds in D0.L

	movem.l	d0-d1/a0-a1/a6,-(sp)
	lea	TimerIORequest(A_DEVICE),a1
	move.w	#TR_ADDREQUEST,IO_COMMAND(a1)
	clr.l	TV_SECS+IO_SIZE(a1)
	move.l	d0,TV_MICRO+IO_SIZE(a1)
	move.l	4,a6
	SYS	DoIO

;Clear signal bit (TimeOutWait depends on the signal bit being 'correct').
	lea	TimerIORequest(A_DEVICE),a1
	move.l	MN_REPLYPORT(a1),a0
	move.b	MP_SIGBIT(a0),d0
	moveq	#0,d1
	bset	d0,d1
	moveq	#0,d0
	SYS	SetSignal
	movem.l	(sp)+,d0-d1/a0-a1/a6
	rts

TimeOutWait:

;Wait for signal mask in D0, but include a time-out specified in D1 (usecs).
;Returns wait mask in D0, or 0 if a time-out occured (Z flag will be set).

	movem.l	d1-d4/a0-a2/a6,-(sp)
	move.l	d0,d3
	move.l	4,a6
	lea	TimerIORequest(A_DEVICE),a1
	move.l	a1,a2
	move.w	#TR_ADDREQUEST,IO_COMMAND(a1)
	clr.l	TV_SECS+IO_SIZE(a1)
	move.l	d1,TV_MICRO+IO_SIZE(a1)
	move.l	MN_REPLYPORT(a1),a0
	moveq	#0,d2
	move.b	MP_SIGBIT(a0),d1
	bset	d1,d2
	SYS	SendIO
	move.l	d3,d0
	add.l	d2,d0	;equivilent to OR in this case
	SYS	Wait
	and.l	d3,d0	;exclude timer signal
	move.l	d0,d4
	beq	.TimeOut	;if zero, timer signal was the only signal
	move.l	a2,a1
	SYS	AbortIO
.TimeOut:	move.l	a2,a1
	SYS	WaitIO
	moveq	#0,d0	;value
	move.l	d2,d1	;mask (timer sig)
	SYS	SetSignal	;make SURE the timer signal is clear
	move.l	d4,d0
	movem.l	(sp)+,d1-d4/a0-a2/a6
	rts

;*************************** Interrupt routines ************************

;------------------------------------------
;Register contents upon entering a handler:
;D1 - (INTENAR) AND (INTREQR)
;A0 - $dff000
;A1 - data ptr (device ptr in this case)
;A6 - ExecBase 

;d0, d1, a0, a1, a5, and a6 are scratch.
;-----------------------------------------

;However, the disk.resource autodoc indicates the following arrangement for
;interrupts installed by GetUnit:

;D0/D1/A0/A1 are scratch
;A1 points to IS_DATA (device pointer in this case).
;Make no other assumptions!

SyncInt:	addq.w	#1,SyncCount(a1)
	move.l	SyncSig(a1),d0
	lea	Task(a1),a1
	push	a6
	move.l	4,a6
	SYS	Signal
	pop	a6

;Delay approximately 63us to avoid two sync interrupts
;This could be improved.
	moveq	#1,d0
.L1:	move.b	vhposr+_custom,d1
.L2:	cmp.b	vhposr+_custom,d1
	beq	.L2
	dbra	d0,.L1
	move.w	#INTF_DSKSYNC,intreq+_custom	;clear sync
	rts	

BlockInt:
	push	a6
	move.l	#_custom,a6
	move.w	#INTF_DSKBLK,intreq(a6)

;Test DEVB_Verify flag. If set, initiate a read into the verify buffer.
	bclr	#DEVB_Verify,DEV_FLAGS(a1)
	beq	.NoVerify

	move.w	#INTF_SETCLR+INTF_DSKSYNC,intena(a6)	;enable sync int
	move.l	VerifyBuffer(a1),dskpt(a6)
	move.w	#ADKF_SETCLR+ADKF_WORDSYNC,adkcon(a6)
	move.w	#0,dsklen(a6)
	move.w	#$9761,dsklen(a6)
	move.w	#$9761,dsklen(a6)
	move.w	#$4489,dsksync(a6)	;set magic sync word
;Now the first sync interrupt we get will be known to be valid, because the
;DMA read is fully started.
	bra	.End

.NoVerify:
	move.l	BlockSig(a1),d0
	lea	Task(a1),a1
	move.l	4,a6
	SYS	Signal
.End:	pop	a6
	rts

IndexInt:
	move.w	IndexDskLen(a1),d0
	beq	.End
	move.w	d0,dsklen+_custom
	move.w	d0,dsklen+_custom
	clr.w	IndexDskLen(a1)
.End:	rts

EnableIndex:
	movem.l	d0-d1/a0-a1/a6,-(sp)
	move.l	CIABase(A_DEVICE),a6
	moveq	#16,d0
	SYS	SetICR	;clear interrupt
	move.l	#16+128,d0
	SYS	AbleICR	;enable interrupt
	movem.l	(sp)+,d0-d1/a0-a1/a6
	rts
DisableIndex:
	movem.l	d0-d1/a0-a1/a6,-(sp)
	move.l	CIABase(A_DEVICE),a6
	moveq	#16,d0
	SYS	AbleICR	;disable interrupt
	moveq	#16,d0
	SYS	SetICR	;clear interrupt
	movem.l	(sp)+,d0-d1/a0-a1/a6
	rts

;*********************** End interrupt routines ************************

ClearSigs:
	movem.l	d0-d1/a0-a1/a6,-(sp)
	move.w	#INTF_DSKBLK+INTF_DSKSYNC,intreq+_custom	;clear sync+done
	clr.w	SyncCount(A_DEVICE)
	clr.w	IndexDskLen(A_DEVICE)
	move.l	4,a6
	moveq	#0,d0	;value
	move.l	SyncSig(A_DEVICE),d1
	add.l	BlockSig(A_DEVICE),d1
	SYS	SetSignal
	movem.l	(sp)+,d0-d1/a0-a1/a6
	rts

SetRegs:
;This routine enables the block interrupt, but leaves sync interrupts
;disabled.

	move.l	RawBuffer(A_DEVICE),dskpt(a6)
	move.w	#ADKF_MSBSYNC+ADKF_PRECOMP0+ADKF_PRECOMP1,adkcon(a6)
	move.w	#ADKF_SETCLR+ADKF_FAST+ADKF_WORDSYNC+ADKF_MFMPREC,adkcon(a6)
	move.w	#DMAF_SETCLR+DMAF_MASTER+DMAF_DISK,dmacon(a6)	;enable disk DMA
	bsr	ClearSigs
	move.w	#INTF_SETCLR+INTF_DSKBLK,intena(a6)	;enable block int
	rts

PreComp:
;Enter with $dff000 in A6
	movem.l	d0-d1,-(sp)
	move.w	#ADKF_PRECOMP0+ADKF_PRECOMP1,adkcon(a6)	;no precomp
	move.w	TDU_CURRTRK(A_UNIT),d0
	cmp.w	TDU_COMP01TRACK(A_UNIT),d0
	bls	.End
	move.w	#ADKF_SETCLR+ADKF_PRECOMP0,d1
	cmp.w	TDU_COMP10TRACK(A_UNIT),d0
	bls	.Done
	move.w	#ADKF_SETCLR+ADKF_PRECOMP1,d1
	cmp.w	TDU_COMP11TRACK(A_UNIT),d0
	bls	.Done
	move.w	#ADKF_SETCLR+ADKF_PRECOMP0+ADKF_PRECOMP1,d1
.Done:	move.w	d1,adkcon(a6)
.End:	movem.l	(sp)+,d0-d1
	rts

ScanSync:
;Scan for sync mark
;A2 - pointer to raw data -- updated
;Error code in D7

	bsr	WaitWordSync
	tst.l	d7
	bne	.End

;This routine is trickier than it appears. The trick is that we must NOT
;assume a $4489 at the beginning of our buffer. This phenomenon occurs when
;the DMA starts in the middle of the first sync word. The second sync word
;is thrown away by the hardware. It sounds exotic, but it actually happens
;quite often!

	push	a4
	move.l	RawBuffer(A_DEVICE),a4
	cmp.l	a4,a2
	lea	DISK_RawBufSize(a4),a4	;does not affect flags
	beq	.found	;if start of buffer, don't scan for sync!!!

.Sync:	cmpi.w	#$4489,(a2)+
	beq	.found
	cmp.l	a2,a4
	bhi	.Sync
.Error:	pop	a4
	moveq	#DISK_NoSync,d7
	rts
.found:	cmpi.w	#$4489,(a2)
	bne	.ok
	addq.l	#2,a2
	cmp.l	a2,a4
	bhi	.found
	bra	.Error
.ok:	pop	a4
.End:	rts

StopDMA:	move.w	#0,dsklen(a6)
	move.w	#1<<15,dsklen(a6)
	move.w	#1<<15,dsklen(a6)	;zero-length DMA transfer
	move.w	#INTF_DSKBLK+INTF_DSKSYNC,intena(a6)	;disable ints
	bra	ClearSigs

WaitWordSync:
;Wait for a sync mark or disk block done
;Will return an error in 300ms if nothing happens.

	movem.l	d0-d1,-(sp)
.Wait:	tst.w	SyncCount(A_DEVICE)
	bne	.Sync
	move.l	SyncSig(A_DEVICE),d0
	add.l	BlockSig(A_DEVICE),d0
	move.l	#300*1000,d1	;time-out (300ms)
	bsr	TimeOutWait
	beq	.Error
	and.l	BlockSig(A_DEVICE),d0
	bne	.Done
	bra	.Wait
.Sync:	subq.w	#1,SyncCount(A_DEVICE)
.End:	movem.l	(sp)+,d0-d1
	rts
.Done:	move.w	#$C000,SyncCount(A_DEVICE)	;this is obscure - should change
	bra	.End
.Error:	moveq	#DISK_NoSync,d7
	bra	.End

ReadTrackAndDecodeBuffered:
	push	d0
	move.l	WriteMap(A_DEVICE),d0
	beq	.OK	;no optimized writes to worry about
	and.l	#%11111111111,d0	;changes for different drive types!
	cmp.l	#%11111111111,d0	;changes for different drive types!
	beq	.End
	moveq	#-1,d0
	move.l	d0,WriteMap(A_DEVICE)	;mark sectors as filled
	bra	.ReadTrack

.OK:	move.b	UnitNum(A_UNIT),d0
	cmp.b	BufferDrive(A_DEVICE),d0
	bne	.ReadTrack
	move.w	TDU_CURRTRK(A_UNIT),d0
	cmp.b	BufferTrack(A_DEVICE),d0
	bne	.ReadTrack
.End:	pop	d0
	rts
.ReadTrack:
	pop	d0

ReadTrackAndDecode:

;Input: Buffer ptr in A0
;Returns error code in D7

	move.b	UnitNum(A_UNIT),BufferDrive(A_DEVICE)
	move.b	TDU_CURRTRK+1(A_UNIT),BufferTrack(A_DEVICE)

ReadTrackAndDecodeNoBuffer:
	move.w	d0,-(sp)
	move.b	TDU_RETRYCNT(A_UNIT),d0
.Retry:	moveq	#0,d7
	bsr	ReadTrackAndDecodeNoRetry
	tst.l	d7
	beq	.End
	subq.b	#1,d0
	bpl	.Retry
;Could not recover from error - clear the buffer
	bsr	Clear

.End:	move.w	(sp)+,d0
	rts

ReadTrackAndDecodeNoRetry:
	movem.l	d0-d6/a0-a2,-(sp)

;Initiate a raw track read
;Because we are using WORDSYNC interrupts, this code is VERY VERY tricky!!!!!

	bsr	SetRegs
	move.w	#$0123,dsksync(a6)	;set invalid sync word
;At this point sync interrupts will stop happenning, because $0000 will
;never occur. So, we clear the interrupt and enable it.
	bsr	ClearSigs
	move.w	#INTF_SETCLR+INTF_DSKSYNC,intena(a6)	;enable sync int
	move.w	#0,dsklen(a6)
	move.w	#$9A9E,dsklen(a6)
	move.w	#$9A9E,dsklen(a6)	;read approx. 13,628 bytes
	move.w	#$4489,dsksync(a6)	;set magic sync word

;Now the first sync interrupt we get will be known to be valid, because the
;DMA read is fully started.

;Decode track
	move.l	RawBuffer(A_DEVICE),a2

	moveq	#11-1,d3	;# of sectors
	move.l	#$55555555,d2

;Wait for first sync marks...
	bsr	WaitWordSync
	tst.l	d7
	bne	.End

.SecLoop:
	bsr	ScanSync
	tst.l	d7
	bne	.End

	bsr	DecodeLong	;get header
	subq.l	#8,a2

	and.w	#$ff00,d0	;mask off sector number
	move.l	d0,d1
	cmp.w	#$0a00,d1
	bhi	.HeaderError
	add.w	d1,d1	;convert to sector offset
	lsr.w	#4,d0	;get sector label offset
	lea	SectorLabels(A_DEVICE),a1
	lea	(a1,d0.w),a1

;This code supports the write optimization...
	lsr.w	#4,d0	;sector number
	move.l	WriteMap(A_DEVICE),d4
	btst	d0,d4	;should we avoid reading this sector?
	bne	.EndLoop	;yes, skip to next sector

	move.l	d1,-(sp)
	moveq	#0,d5
	move.l	(a2)+,d0
	eor.l	d0,d5
	move.l	(a2)+,d0
	eor.l	d0,d5
;Decode and checksum sector label
	moveq	#3,d6
.Label:	move.l	16(a2),d4
	eor.l	d4,d5
	and.l	d2,d4
	move.l	(a2)+,d1
	eor.l	d1,d5
	and.l	d2,d1
	add.l	d1,d1
	or.l	d1,d4
	move.l	d4,(a1)+
	dbra	d6,.Label
	and.l	d2,d5
	lea	16(a2),a2	;point at header checksum
	move.l	(sp)+,d1
	bsr	DecodeLong	;header checksum
	cmp.l	d0,d5
	bne	.HeaderError

;Verify track position
	swap	d1
	cmp.b	TDU_CURRTRK+1(A_UNIT),d1
	bne	.WrongTrack
	swap	d1

	bsr	DecodeLong	;data area checksum
	lea	(a0,d1.w),a1	;compute destination

;Decode and checksum data block
	moveq	#127,d6
	moveq	#0,d5
.L1:	move.l	512(a2),d4
	eor.l	d4,d5
	and.l	d2,d4
	move.l	(a2)+,d1
	eor.l	d1,d5
	and.l	d2,d1
	add.l	d1,d1
	or.l	d1,d4
	move.l	d4,(a1)+
	dbra	d6,.L1
	and.l	d2,d5
	lea	512(a2),a2

	cmp.l	d0,d5	;check data area checksum
	bne	.DataError
.EndLoop:	dbra	d3,.SecLoop

.End:	bsr	StopDMA
	movem.l	(sp)+,d0-d6/a0-a2
	rts
.HeaderError:
	moveq	#DISK_BadHeader,d7
	bra	.End
.DataError:
	moveq	#DISK_BadData,d7
	bra	.End
.WrongTrack:
	moveq	#TDERR_SeekError,d7
	move.w	TDU_CURRTRK(A_UNIT),d3
	bsr	SeekZero
	bsr	SelectDrive
	bsr	SeekNoUpdate
	bra	.End

DecodeLong:
;A2 - ptr to buffer -- updated
;D2 - $55555555
;D0 - result

	move.l	d1,-(sp)
	move.l	(a2)+,d0
	move.l	(a2)+,d1
	and.l	d2,d0
	and.l	d2,d1
	add.l	d0,d0	;was lsl.l #1,d0
	or.l	d1,d0
	move.l	(sp)+,d1
	rts

EncodeLong:
;Enter with data to be encoded in D0.L
;and pointer to destination in A0 -- updated
;Exit with checksum in D5

	movem.l	d0-d4,-(sp)
	moveq	#0,d5
	move.l	#$55555555,d4
	move.l	d0,d3
	lsr.l	#1,d0
	bsr	Encode
	move.l	d3,d0
	bsr	Encode
	and.l	#$55555555,d5
	movem.l	(sp)+,d0-d4
	rts

Encode:
;Enter with longword to code in D0.L and #$55555555 in D4
;uses d0,d1,d2,a0 -- not saved

;Accumulates checksum in D5

	and.l	d4,d0
	move.l	d0,d2
	eor.l	d4,d2
	move.l	d2,d1
	add.l	d2,d2
	lsr.l	#1,d1
	bset	#31,d1
	and.l	d2,d1
	or.l	d1,d0
	btst	#0,-1(a0)
	beq	.ok
	bclr	#31,d0
.ok:	eor.l	d0,d5
	move.l	d0,(a0)+
	rts

EncodeBlock:
;Destination is always chip RAM (RawBuffer).
;Source could be in chip RAM or fast RAM (in A2).

	movem.l	d0-d1/a0-a1/a6,-(sp)
	move.l	4,a6
	move.l	a2,a1
	SYS	TypeOfMem
	and.l	#MEMF_CHIP,d0
	bne	.Chip
	movem.l	(sp)+,d0-d1/a0-a1/a6
	bra	EncodeBlockCPU
.Chip:	movem.l	(sp)+,d0-d1/a0-a1/a6
	bra	EncodeBlockBlit

EncodeBlockCPU:
;Enter with pointer to source data in A2 -- updated
;Enter with pointer to destination in A0 -- updated

;Exit with checksum in D5
	move.l	d6,-(sp)
	moveq	#0,d5
	move.w	#(512/4)-1,d6

EncodeBlockSub:
;Number of longwords to encode (minus one) in D6.w
	movem.l	d0-d4,-(sp)

;Encode odd bits
	push	a2
	move.w	d6,d3
	move.l	#$55555555,d4
.L1:	move.l	(a2)+,d0
	lsr.l	#1,d0
	bsr	Encode
	dbra	d3,.L1

;Encode even bits
	pop	a2
	move.w	d6,d3
.L2:	move.l	(a2)+,d0
	bsr	Encode
	dbra	d3,.L2
	and.l	#$55555555,d5
	movem.l	(sp)+,d0-d4
	move.l	(sp)+,d6
	rts

EncodeSectorLabels:
;D5 (checksum) must be initialized by caller
	move.l	d6,-(sp)
	move.w	#(16/4)-1,d6
	bra	EncodeBlockSub

EncodeBlockBlit:
;Enter with pointer to source data in A2 -- updated
;Enter with pointer to destination in A0 -- updated

;Exit with checksum in D5

	movem.l	d0-d2/a0-a1/a6,-(sp)
	push	a0
	move.l	GraphBase(A_DEVICE),a6
	SYS	OwnBlitter
	move.l	(sp),a0
	move.l	#_custom,a1

	move.w	#$808,d0	;BLTSIZE

	SYS	WaitBlit

	move.w	#$ffff,bltafwm(a1)
	move.w	#$ffff,bltalwm(a1)
	clr.w	bltbmod(a1)
	clr.w	bltamod(a1)
	clr.w	bltdmod(a1)
	move.w	#$5555,bltcdat(a1)

	move.l	a2,bltbpt(a1)
	move.l	a2,bltapt(a1)
	move.l	a0,bltdpt(a1)
	move.w	#$1db1,bltcon0(a1)
	clr.w	bltcon1(a1)
	move.w	d0,bltsize(a1)

	SYS	WaitBlit

	move.l	a0,bltbpt(a1)
	move.l	a2,bltapt(a1)
	move.l	a0,bltdpt(a1)
	move.w	#$2d8c,bltcon0(a1)
	move.w	d0,bltsize(a1)
	movem.l	a0/a2,-(sp)
	lea	510(a2),a2	;ptr to end of src
	lea	1022(a0),a0

	SYS	WaitBlit

	move.l	a2,bltbpt(a1)	;src end
	move.l	a2,bltapt(a1)	;src end
	move.l	a0,bltdpt(a1)	;dst end
	move.w	#$0db1,bltcon0(a1)
	move.w	#$1002,bltcon1(a1)	;decrement
	move.w	d0,bltsize(a1)
	movem.l	(sp)+,a0/a2
	lea	512(a0),a0

	SYS	WaitBlit

	move.l	a0,bltbpt(a1)
	move.l	a2,bltapt(a1)
	move.l	a0,bltdpt(a1)
	move.w	#$1d8c,bltcon0(a1)
	clr.w	bltcon1(a1)
	move.w	d0,bltsize(a1)
	pop	a0

	SYS	WaitBlit

	bsr	Correct
	lea	512(a0),a0
	bsr	Correct
	lea	-512(a0),a0

	move.w	#(1024/4)-1,d0
	move.l	#$55555555,d2
	moveq	#0,d5
..	move.l	(a0)+,d1
	eor.l	d1,d5
	dbra	d0,..
	and.l	d2,d5

	SYS	DisownBlitter
	movem.l	(sp)+,d0-d2/a0-a1/a6
	lea	512(a2),a2	;update source pointer
	lea	1024(a0),a0	;update destination pointer
	rts

;This routine corrects the high bit of the current byte based on the
;low bit of the previous byte.

Correct:
	push	d0
	move.b	(a0),d0
	btst	#0,-1(a0)
	bne	.ResetClock
	btst	#6,d0
	bne	.end
	bset	#7,d0
	bra	.end1
.ResetClock:
	bclr	#7,d0
.end1:	move.b	d0,(a0)
.end:	pop	d0
	rts

DISK_Wait:
;Assumes $dff000 in A6.
	movem.l	d0-d1,-(sp)
	tst.w	SyncCount(A_DEVICE)
	bmi	.OK	;if WaitWordSync detected a BlockSig, don't wait!
	move.l	BlockSig(A_DEVICE),d0
	move.l	#300*1000,d1
	bsr	TimeOutWait
	bne	.OK
	moveq	#DISK_NoSync,d7
.OK:	move.w	#INTF_DSKBLK+INTF_DSKSYNC,intena(a6)	;disable ints
	bsr	ClearSigs
	movem.l	(sp)+,d0-d1
	rts

EncodeAndWriteTrack:
;Enter with pointer to source data in A2

	movem.l	d0-d6/a0-a2,-(sp)

	btst	#CIAB_DSKPROT,ciaapra	;check write protect status
	beq	.Protected

	move.l	RawBuffer(A_DEVICE),a0

;Gap = 1660 bytes - 2 bytes for hardware bug
	move.l	#$aaaaaaaa,d1	;10101010...
	move.w	#414,d0
..	move.l	d1,(a0)+
	dbra	d0,..
	subq.l	#2,a0	;leave room for 2 extra bytes at the very end

	moveq	#11,d1	;number of sectors
	moveq	#0,d3	;sector count
.SecLoop:
	move.l	#$aaaaaaaa,(a0)
	bsr	Correct
	addq.l	#4,a0
	move.l	#$44894489,(a0)+
	move.l	#$ff000000,d0
	moveq	#0,d6
	move.w	TDU_CURRTRK(A_UNIT),d6
	swap	d6
	or.l	d6,d0
	move.l	d3,d6
	lsl.l	#8,d6
	or.l	d6,d0
	or.l	d1,d0
	bsr	EncodeLong	;header

;Encode sector label
	push	a2
	lea	SectorLabels(A_DEVICE),a2
	move.l	d3,d0
	lsl.l	#4,d0	;sector*16
	lea	(a2,d0.w),a2
	bsr	EncodeSectorLabels
	pop	a2

	move.l	d5,d0
	bsr	EncodeLong	;header checksum
	move.l	a0,d2	;save raw data pointer
	addq.l	#8,a0
	bsr	EncodeBlock	;encode data block
	move.l	d5,d0
	exg	a0,d2
	bsr	EncodeLong	;data block checksum
	bsr	Correct
	move.l	d2,a0
	addq.l	#1,d3
	subq.l	#1,d1
	bne	.SecLoop

	move.w	#$aaa8,(a0)
	bsr	Correct	;extra word to avoid hardware bug

;Physically write the data
.WriteAgain:
	bsr	SetRegs
	bsr	PreComp
	move.w	#ADKF_WORDSYNC,adkcon(a6)	;turn OFF wordsync!!!
	move.w	#$0123,dsksync(a6)
	bsr	ClearSigs

	btst	#TDPB_VERIFY,TDU_PUBFLAGS(A_UNIT)
	beq	.SkipVerify
	bset	#DEVB_Verify,DEV_FLAGS(A_DEVICE)
.SkipVerify:

	move.l	VerifyBuffer(A_DEVICE),a0
	clr.l	(a0)+
	clr.l	(a0)
	move.w	#0,dsklen(a6)
	move.w	#$DA9E,dsklen(a6)
	move.w	#$DA9E,dsklen(a6)	;write approx. 13,628 bytes

;This piece of code (commented out) tests the function of the rare
;"interrupt delayed" requester. (I've never seen it appear in actual use).
	comment |
;TEST
	push	a6
	move.l	4,a6
	SYS	Disable
	move.w	#5000,d0
.L1:	move.b	vhposr+_custom,d1
.L2:	cmp.b	vhposr+_custom,d1
	beq	.L2
	dbra	d0,.L1
	SYS	Enable
	pop	a6
|

	btst	#TDPB_VERIFY,TDU_PUBFLAGS(A_UNIT)
	beq	.NoVerify

;VERIFY

;We verify by comparing the raw MFM data in RawBuffer (what we just wrote)
;and VerifyBuffer (what is coming in). Due to the ingenious method of
;verifying (thanks to Sebastiano Vigna!) the data comes in sector-by-sector
;in the same order that we wrote it.

	move.l	VerifyBuffer(A_DEVICE),a0
	move.l	RawBuffer(A_DEVICE),a2
	lea	1666(a2),a2	;first sector (minus sync)
	bsr	WaitWordSync
	tst.l	d7
	bne	.VerifyError
	bsr	WaitWordSync
	tst.l	d7
	bne	.VerifyError
;Now we have our first sector in the verify buffer. Scan for a
;sync mark. (There may be 1 or 2 sync marks).
	cmp.w	#$4489,(a0)+
	bne	.VerifyError
	cmp.w	#$4489,(a0)
	bne	.HaveSync
	addq.l	#2,a0
.HaveSync:

;We go through a rather elaborate procedure here to make sure that we've
;started reading with sector 0. (If not, display a requester informing the
;user that something is locking out level-1 interrupts for a long period of
;time).
	push	a2
	move.l	#$55555555,d2
	move.l	a0,a2
	bsr	DecodeLong
	pop	a2
	and.w	#$ff00,d0
	beq	.Sector0
	move.l	a0,a1
	moveq	#9,d0
	moveq	#0,d1
..	move.l	(a1)+,d3
	eor.l	d3,d1
	dbra	d0,..
	and.l	d2,d1
	push	a2
	move.l	a1,a2
	bsr	DecodeLong
	pop	a2
	cmp.l	d0,d1
	bne	.VerifyError

;Display an informational requester.
	bsr	StopDMA
	push	a6
	move.l	IntBase(A_DEVICE),a6
	cmp.w	#36,LIB_VERSION(a6)
	bhi	.KS20

;Running under 1.3. Put up a DisplayAlert.
	moveq	#0,d0	;alert type (recoverable)
	lea	.AlertLockOut(pc),a0
	moveq	#20,d1	;height
	SYS	DisplayAlert
	pop	a6
	bra	.WriteAgain

;Running under 2.0. Put up a EasyRequest.
.KS20:
	sub.l	a0,a0
	sub.l	a2,a2
	lea	.LockOut(pc),a1
	SYS	EasyRequestArgs
	pop	a6
	bra	.WriteAgain

;Note: Probably should compare with the blitter, but this will be fairly
;fast.

.Sector0:
	move.w	#270-1,d0
..	cmp.l	(a0)+,(a2)+
	dbne	d0,..
	bne	.VerifyError

;Now we are over the initial hump of the first sync mark. The rest of the
;compare is even easier.
	moveq	#9-1,d1
.VLoop:	bsr	WaitWordSync
	tst.l	d7
	bne	.VerifyError
	move.w	#272-1,d0
..	cmp.l	(a0)+,(a2)+
	dbne	d0,..
	bne	.VerifyError
	dbra	d1,.VLoop

;We have one more sector to verify. This time we must wait for "Block
;done", rather than another sync.

	bsr	DISK_Wait
	tst.l	d7
	bne	.VerifyError
	move.w	#272-1,d0
..	cmp.l	(a0)+,(a2)+
	dbne	d0,..
	bne	.VerifyError
	bra	.End

.VerifyError:
	moveq	#0,d7	;don't propagate the error to the app
;We go here if an error is detected during the verify. We first shut down
;the read operation that may be in progress, then put up a requester and let
;the user choose whether to retry or abort.

	bsr	StopDMA	;stop!!
	push	a6
	move.l	IntBase(A_DEVICE),a6
	cmp.w	#36,LIB_VERSION(a6)
	bhi	.DoKS20

;Running under 1.3. Put up a DisplayAlert.
	moveq	#0,d0	;alert type (recoverable)
	lea	.AlertVError(pc),a0
	moveq	#20,d1	;height
	SYS	DisplayAlert
;D0 is set to 'TRUE' if the LEFT button was pressed.
	pop	a6
	tst.l	d0
	bne	.WriteAgain	;go if left button pressed
	bra	.End

;Running under 2.0. Put up a EasyRequest.
.DoKS20:	sub.l	a0,a0
	sub.l	a2,a2
	lea	.VError(pc),a1
	SYS	EasyRequestArgs
	pop	a6
	tst.l	d0
	bne	.WriteAgain	;go if left gadget hit
	bra	.End
.NoVerify:
	bsr	DISK_Wait
	move.l	#4*1000,d0
	bsr	delay	;post-write delay
.End:	movem.l	(sp)+,d0-d6/a0-a2
	rts
.Protected:
	moveq	#DISK_WriteProtected,d7
	bra	.End

.AlertVError:
	dc.w	10	;x coordinate
	dc.b	10	;y coordinate
	dc.b	'*** VERIFY ERROR !!! ***  Hit LEFT button to RETRY'
	dc.b	', or RIGHT button to CANCEL.',0
	dc.b	0	;continuation byte
	even

.AlertLockOut:
	dc.w	10	;x coordinate
	dc.b	10	;y coordinate
	dc.b	'Disk block interrupt delayed by >10ms.'
	dc.b	'  Press mouse button.',0
	dc.b	0	;continuation byte
	even

.VError:	dc.l	es_SIZEOF
	dc.l	0
	dc.l	.Title
	dc.l	.MainText
	dc.l	.GadgetText
.LockOut:	dc.l	es_SIZEOF
	dc.l	0
	dc.l	.Title
	dc.l	.LockOutTxt
	dc.l	.Okay
.LockOutTxt:
	dc.b	'Disk block interrupt delayed by >10ms caused erroneous'
	dc.b	' verify.',0
.Okay:	dc.b	'If you say so. Try it again!',0
.Title:	dc.b	'hackdisk.device message',0
.MainText:
	dc.b	'*** VERIFY ERROR!!! ***',0
.GadgetText:
	dc.b	'Retry|Cancel',0
	even

DISK_Write:

;Error code returned in D7, as always.

	movem.l	d0-d5/a0-a2,-(sp)

	btst	#UNITB_WriteProtected,UNIT_FLAGS(A_UNIT)
	bne	.ProtError

	move.l	d0,d5	;length

;The meat of the write routine...

.WriteLoop:
	move.l	d1,d3	;offset
	divu.w	d6,d3	;d3.w is track #
	move.l	d3,d4
	clr.w	d4
	swap	d4	;d4.l is byte offset into track
	bsr	MinSeek
	tst.l	d4	;any offset?
	bne	.Complex	;yes
	cmp.l	d6,d5	;at least a track left?
	blo	.Complex	;no
	move.l	a0,d0
	btst	#0,d0	;word aligned?
	bne	.Complex	;no, do it the long way
	move.l	a0,a2
	bsr	EncodeAndWriteTrack
	tst.l	d7
	bne	.End
	sub.l	d6,d5	;update length
	beq	.End
	add.l	d6,a0	;update source pointer
	add.l	d6,d1	;update offset
	bra	.WriteLoop

.Complex:

;This part is somewhat difficult. We check the offset and length parameters
;to see whether they're a multiple of 512. If so, we keep track of which
;sectors will be written in the buffer. This information is later used by
;Update to determine whether a part of the original track must be read in.
;(We don't attempt this optimization if the user is writing some odd number
;of bytes...This is probably why trackdisk has the limits that it does).

	tst.l	d5
	beq	.End	;nothing left, forget it
	move.l	d4,d0
	and.w	#%111111111,d0
	bne	.NoOpt
	move.l	d4,d0
	move.l	d5,d2
	and.w	#%111111111,d2
	bne	.NoOpt
	move.l	d5,d2
	lsr.l	#8,d0
	lsr.l	#1,d0	;get starting sector number
	lsr.l	#8,d2
	lsr.l	#1,d2	;get length in sectors
	move.l	WriteMap(A_DEVICE),d7
.OptLoop:	bset	d0,d7
	addq.b	#1,d0
	cmp.b	#32,d0
	beq	.EOpt
	subq.l	#1,d2
	bne	.OptLoop
.EOpt:	move.l	d7,WriteMap(A_DEVICE)
	moveq	#0,d7

;This is normally done by ReadTrackAndDecode.
	move.b	UnitNum(A_UNIT),BufferDrive(A_DEVICE)
	move.b	TDU_CURRTRK+1(A_UNIT),BufferTrack(A_DEVICE)
	bra	.Opt	;don't read (yet)

.NoOpt:	push	a0
	move.l	DecodedBuffer(A_DEVICE),a0
	bsr	ReadTrackAndDecodeBuffered
	pop	a0
	tst.l	d7
	bne	.End
.Opt:	move.l	DecodedBuffer(A_DEVICE),a1
	add.l	d4,a1	;add byte offset into track
	sub.l	d6,d4
	neg.l	d4
	add.l	d4,d1	;update offset

;D4.L - number of bytes that could be transferred from this track
;D5.L - number of bytes left in the entire Read request

	cmp.l	d5,d4
	bhs	.FinalWrite
	sub.l	d4,d5	;update length
	move.l	d4,d0
	bsr	CopyMem
	add.l	d0,a0	;update dest pointer
	bset	#DEVB_Dirty,DEV_FLAGS(A_DEVICE)
	bra	.WriteLoop

.FinalWrite:
	move.l	d5,d0
	bsr	CopyMem
	bset	#DEVB_Dirty,DEV_FLAGS(A_DEVICE)
.End:	movem.l	(sp)+,d0-d5/a0-a2
	rts
.ProtError:
	moveq	#DISK_WriteProtected,d7
	bra	.End

MotorOff:
;MotorOff turns off all drive motors and leaves all drives deselected
	bsr	Deselect
	bset	#CIAB_DSKMOTOR,ciabprb	;motor off
	and.b	#~(CIAF_DSKSEL0+CIAF_DSKSEL1+CIAF_DSKSEL2+CIAF_DSKSEL3),ciabprb	;select all drives
	bsr	Deselect
	clr.b	MotorState(A_DEVICE)
	rts

Inquire:
	IFND	_LVOGetUnitID
_LVOGetUnitID	equ	-30
	ENDC
	movem.l	d0-d2/a0-a1/a6,-(sp)
	moveq	#0,d2
	move.l	DiskResourceBase(A_DEVICE),a6
.Loop:	move.l	d2,d0
	SYS	GetUnitID
	tst.l	d0
	bne	.Next
	bset	d2,InquireBits(A_DEVICE)
.Next:	addq.l	#1,d2
	cmp.w	#MD_NUMUNITS,d2
	blo	.Loop
	movem.l	(sp)+,d0-d2/a0-a1/a6
	rts

	comment |
;Find out what drives are in the system
;Exit with "drive map" in D0 (bit 0 = drive 0, bit 1 = drive 1, etc.)

;See the Hardware Reference Manual, Appendix E for a description of
;the procedure.

;Note: Drive zero is always present. In fact, the identification 
;scheme does not work with drive zero (except for the half-speed drive).

Inquire:
	movem.l	d1-d5/a0,-(sp)
	moveq	#4,d3
	moveq	#1,d2	;drive count
	moveq	#1,d0	;drive map (drive zero always present)
	moveq	#7,d1
	move.l	#ciabprb,a0

	bsr	Deselect

.InquireLoop:
	moveq	#31,d4
	moveq	#0,d5	;identification longword

	bclr	d1,(a0)	;motor on
	bset	d3,(a0)	;deselect drive
	bclr	d3,(a0)	;select drive

	bset	d1,(a0)	;motor off
	bset	d3,(a0)	;deselect drive
	bclr	d3,(a0)	;select drive
	bset	d3,(a0)	;deselect drive

.ReadIdent:
	bclr	d3,(a0)	;select drive
	btst	#5,ciaapra	;test ready
	beq	.zero
	bset	d4,d5
.zero:	bset	d3,(a0)	;deselect drive
	dbra	d4,.ReadIdent

	tst.l	d5
	bne	.Skip
	bset	d2,d0
.Skip:	addq.l	#1,d3
	addq.l	#1,d2
	cmp.b	#4,d2
	bne	.InquireLoop
	movem.l	(sp)+,d1-d5/a0
	rts
|

SeekZeroAll:
	movem.l	d0-d2/A_UNIT,-(sp)
	move.b	InquireBits(A_DEVICE),d0
	lea	Unit0(A_DEVICE),A_UNIT
	moveq	#0,d1
	moveq	#MD_NUMUNITS-1,d2
.Loop:	btst	d1,d0
	beq	.Next
	bsr	SeekZero
.Next:	lea	MyUnit_Sizeof(A_UNIT),A_UNIT
	addq.b	#1,d1
	dbra	d2,.Loop
	movem.l	(sp)+,d0-d2/A_UNIT
	rts

SeekZero:
;Places the drive in UnitNum(A_UNIT) on track zero.
;Drive does not need to be selected in advance.
;Drive will be left _deselected_!

	movem.l	d0-d1,-(sp)

.StepLoop:
	bsr	SelectDriveSameMotor
	bset	#CIAB_DSKDIREC,ciabprb	;set to "out" (lower tracks)
	btst	#CIAB_DSKTRACK0,ciaapra	;check track zero flag
	beq	.EndStepLoop
	bset	#CIAB_DSKSTEP,ciabprb
	bclr	#CIAB_DSKSTEP,ciabprb	;step head
	bset	#CIAB_DSKSTEP,ciabprb
	bsr	Deselect
	move.l	TDU_CALIBRATEDELAY(A_UNIT),d0
	bsr	delay
	bra	.StepLoop

.EndStepLoop:
	bsr	Deselect
	move.l	TDU_SETTLEDELAY(A_UNIT),d0
	bsr	delay
	clr.w	TDU_CURRTRK(A_UNIT)
	movem.l	(sp)+,d0-d1
	rts

CopyMemSlow:
;Only to be called by CopyMem
	move.l	4,a6
	SYS	CopyMem
	movem.l	(sp)+,d0-d7/a0-a6
	rts

CopyMem:

;A0 - source
;A1 - destination
;D0 - size

	movem.l	d0-d7/a0-a6,-(sp)

	move.l	a0,d1
	btst	#0,d1
	bne	CopyMemSlow
	move.l	a1,d1
	btst	#0,d1
	bne	CopyMemSlow


.More:	cmp.l	#512,d0
	blo	CopyMemSlow

;Copy 480 bytes
n	set	0
	REPT	10
	movem.l	(a0)+,d1-d7/a2-a6
	movem.l	d1-d7/a2-a6,n*48(a1)
n	set	n+1
	ENDR
;Copy 32 bytes
	movem.l	(a0)+,d1-d7/a2
	movem.l	d1-d7/a2,480(a1)
	lea	512(a1),a1
	sub.l	#512,d0
	bne	.More
	movem.l	(sp)+,d0-d7/a0-a6
	rts

;*************************** Debugging stuff *************************

	IFNE	INFO_LEVEL

KPutFmt:	move.l	a2,-(sp)
	lea	KPutChar(pc),a2
	bsr	KDoFmt
	move.l	(sp)+,a2
	rts

KDoFmt:	move.l	a6,-(sp)
	move.l	4,a6
	SYS	RawDoFmt
	move.l	(sp)+,a6
	rts

KPutChar:

;Serial
	comment |
	move.l	a6,-(sp)
	move.l	4,a6
	SYS	RawPutChar
	move.l	(sp)+,a6
	rts
|

;Printer
	comment |
	move.b	#$ff,$bfe301
.Print:	btst	#0,$bfd000
	bne	.Print
	move.b	d0,$bfe101
	rts
|

;Memory
	tst.l	MemPtr
	bne	.OK
	move.l	#$500000,MemPtr
.OK:
	push	a0
	move.l	MemPtr(pc),a0
	move.b	d0,(a0)+
	move.l	a0,MemPtr
	pop	a0
	rts

MemPtr:	dc.l	0

	ENDC

EndCode:
