; FILE: Source:hackdisk.ASM          REV: 8 --- hackdisk.device

********************************************************************************
* hackdisk.ASM -- my very own version of trackdisk.device
* hackdisk.device - Copyright © 1992,93 by Dan Babcock,
* hackdisk.device - Copyright © 1996-1997 by Harry Sintonen.
*
* 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.
* V2.00 12/26/92 Supports Chinon/Commodore FB357A 150RPM HD floppy drives.
*                Support added for 5.25 inch drives (NOT TESTED!!!)
*                Added track number to verify error display (only under 2.0+).
*                Added a tiny module at the end (see the comments for details).
*                Released on Fish 803.
* V2.01 01/22/93 Bug fix: write protection detected (broke in 2.00)
* V2.02 03/13/93 Now performs an AllocUnit on all units in the initroutine
*
* V2.03 11/06/96 Fixed one bug and turned code to compilable with DevPac 3.
* V2.04 03/25/97 Aminet release.
********************************************************************************

;This source was assembled with Devpac 3.

DEVPAC	SET	1
NUMTRACKS	EQU	80
SECPERTRACK	EQU	11

RETRYCNT	EQU	3


	IFD	DEVPAC
	incdir	"include:"
	include	"exec/types.i"
	include	"exec/exec.i"
	include	"hardware/custom.i"
	include	"hardware/cia.i"
	include	"hardware/dmabits.i"
	include	"hardware/intbits.i"
	include	"hardware/adkbits.i"
	include	"devices/timer.i"
	include	"devices/trackdisk.i"
	include	"resources/disk.i"

;;	include	"intuition/intuition.i"
	IFND	es_SIZEOF
es_SIZEOF	EQU	20
	ENDC

	include	"exec/exec_lib.i"
	include	"libraries/graphics_lib.i"
	include	"libraries/intuition_lib.i"
	IFND	_LVOSubTime
_LVOSubTime	EQU	-$30
_LVOGetSysTime	EQU	-$42
	ENDC
	IFND	_LVOAbleICR
_LVOAbleICR	EQU	-$12
_LVOSetICR	EQU	-$18
	ENDC
	IFND	IOERR_NOCMD
IOERR_NOCMD	EQU	-3
	ENDC
push	MACRO	* ea
	move.l	\1,-(sp)
	ENDM
pop	MACRO	* ea
	move.l	(sp)+,\1
	ENDM

	ELSE

	exeobj
	objfile	'devs:hackdisk.device'
	MC68000
	multipass
	ENDC

;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

;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	0
MYPRI	EQU	0

MD_NUMUNITS	EQU	4
STACKSIZE	EQU	4000
TASKPRI	EQU	5

;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
	BYTE	UnitNum
	BYTE	unit_pad1

;The following will be InitStruct'ed in GetDriveType
	LABEL	DriveParams
	BYTE	DriveType
	BYTE	NumTracks
	LONG	BytesPerDisk
	LONG	SectorMask
	WORD	GapCount
	LONG	SectorsPerTrack
	LONG	RawBufSize
	WORD	WriteDskLen
	WORD	ReadDskLen
	WORD	VerifyDskLen
	WORD	MaxValidSec
	WORD	FirstSector
	LABEL	EndDriveParams
	LABEL	MyUnit_Sizeof

DriveParams_Sizeof	EQU	EndDriveParams-DriveParams
	IFGT	DriveParams_Sizeof&1
	FAIL	DriveParams_Sizeof MUST be word-aligned!!!
	ENDC

;*
;* 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,SECPERTRACK*2*16	22*16 !??
	STRUCT	Unit0,MyUnit_Sizeof
	STRUCT	Unit1,MyUnit_Sizeof
	STRUCT	Unit2,MyUnit_Sizeof
	STRUCT	Unit3,MyUnit_Sizeof
	LONG	GraphBase
	LONG	Int_Base		;hs
	LONG	DiskResourceBase
	LONG	CIABase
	WORD	SyncCount
	WORD	IndexDskLen
	WORD	BlockIntDskLen
	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

;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

Name:	dc.b	'trackdisk.device',0
	even

IDString:	dc.b	'Hackdisk 2.04 - Copyright © 1996-1997 by Harry Sintonen,',10
	dc.b	'Original Hackdisk Copyright © 1992,93 by Dan Babcock',10,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
	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	TDGetDriveType-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

;Try to allocate all the units.
	move.l	d0,a6
	moveq	#0,d2
.AllocDisk:
	move.l	d2,d0
	IFND	_LVOAllocUnit
_LVOAllocUnit	EQU	-6
	ENDC
	SYS	AllocUnit
	addq.l	#1,d2
	cmp.w	#4,d2
	blo	.AllocDisk
	move.l	4,a6

	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
;hs	lea	IntName(pc),a1
;	SYS	OldOpenLibrary
;	move.l	d0,IntBase(A_DEVICE)
;	beq	.Failed

;Allocate buffer memory.
	move.l	#DISK_RawBufSize*2,d0
	moveq	#MEMF_CHIP,d1
	SYS	AllocMem
	move.l	d0,RawBuffer(A_DEVICE)
	beq	.Failed

	move.l	#512*SECPERTRACK*2,d0	22,d0	!??
	moveq	#MEMF_CHIP,d1
	SYS	AllocMem
	move.l	d0,DecodedBuffer(A_DEVICE)
	beq	.Failed

	move.l	#(1088*SECPERTRACK*2)+2,d0	!??
	moveq	#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:
	move.b	d2,UnitNum(A_UNIT)
	lea	ChangeIntList(A_UNIT),a0
	NEWLIST	a0
	bset	#1,TDU_PUBFLAGS(A_UNIT)	;verify ON!
	move.b	#RETRYCNT,TDU_RETRYCNT(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

	IFGT	0
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:
;hs	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*SECPERTRACK,d0
	SYS	FreeMem
.SkipDecoded:
	move.l	VerifyBuffer(A_DEVICE),d0
	beq	.SkipVerify
	move.l	d0,a1
	move.l	#(1088*SECPERTRACK)+2,d0
	SYS	FreeMem
.SkipVerify:
.End:	movem.l	(sp)+,d0-d1/a0-a1/a6
	rts

	ENDC


;******************************* 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

;Get drive types
	moveq	#MD_NUMUNITS-1,d0
	lea	Unit0(A_DEVICE),A_UNIT
.TypeLoop:
	bsr	GetDriveType
	lea	MyUnit_Sizeof(A_UNIT),A_UNIT
	dbra	d0,.TypeLoop

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

	bra	.NextMessage

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

.MainLoop:
	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	GetDriveType	;reread drive type (leaves drive deselected)
;	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

;******************** 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'>
	moveq	#0,d0
	rts

Expunge:

;A6 - device ptr

;Must return either 0 or SegList ptr in D0.

	moveq	#0,d0
	rts

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.

	IFND	DEVPAC
SaveRegs	setrl	d0-d3/d6-d7/a0-a2/a6
	ENDC
Read:
	PUTDEBUG	<'Read: Called'>
	IFD	DEVPAC
	movem.l	d0-d3/d6-d7/a0-a2/a6,-(sp)
	ELSE
	movem.l	SaveRegs,-(sp)
	ENDC
	bsr	RWSetup
	tst.l	d7
	bne	FinishRW

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

	bsr	DISK_Read
	PUTDEBUG	<'Read: Done'>
	bra	FinishRW
Format:
Write:
	IFD	DEVPAC
	movem.l	d0-d3/d6-d7/a0-a2/a6,-(sp)
	ELSE
	movem.l	SaveRegs,-(sp)
	ENDC
	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)
	bne	.Skip
	move.l	IO_LENGTH(A_IO),IO_ACTUAL(A_IO)
.Skip:
	IFD	DEVPAC
	movem.l	(sp)+,d0-d3/d6-d7/a0-a2/a6
	ELSE
	movem.l	(sp)+,SaveRegs
	ENDC
	rts
RWSetup:
	move.l	SectorsPerTrack(A_UNIT),d6
	lsl.l	#8,d6
	add.l	d6,d6	;*512
	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	BytesPerDisk(A_UNIT),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*SECPERTRACK*2)/4)-1,d3	22 !??
.il	move.l	(a1)+,(a2)+	;copy all sector labels
	dbra	d3,.il
	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

TDGetDriveType:
;Returns type of drive in IO_ACTUAL
;* EXECUTED IMMEDIATELY, POSSIBILY WITHIN AN INTERRUPT *
	clr.l	IO_ACTUAL(A_IO)
	move.b	DriveType(A_UNIT),IO_ACTUAL+3(A_IO)
	rts

GetNumTracks:
;Returns number of tracks (note: not cylinders) in IO_ACTUAL
;* EXECUTED IMMEDIATELY, POSSIBILY WITHIN AN INTERRUPT *

	clr.l	IO_ACTUAL(A_IO)
	move.b	NumTracks(A_UNIT),IO_ACTUAL+3(A_IO)
	rts

GetGeometry:
;Fills in DriveGeometry structure pointed to by IO_DATA
	movem.l	d0-d1/a0,-(sp)
	move.l	IO_DATA(A_IO),a0
	move.l	#512,dg_SectorSize(a0)	;in bytes
	move.l	#2,dg_Heads(a0)	;number of surfaces
	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)
	move.l	SectorsPerTrack(A_UNIT),d0
	move.l	d0,dg_TrackSectors(a0)	;number of sectors/track
	add.l	d0,d0
	move.l	d0,dg_CylSectors(a0)	;number of sectors/cylinder
	moveq	#0,d1
	move.b	NumTracks(A_UNIT),d1
	lsr.l	#1,d1	;convert tracks -> cylinders
	move.l	d1,dg_Cylinders(a0)	;number of cylinders
	mulu.w	d0,d1
	move.l	d1,dg_TotalSectors(a0)	;total # of sectors on drive
	movem.l	(sp)+,d0-d1/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:	movem.l	d0/d3,-(sp)
	bsr	SelectDriveSameMotor
	move.l	IO_OFFSET(A_IO),d3
	cmp.l	BytesPerDisk(A_UNIT),d3
	bhs	.Error
	move.l	SectorsPerTrack(A_UNIT),d0
	lsl.l	#8,d0
	add.l	d0,d0	;*512 = bytes per track
	divu.w	d0,d3
	bsr	Seek
.End:	movem.l	(sp)+,d0/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.b	NumTracks(A_UNIT),d3
	bhs	.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*2,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.
	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:	movem.l	(sp)+,d0-d1/a0-a2/a6
	rts

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

	movem.l	d0-d1/a0-a1/a6,-(sp)
	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
.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	SectorMask(A_UNIT),d2
	cmp.l	SectorMask(A_UNIT),d2
	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.

	IFND	DEVPAC
SaveRegs	setrl	d0/d3-d4
	ENDC

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

	IFD	DEVPAC
	movem.l	d0/d3-d4,-(sp)
	ELSE
	movem.l	SaveRegs,-(sp)
	ENDC
	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:
	IFD	DEVPAC
	movem.l	d0/d3-d4,-(sp)
	ELSE
	movem.l	SaveRegs,-(sp)
	ENDC

	bsr	DISK_Update
SeekAfterUpdate:
	tst.l	d7
	bne	.End

	move.l	TDU_STEPDELAY(A_UNIT),d0
	cmp.b	NumTracks(A_UNIT),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	#NUMTRACKS,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:
	IFD	DEVPAC
	movem.l	(sp)+,d0/d3-d4
	ELSE
	movem.l	(sp)+,SaveRegs
	ENDC
	rts
.SideSelectOnly:
	move.l	#2*1000,d0
	bsr	delay
	bra	.End
.Error:
;	move.w	#$f00,$dff180
	bra	.End

;*************************** 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	BlockIntDskLen(a1),dsklen(a6)
	move.w	BlockIntDskLen(a1),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
	or.l	BlockSig(A_DEVICE),d1	;hs (add)
	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	a0
	move.l	RawBuffer(A_DEVICE),a0
	cmp.l	a0,a2
	add.l	RawBufSize(A_UNIT),a0
	beq	.found	;if start of buffer, don't scan for sync!!!

.Sync:	cmpi.w	#$4489,(a2)+
	beq	.found
	cmp.l	a2,a0
	bhi	.Sync
.Error:	pop	a0
	moveq	#DISK_NoSync,d7
	rts
.found:	cmpi.w	#$4489,(a2)
	bne	.ok
	addq.l	#2,a2
	cmp.l	a2,a0
	bhi	.found
	bra	.Error
.ok:	pop	a0
.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
	or.l	BlockSig(A_DEVICE),d0	;hs (add)
	move.l	#300*1000*2,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	SectorMask(A_UNIT),d0
	cmp.l	SectorMask(A_UNIT),d0
	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 $0123 will
;(hopefully!) 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	ReadDskLen(A_UNIT),dsklen(a6)
	move.w	ReadDskLen(A_UNIT),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.

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

	move.l	SectorsPerTrack(A_UNIT),d3
	subq.l	#1,d3
	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	MaxValidSec(A_UNIT),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*2,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	GapCount(A_UNIT),d0	;414/829
..	move.l	d1,(a0)+
	dbra	d0,..
	subq.l	#2,a0	;leave room for 2 extra bytes at the very end

	move.l	SectorsPerTrack(A_UNIT),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
	move.w	VerifyDskLen(A_UNIT),BlockIntDskLen(A_DEVICE)

	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	WriteDskLen(A_UNIT),dsklen(a6)
	move.w	WriteDskLen(A_UNIT),dsklen(a6)

;This piece of code (commented out) tests the function of the rare
;"interrupt delayed" requester. (I've never seen it appear in actual use).
	IFGT	0
;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
	ENDC

	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
	add.w	FirstSector(A_UNIT),a2	;1666/3326 - 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
.il2	move.l	(a1)+,d3
	eor.l	d3,d1
	dbra	d0,.il2
	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
	lea	IntName(pc),a1		;hs
	SYS	OldOpenLibrary
	tst.l	d0
	beq.s	.WriteAgain2
	move.l	d0,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
.WriteAgain3	move.l	a6,a1
	SYS	CloseLibrary
.WriteAgain2	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
	bra.s	.WriteAgain3		;/hs


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

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

;Now we are over the initial hump of the first sync mark. The rest of the
;compare is even easier.
	move.l	SectorsPerTrack(A_UNIT),d1
	subq.l	#3,d1
.VLoop:	bsr	WaitWordSync
	tst.l	d7
	bne	.VerifyError
	move.w	#272-1,d0
.il4	cmp.l	(a0)+,(a2)+
	dbne	d0,.il4
	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
.il5	cmp.l	(a0)+,(a2)+
	dbne	d0,.il5
	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
	lea	IntName(pc),a1		;hs
	SYS	OldOpenLibrary
	tst.l	d0
	beq	.WriteAgain2
	move.l	d0,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.
.waend	move.l	a6,a1
	SYS	CloseLibrary
	pop	a6
	tst.l	d0
	bne	.WriteAgain	;go if left (button/gadget) pressed
	bra	.End

;Running under 2.0. Put up a EasyRequest.
.DoKS20:	sub.l	a0,a0
	sub.l	a2,a2
	lea	.VError(pc),a1
	push	a3
	moveq	#0,d0
	move.w	TDU_CURRTRK(A_UNIT),d0
	move.l	d0,-(sp)
	move.l	sp,a3
	SYS	EasyRequestArgs
	addq.l	#4,sp
	pop	a3
	bra.s	.waend
.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!!! ***',$a,'Track %ld',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
	moveq	#-1,d1
	cmp.l	d0,d1
	beq	.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

GetDriveType:

;Determines type of drive and places the proper code into DriveType(A_UNIT).
;One of: DRIVE3_5, DRIVE5_25, DRIVE3_5_150RPM

;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).

;Warning! Leaves drive deselected!

	movem.l	d0-d1/d3-d5/a0-a2/a6,-(sp)
	move.b	UnitNum(A_UNIT),d3
	addq.b	#3,d3
	moveq	#7,d1
	move.l	#ciabprb,a0
	bsr	Deselect
	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

;D5 contains the drive ID.
	lea	DriveParams(A_UNIT),a0
	move.w	#(DriveParams_Sizeof/2)-1,d0
..	clr.w	(a0)+
	dbra	d0,..
	lea	HDParams(pc),a1
	cmp.l	#DRT_150RPM,d5
	beq	.DoInitStruct
	lea	LDParams(pc),a1
;	cmp.l	#DRT_AMIGA,d5
	tst.l	d5
	beq	.DoInitStruct
	moveq	#-1,d0
	cmp.l	d0,d5
	beq	.DoInitStruct
	lea	OldParams(pc),a1	;5.25 inch drive
.DoInitStruct:
	moveq	#0,d0	;area to clear
	move.l	A_UNIT,a2
	move.l	4,a6
	SYS	InitStruct
.End:	movem.l	(sp)+,d0-d1/d3-d5/a0-a2/a6
	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

LDParams:
	INITWORD	TDU_COMP10TRACK,-1
	INITWORD	TDU_COMP11TRACK,-1
	INITWORD	TDU_COMP01TRACK,NUMTRACKS
	INITLONG	TDU_STEPDELAY,3*1000
	INITLONG	TDU_SETTLEDELAY,15*1000
	INITLONG	TDU_CALIBRATEDELAY,4*1000

	INITBYTE	NumTracks,NUMTRACKS*2
	INITBYTE	DriveType,DRIVE3_5
	INITLONG	BytesPerDisk,2*SECPERTRACK*512*NUMTRACKS
	INITLONG	SectorMask,%11111111111
	INITWORD	GapCount,414
	INITLONG	SectorsPerTrack,SECPERTRACK
	INITLONG	RawBufSize,13630

	INITWORD	FirstSector,1666
	INITWORD	MaxValidSec,$0a00
	INITWORD	WriteDskLen,$da9e
	INITWORD	ReadDskLen,$9a9e
	INITWORD	VerifyDskLen,$9761
	dc.w	0

OldParams:	;for 5.25 inch drives
	INITWORD	TDU_COMP10TRACK,-1
	INITWORD	TDU_COMP11TRACK,-1
	INITWORD	TDU_COMP01TRACK,NUMTRACKS/2
	INITLONG	TDU_STEPDELAY,3*1000*2
	INITLONG	TDU_SETTLEDELAY,15*1000*2
	INITLONG	TDU_CALIBRATEDELAY,4*1000*2

	INITBYTE	NumTracks,NUMTRACKS
	INITBYTE	DriveType,DRIVE5_25
	INITLONG	BytesPerDisk,2*SECPERTRACK*512*NUMTRACKS/2
	INITLONG	SectorMask,%11111111111
	INITWORD	GapCount,414
	INITLONG	SectorsPerTrack,SECPERTRACK
	INITLONG	RawBufSize,13630

	INITWORD	FirstSector,1666
	INITWORD	MaxValidSec,$0a00
	INITWORD	WriteDskLen,$da9e
	INITWORD	ReadDskLen,$9a9e
	INITWORD	VerifyDskLen,$9761
	dc.w	0

HDParams:
	INITWORD	TDU_COMP10TRACK,-1
	INITWORD	TDU_COMP11TRACK,-1
	INITWORD	TDU_COMP01TRACK,NUMTRACKS
	INITLONG	TDU_STEPDELAY,3*1000
	INITLONG	TDU_SETTLEDELAY,15*1000
	INITLONG	TDU_CALIBRATEDELAY,4*1000

	INITBYTE	NumTracks,NUMTRACKS*2
	INITBYTE	DriveType,DRIVE3_5_150RPM
	INITLONG	BytesPerDisk,2*SECPERTRACK*512*NUMTRACKS*2
	INITLONG	SectorMask,%1111111111111111111111
	INITWORD	GapCount,829
	INITLONG	SectorsPerTrack,SECPERTRACK*2
	INITLONG	RawBufSize,13630*2

	INITWORD	FirstSector,3326
	INITWORD	MaxValidSec,$1500
	INITWORD	WriteDskLen,$f53c
	INITWORD	ReadDskLen,$b53b
	INITWORD	VerifyDskLen,$aec1
	dc.w	0

ENDCode:

;************************* Northgate fix *********************************

;This tiny module performs a keyboard handshake. Its purpose is to wake up a
;Northgate keyboard on accelerated systems. If you don't fit that description
;then just ignore it. It's harmless.

NRomTag:	dc.w	RTC_MATCHWORD	;$4AFC ('illegal' opcode)
	dc.l	NRomTag
	dc.l	NENDCode	;pointer to end of code
	dc.b	RTF_COLDSTART
	dc.b	0	;version
	dc.b	NT_LIBRARY	;module type (either device or library)
	dc.b	0	;priority
	dc.l	FixName	;name
	dc.l	FixName	;IDString
	dc.l	Northgate	;init routine
FixName:	dc.b	'Northgate_fix',$a,0
	even

Northgate:
;Perform a keyboard handshake
	or.b	#$40,$BFEE01
	clr.b	$bfec01
	move.l	#$bfe001,a0
	move.w	#$46*3,d0
..	move.b	(a0),(a0)
	dbra	d0,..
	and.b	#$BF,$BFEE01
	rts

NENDCode:

;*************************** 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
	IFGT	0
	move.l	a6,-(sp)
	move.l	4,a6
	SYS	RawPutChar
	move.l	(sp)+,a6
	rts
	ENDC

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

;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
