{  File:  NewFloppyTask.asm
Last edited by:

JMM   7-Oct-85 13:14:16  Added Mitch's patches and restored FloppyDefs name.
Last change by MPL 24-Sep-85 13:48:05 timeouts in WaitFDC
	       Changes located around lines beginning with  ## Mitch ##
	       and ending with lines ## EndMitch ##
Last change by Mitch Lichtenberg  8-Jan-84 23:58:02 no more 58x MP errors!
Last change by Dennis Grundler 25-May-83 16:50:35: remove dependency on BC register pair
Last change by Jim Frandeen July 27, 1982  10:24 AM: new IO Page format
Last change by Jim Frandeen May 24, 1982  10:29 AM: Simplify resource control and task management, change format of Dma Control Block
- Have Disk pointers wrap around if ends of buffer will extend over end of ring buffer area.  (May 29, 1981  2:14 PM)
- Use DisableRST and EnableRST subs  (February 24, 1981  12:31 PM)
- Renamed to FloppyTask  (February 4, 1981  3:50 PM)
- use interrupts in an attempt to transfer runs of sectors at full speed (January 23, 1981  11:01 AM)
- Consolidate Read and Write transfers into one main routine - Added Format track (January 8, 1981  3:27 PM) (December 30, 1980  4:24 PM)
- Interleave sectors during sector command  (December 28, 1980  6:27 PM)
- 10 Step-Ins moved from Initialize command to Recalibrate command (December 26, 1980  5:17 PM)
- WaitFDCCompletion changed back to test Interrupt bit in IntReq register.  
- reset the virtual IOCB address in the CSB as a completion signal instead of clearing the valid flag.  The valid flag is being discarded because of atomicity problems. (December 20, 1980  2:50 PM)
- test busy flag in 1797 directly in "WaitFDCCompletion", code seems to stall  waiting for the interrupt even though busy has gone false (December 19, 1980 7:35 PM)
- attempt to get rid of Data Lost on Writes by starting CP DMA transfers before disk DMA transfers (December 19, 1980  6:14 PM)
- All Yields except Block made null (***debug) (October 12, 1980  3:26 PM)

Written by Roy OgusAugust 26, 1980  1:22 P.M.
}

;  DEFINITION FILES:

	get "SysDefs"		; System defs
	get "CommonDefs"		; Common defs
	get "FloppyDefs"	; Floppy defs


;  IMPORTS/EXPORTS:

	IMP	ByteToWord	; From CPSubs
	IMP	CheckCPDMAComplete	; From CPSubs
	IMP	ClearDmaChannel		; From DmaSubs
	IMP	Copy		; From CPSubs
	IMP	DoNakedNotify	; From CPSubs
	IMP	DisableRST	; From Common
	IMP	EnableRST	; From Common
	IMP	EndFDBuffer	; From Buffer
	IMP	ErrorReport	; From MPTask
	IMP	FloppyActiveSwitch1	; From DmaSubs
	IMP	FloppyActiveSwitch2	; From DmaSubs
	IMP	FloppyActiveSwitch3	; From DmaSubs
	IMP	FloppyActiveSwitch4	; From RS232CGet
	IMP	FloppyIocbAddr	; From Common
	IMP	FloppyIocbAddrHi	; From Common
	IMP	PortBusyFlag	; From CPSubs
	IMP	ReadCPbuffer	; From CPSubs
	IMP	ReadDmaCompletion	; From DmaSubs
	IMP	StartCPReadDMA	; From CPSubs
	IMP	StartCPWriteDMA	; From CPSubs
	IMP	StartFloppyChannel	; From DmaSubs
	IMP	StartFDBuffer	; From Buffer
	IMP	WordToByte	; From CPSubs
	IMP	WriteCPbuffer	; From CPSubs
	IMP	WriteDDTrack	; From DirectFormat
	IMP	WriteSDTrack	; From DirectFormat
	IMP	ZeroCommand	; From Common

	EXP	CPXferCnt	; ** Debugging
	EXP	Cylinder	;  For DirectFormat
	EXP	DiskHead	;  For DirectFormat
	EXP	DiskStatus	;  For DirectFormat
	EXP	DiskStatusHi	;  For DirectFormat
	EXP	EncSectorLen	;  For DirectFormat
	EXP	FloppyIOCB
	EXP	FloppyIntr	;  for Common
	EXP	Gap3Len		;  For DirectFormat
	EXP	QuarterSectorLen;  For DirectFormat
	EXP	Sector		;  For DirectFormat
	EXP	SectorCnt	;  For DirectFormat
; 
	DB	opJMP
FloppyTaskResumeAddress:
	DW	StartFloppyTask
;	JMP	WaitForPortToReadIOCB
;	JMP	WaitForPortForBufService
;	JMP	WaitCPDmaCompletion
;	JMP	TestDiskSectorDone

;  Floppy task internal storage.

FloppyCSB:			;14021	
	DS	1*2


;  IOCB:
;  Note: words 11 and 12 are only used by the Format Track command.
FloppyIOCB:
	ds	2		;  Word 0: Buffer Pointer (low 16 bits)
	ds	2		;  Word 1: Buffer Pointer (high)
	ds	2		;  Word 2: Buffer size (words)
	ds	2		;  Word 3: Sector Length (words)
	ds	2		;  Word 4: Context (bit 11-Troy/IBM', bit 12-Double/Single'
	ds	2		;  Word 5: Cylinder
	ds	2		;  Word 6: Head (bits 0:7), Sector (bits 8:15)
	ds	2		;  Word 7: Sector count
	ds	2		;  Word 8: IOCB result
	ds	2		;  Word 9: IOCB command
	ds	2		;  Word 10: Escape Command
	ds	2		;  Word 11: SectorLen/4 (bits 0:7),
				;	    Encoded Sector len (bits 8:15)
	ds	2		;  Word 12: SectorsPerTrack (bits 0:7)
				;	    NumFillCharsInGap3 (bits 8:15)
	ds	6		;  Word 13-15: (Reserved)


;  DATA BUFFER  (it should be at least 2k bytes = 4 standard sectors):
;  (In Buffer.asm)

;  CONTROL Blocks:

DiskDmaFunction:
	ds	1		;  Function field: DmaRead or DmaWrite
;  Dma control block for Disk operation:
DiskDmaCB:
	ds	2		;  Size of data buffer (bytes)
DiskBufPtr:
	ds	2		;  Start of data buffer

 
;  CP Port Control Blocks:
;  Items that are not fixed are marked with an '*'

;  PCB for IOCB Fetch.
FDIocbPCB:
	ds	2		;  *CP buffer pointer (low): IOCB pointer from CSB
	DW	CPIOCBHi	;  CP buffer pointer (high)
	dw	32		;  CP buffer count (bytes):  IOCB size
	dw	FloppyIOCB	;  Pointer to IOP buffer:  Local IOCB

;  PCB for data transfer.
FDDataPCB:
	ds	2		;  *CP buffer pointer (low): Data buffer address
	DW	1		;  *CP buffer pointer (high): Data buffer address
				;  govern the transfer.  Swapped + Virtual.
	ds	2		;  *CP buffer count (bytes):  Data buffer size
CPBufPtr:
	ds	2		;  *Pointer to IOP buffer:  Local Data buffer

;  PCB for IOCB result update.
FDResultPCB:
	ds	2		;  *CP buffer pointer (low): IOCB.sectorCount
	DW	CPIOCBHi	;  CP buffer pointer (high)
	dw	4		;  CP buffer count (bytes):  Sector count, result
	dw	FloppyIOCB+IocbSectorCnt	;  Pointer to IOP buffer:  Local IOCB.sectorCount

;  PCB to reset FloppyIocbAddr.
ResetFloppyIocbAddr:
	dw	FloppyIocbLoc	;  CP buffer pointer (low): IOCB pointer
	dw	CPIOPageHi	;  CP buffer pointer (high)
	dw	2		;  CP buffer count (bytes):  IOCB size
	DW	ZeroCommand	;  Pointer to IOP buffer:

;  PCB to read Floppy CSB.
ReadFloppyCSB:
	dw	FloppyCSBLoc	;  CP buffer pointer (low): IOCB pointer
	dw	CPIOPageHi	;  CP buffer pointer (high)
	dw	FloppyCSBSize	;  CP buffer count (bytes):  IOCB size
	DW	FloppyCSB	;  Pointer to IOP buffer:



;  Miscellaneous storage:

FDCStateVal:
	ds	1		;  constant for FDC state register
DefaultFDCState:
	db	DefFDCStateVal	;  constant for FDC state register
DiskStatus:
	ds	2		;  Disk status after operation (Low: internal, Hi: external)
DiskStatusHi	equ	DiskStatus+1
RemainSize:
	ds	2		;  Remaining size of IOCB buffer
RemainSizeHi	equ	RemainSize+1
RestoreCmd:
	ds	1		;  Restore Command
SeekCmd:
	ds	1		;  Seek Command
ReadSectorCmd:
	ds	1		;  Read Sector Command
WriteSectorCmd:
	ds	1		;  Write Sector Command
SectorCmd:
	ds	1		;  Sector Command storage for the sector command

;  Disk address data.
DiskHead:
	ds	1		;  Current side
Cylinder:
	ds	1		;  Current cylinder
DCylinder:
	ds	1		;  Desired cylinder in Seek

;  Note:  Sector, NewIntCycle, ErrorSectorCnt MUST be adjacent bytes!!.
Sector:
	ds	1		;  Current sector
F1797:
	dw	8H		;  F1797 commands:  8H => 1797, 0 => 1793
S800:
	dw	0H		;  SA800 drive:  2H => SA800, 0 => SA850

;  Variables used to format a track
SectorCnt:
	ds	1		;  number of sectors left in track being
				;  formatted
Gap3Len:
	ds	1		;  number of fill chars in Gap 3 of Diskette
QuarterSectorLen:
	ds	1		;  size of data record/4.
EncSectorLen:
	ds	1		;  encoded length of sector (00=>128 bytes,
				;  01=>256, 10=>512, 11=> 1024 bytes)

;  Disk and CP transfer counts
CPXferCnt:
	ds	1		;  # or CP transfers to do in disk operation
;  Error mask used in transfer
DkErrorMask:
	ds	1		;  one mask for reads, one for writes

;  Boolean used to decide if disk is running
DiskActive:
	ds	1		;  0FF=> disk is doing a data transfer,
				;  00 => no data transfer
;  Boolean used to decide if CP<->IOP transfer should occur
DiskSectorDone:
	ds	1		;  00 => no transfer needed
				; 0FF => need a transfer
;  **Debugging variables:
NoYield:
	db	0		;  1 = Yield only at end of Command
;
;  -------------------------------------------------------------------

{ Check CSB-IOCB pointer. Get high byte of virtual address. If it is non-zero, start the floppy.  No IOCB will ever be put in virtual page zero.}

StartFloppyTask:
	lda	FloppyIocbAddrHi	
	ora	a		
	JZ	FloppyTaskYield

	LDA	PortBusyFlag
	ORA	A
	JNZ	FloppyTaskYield

	LXI	H,ReadFloppyCSB
	CALL	ReadCPBuffer	;Read Floppy CSB

{Transfer the IOCB into local memory.}
	call	Disable75	;  turn off all FDC interrupts so commands
				;  that don't use them won't be bothered
	LHLD	FloppyIocbAddr	;  Fetch IOCB pointer in main memory (low 16 bits)
	shld	FDIocbPCB+CPAddr3	;  Store in PCB CP address slot
;  Setup the FDResultPCB with the pointer to the Sector count in the IOCB.
	lxi	D,CPIocbSectorCnt	;  DE ← SectorCnt index
	dad	D		;  H,L ← Pointer to SectorCnt location in IOCB
	shld	FDResultPCB+CPAddr3	;  Store back (Note: assume no 64K crossing)

	lxi	h,FDIocbPCB	;  Pointer to FDIocbPCB CPport control block
	call	ReadCPbuffer	;  Read CP main memory
;  IOCB is in local memory.  Clear the Result slot.
	lxi	h,0		;  Result cleared
	shld	FloppyIOCB+IocbResult

;  Dispatch on command.
FDCommandDisp:
	lda	FloppyIOCB+IocbCommand	;  Get command (low 8 bits)
	lxi	h,FDCommandTable	;  Point to command table
	jmp	CommandDispatch

;  Command table is a table of starting address of the command code.

FDCommandTable:
C0:	dw	DoNOPCmd		;  NOP (command=0)
C1:	dw	DoReadSectorCmd	;  Read Sector (command=1)
C2:	dw	DoWriteSectorCmd	;  Write Sector (command=2)
C3:	dw	DoWriteDelSectorCmd	;  Write Deleted Sector (command=3)
C4:	dw	DoReadIDCmd		;  ReadID (command=4)
C5:	dw	DoFormatTrackCmd	;  FormatTrack (command=5)
C6:	dw	DoRecalibrateCmd	;  Recalibrate (command=6)
C7:	dw	DoInitializeCmd		;  Initialize (command=7)
C8:	dw	DoEscapeCmd		;  Escape (command=8)
EndCommandTable:
	nop
MaxFloppyCommand	equ	(EndCommandTable-FDCommandTable)/2	;  Max. number +1

;  Check for valid command, and dispatch on the command.
;    Jump to Contents [CommandTable + 2*(Command)].
CommandDispatch:
	cpi	MaxFloppyCommand	; Check for largest command
	jnc	NoValidCommand	; nc => command (in A) >= MaxCommand
	add	a		;  A ← 2*(Command), assume less than 128 commands
	mov	e,a		;  DE ← 2*Command
	mvi	d,0
	dad	d		; HL ← CommandTable+2*(Command)
;  Command Found.  H,L points to appropriate slot
	mov	e,m		;  Address to D,E
	inx	h
	mov	d,m
	push	d		;  stack ← starting addr of command's routine

{When the floppy and RS232C are both running, we must disable interrupts whenever DmaActive is being updated.}
	MVI	A,opJZ
	STA	FloppyActiveSwitch1
	STA	FloppyActiveSwitch2
	STA	FloppyActiveSwitch3
	MVI	A,opDI
	STA	FloppyActiveSwitch4
	ret				;  Jump to command

;  The command was not valid.  Mark IOCB with error result.
NoValidCommand:
	lxi	h,ErrorNoValidCommand		;  ERROR:  Invalid command
	call	MakeBadIOCBResult
	jmp	FinishCommand		; return to normal mode
;

;  COMMAND processing.

;  All commands assume that the current context is up to date, and that the controller
;   is enabled and ready for a command.

;  Command 0:  NOP command.

{ Issue a Force Interrupt command, to obtain a type 1 status.}
DoNOPCmd:
	mvi	a,ForceInt0Cmd		;  Issue command with immediate interrupt
	out	FDCCommand		;  Issue command
;  Wait for completion (should be immediate).
	call	WaitFDCBusy		;  Call end of FDCReset subroutine
	
;--new			7-Oct-85 12:03:23
	jnc	FDCNoopOkay
	lxi	h,597 ; FDCTimeoutInNOP
	call	MakeBadIOCBResult
	jmp	FinishCommand
;--endnew		7-Oct-85 12:03:23

FDCNoopOkay:
	call	ReadFDCStatus	;  Call end of WaitFDCCompletion for status
;  Command complete.  Form IOCB result from status in B,C.
	call	MakeIOCBResultType1	;  Convert B,C into an IOCB.result, store in IOCB

;  Finish up the command.  This code used by all commands.
FinishCommand:
;  Update the Sector Count and Result fields of the IOCB in main memory.
;  The fields are already updated in local IOCB.
;  Then clear the IOCB pointer in the CSB, and do a naked notify.
;  Call the subroutine at NotifyCPResultNoAcq if the CP port is already acquired.

WaitForPortToNotifyResult:
	LDA	PortBusyFlag
	ORA	A
	JZ	NotifyCPResult
	LXI	H,WaitForPortToNotifyResult
	JMP	SaveFloppyTaskResumeAddressAndYield
NotifyCPResult:
	lxi	h,FDResultPCB	;  Pointer to FDResultPCB CPport control block
	call	WriteCPbuffer	;  Write IOCB in main memory
;  Clear FloppyIocbAddr.
	lxi	h,ResetFloppyIocbAddr
	call	WriteCPbuffer	;  Write IOCB in main memory
;  Do the naked notify.
	lhld	FloppyCSB	;  Mask is in first word
	call	DoNakedNotify

FinishCommand1:
{When the floppy and RS232C are both running, we must disable interrupts whenever DmaActive is being updated. Put things back so that we don't disable interrupts. This is important for fast RS232C.}
	MVI	A,opJMP
	STA	FloppyActiveSwitch1
	STA	FloppyActiveSwitch2
	STA	FloppyActiveSwitch3
	XRA	A		;A ← NOP
	STA	FloppyActiveSwitch4
	LXI	H,StartFloppyTask
	JMP	SaveFloppyTaskResumeAddressAndYield


;  Command 1:  Read Sector.

{  The Read Sector command can operate on a run of sectors on the same track.
To overlap disk operations with transfers to main memory, double buffering is used.
}

DoReadSectorCmd:
	lda	ReadSectorCmd	;  Set sector command
	sta	SectorCmd
DoReadAddrCmd:
	mvi	a,ReadErrorMask		;  Set the read error mask for
	sta	DkErrorMask		;  Do Transfer
	xra	a			;  Disk has done nothing yet
	sta	DiskSectorDone		;  so start with a disk op.
	mvi	a,InDmaFunc	;  Set up Dma CB for Reads, memory writes
	jmp	DoTransfer		;  Do the rest of the command



;  Command 2:  Write Sector.

{  The Write Sector command can operate on a run of sectors on the same track.
To overlap disk operations with transfers to main memory, double buffering is used.
}

DoWriteSectorCmd:
	lda	WriteSectorCmd	;  Fix write sector command
	ani	nDeletedDataMask	;  Clear the Deleted data bit
	sta	SectorCmd		;  Store in command location
DoWriteSectorStart:
	mvi	a,WriteErrorMask	;  Set proper error mask used to
	sta	DkErrorMask		;  detect FDC errors during writes
	mvi	a,0FFH			;  say disk has "written" all
	sta	DiskSectorDone		;  sectors so we will start by
					;  filling all from the CP
	mvi	a,OutDmaFunc		;  Set up Dma CB for Writes, memory reads
	jmp	DoTransfer		;  go finish the Write command



;  Command 3:  Write Deleted Sector.

DoWriteDelSectorCmd:
	lda	WriteSectorCmd	;  Fix write sector command
	ori	DeletedDataMask	;  Set the Deleted data bit
	sta	SectorCmd
	jmp	DoWriteSectorStart



;  Command 4:  Read ID.

{  Read the first Disk ID field.  Set up Sector command, change "sector length",
and jump into Read Sector command.}

DoReadIDCmd:
	mvi	a,RAddrCmd	;  Set ReadID command
	sta	SectorCmd
	lxi	h,RAddrDataLen	;  Fake sector length
	shld	FloppyIOCB+IocbSectorLen
	jmp	DoReadAddrCmd


;  Command 5:  Format Track.

{  The Format Track can format a run of track on one side of the disk.  The recording density is specified in bit 12 of the IocbMiscContext word of the IOCB.  The number of sectors per track is in IocbSectorCnt.  The desired Cylinder is in IocbCylinder and the side in IocbHead.  The encoded sector length is in IocbEncSectorLen, the Sector Length/4 (always fits in a byte) is in IocbQuarterSectorLen, the number of fill characters in Gap Three is in IocbGap3Len and the number of track to be formatted is in IocbTrackCnt.  All parameters needed by DirectFormat must be put in individual variables.  DirectFormat cannot access them in FloppyIOCB since it cannot have an imported variable (FloppyIOCB) in expressions.  This command loops seeking to a track, then formatting it.
}

DoFormatTrackCmd:
;  First, was there a disk change?  If so, don't format the new disk
	call	CheckDiskChange
	ora	a		; 0=> no disk change, nz=> disk changed
	jnz	FinishCommand	;  If disk changed, quit now
;  Set the context for operation and update FDCState register. (Density, head, format)
	call	SetIOCBContext		;  Set up the current context
;  Check whether we need to do a seek.  Compare desired cylinder with current value.
	lxi	h,Cylinder		;  Point to current value
	lda	FloppyIOCB+IocbCylinder	;  Assumes high part of cylinder is 0
	cmp	m
	jz	FormatTrackStart	;  z => same, don't need a seek
FormatTrackSeek:
	sta	DCylinder
	mov	d,a			;  Desired cylinder to D
	mvi	a,SkCmdNoV		;  do a seek with no verify since
					;  there is probably trash there
	mov	e,a			;  Command to E
	call	DoSeek
;  Wait for disk completion.  Yield if not complete.
	call	WaitFDCCompletion
;  Command is complete.  D has internal status, E has external status.
;  Check for error completion in D  (Not Ready, Seek error, CRC error, Busy)
FormatTrkSeekComplete:
	mov	a,D	
	ani	Type1ErrorMask	;  Check for error status
	jnz	FormatTrackStepError	;  nz => there was an error, abort
;  Check and update the Cylinder location.
	call	UpdateCylinder
;  Successful seek.  Start the Format.
;  Decide whether to do single or double density and go do the appropriate
;  routine
FormatTrackStart:
	mvi	a,0		;  load number of first sector-1 (code does
				;  increment before storing
	sta	Sector
	lda	FloppyIOCB+IocbEncSectorLen	; set the encoded sector
				;  length
	sta	EncSectorLen
	lda	FloppyIOCB+IocbSectorsPerTrack	;  get number of sectors
				;  per track
	sta	SectorCnt
	lda	FloppyIOCB+IocbQuarterSectorLen	; set the data record len/4
	sta	QuarterSectorLen
	lda	FloppyIOCB+IocbGap3Len	;  Set the number of fill chars
				;  in Gap 3
	sta	Gap3Len
	lda	FDCStateVal
	ani	DDenMask	; is the double density bit on?
	jnz	DoDoubleDensityFormat	;  yes, format for double density
	call	WriteSDTrack	;  no, format a single density track
	jmp	CheckFormatResult	;  and see if done with run of tracks
DoDoubleDensityFormat:
	call	WriteDDTrack	;  format a double density track
CheckFormatResult:
{Direct format used to return the disk status in the BC register pair as well as in DiskStatus and DiskStatusHi.  Now we don't take any chances messing with BC while interrupts can occur.}
	LDA	DiskStatusHi
	MOV	E,A
	LDA	DiskStatus
	MOV	D,A
	ani	WriteErrorMask		;  Check for error status
	jnz	FormatTrackError	;  nz => there was an error, abort the IOCB
;  Successful Format.  Store completion in IOCB, in case it's the last
;  written.
	call	MakeIOCBResultType2	;  Convert DE into an IOCB.result,
					;  store in IOCB
;  See if there are any more tracks to format
	lda	FloppyIOCB+IocbSectorCnt	;  This holds the Track count
	dcr	a			;  have done one more track
	sta	FloppyIOCB+IocbSectorCnt
	jz	EndFormatTrk		;  quit if no more Tracks to do
	lda	Cylinder		;  Else, calculate next cylinder num
	inr	a
	jmp	FormatTrackSeek		;  and go format that track on this
					;  side
;  Exits:
FormatTrackStepError:
	call	SetErrorResult		;  Set the Error bit
	call	MakeIOCBResultType1	;  Update Result location (from B,C)
	jmp	EndFormatTrk

;  Completion error in the Format Track command.
;  Set the error bits in IOCB.Result
FormatTrackError:
	call	SetErrorResult		;  Set the Error bit
	call	MakeIOCBResultType2	;  Convert DE into an IOCB.result
EndFormatTrk:
	jmp	FinishCommand




;  Command 6:  Recalibrate.

{  Do a Restore command, and check for Track 0.  Set Cylinder to 0.  Before the Restore 
is done, do 10 step-ins to ensure that the subsequent restore will work}

DoRecalibrateCmd:
	mvi	d,10		;  Issue 10 Stepin commands
	mvi	e,StepInCmdNoV	;  Send StepIn commands (no verify)
InitStepLoop:
	mov	a,e
	out	FDCCommand	;  Issue command
;  Wait for completion.
	XCHG	
	SHLD	SaveDEOverYield
	call	WaitFDCCompletion	;  A = completion
	DB	opLXID		;DE ← SaveDEOverYield
SaveDEOverYield:
	DW	0
;  Don't care what the status is.  Any errors will be caught by the Recalibrate.
	dcr	d		;  More Step-ins?
	jnz	InitStepLoop
;  We should now be beyond (in) from Track 00.  Do the Restore.
DoRestore:
	lda	RestoreCmd
	out	FDCCommand	;  Start the command
;  Wait for disk completion.  Yield if not complete.
	call	WaitFDCCompletion
;  Command is complete.  D has internal status, E has external status.
;  Check for error completion in D  (Not Ready, Seek error, CRC error, Busy)
	mov	a,D	
	ani	Type1ErrorMask	;  Check for error status
	jnz	RestoreError	;  nz => there was an error, abort
;  Check for Track 0 bit.
	mov	a,D
	ani	FDCTk0Mask
	jz	RecalibrateError	;  z => NO track 00 bit, error
;  Set completion.
	call	MakeIOCBResultType1	;  Convert B,C into an IOCB.result, store in IOCB
;  Check and update the Cylinder location.
	xra	a			;  Desired cyclinder is 0
	sta	DCylinder
	call	UpdateCylinder
;  Complete command.
EndRecalibrate:
	jmp	FinishCommand		;  Finish up the command

;  Error exits.
;  Error on the Restore command.
RestoreError:
	call	SetErrorResult		;  Set the Error bit
	call	MakeIOCBResultType1	;  Update Result location (from B,C)
	jmp	EndRecalibrate

;  No track 0 bit.  Set recalibrate error.
RecalibrateError:
	mvi	a,ErrorMask		;  Set the Error bit in Result
	ori	RecalibrateErrorMask		;  Set the RecalibrateError bit
	call	SetErrorResult1	;  Store the Error bits
	call	MakeIOCBResultType1	;  Update Result location (from B,C)
	jmp	EndRecalibrate


;  Command 7:  Initialize.

{  This command is the first command that should be issued to the FloppyTask.
It will initialize the floppy controller, set default values of single density, head=0,
no Precomp, and IBM format in the FDC State register.  Setup the values of the various
disk commands.  The DiskChange bit in the drive is cleared.}

DoInitializeCmd:
	call	FDCReset		;  Reset the controller
	
;new 	  		7-Oct-85 12:03:23
	jnc	FDCResetOkay2
	lxi	h,598 ; FDCTimeoutInInit
	call	MakeBadIOCBResult
	jmp	FinishCommand
;endnew 		 7-Oct-85 12:03:23

;  Initialize various command values.

FDCResetOkay2:
	lda	F1797			;  Form ReadSector command
	adi	RSectorCmd
	sta	ReadSectorCmd
	lda	F1797			;  Form WriteSector command
	adi	WSectorCmd
	sta	WriteSectorCmd
	lda	S800			;  Form Restore command
	adi	ResCmd
	sta	RestoreCmd
	lda	S800			;  Form Seek command
	adi	SkCmd
	sta	SeekCmd
;  Disable interrupts from the FDC chip
	call	Disable75
;  Do a NOP command to get the disk status
	jmp	DoNOPCmd



;  Command 8:  Escape.

{  Look at the next word in the IOCB to determine the Escape command.
Currently, 1 => Clear Disk Change, #1 => invalid.
}

DoEscapeCmd:
	lda	FloppyIOCB+IocbEscCommand	;  Get escape command
	cpi	ClrDiskChgCmd		;  Is it a Disk Change Clear?
	jz	DoDiskChgClr		;  z => Disk change clear
InvalidEscapeCmd:
	lxi	h,ErrorInvalidEscapeCmd	;  ERROR:  Invalid Escape command
	call	MakeBadIOCBResult
	jmp	FinishCommand

;  Clear disk change by disabling drive and then enabling the drive with drive select.
DoDiskChgClr:
	lda	FDCStateVal		;  Get current value for FDCState
	ani	nDriveSelectMask	;  Clear Drive Select
	out	FDCState
	ori	DriveSelectMask	;  Set Drive Select again
	out	FDCState
	jmp	DoNOPCmd		;  Do a NOP command to get the status




;
;  FLOPPY DISK SUBROUTINES.

;  Subroutine:   Disable75
;  Disable the RST 7.5 interrupt, clear the RST 7.5 FF.

Disable75:
	mvi	a,ResetRst75+Rst75DisableMsk	;  RST 7.5, clear 7.5 FF
	call	DisableRST		;  Do the disable
	ret

;new - 			7-Oct-85 12:03:23
;  Subroutine:   FDCReset.
;  Reset the FDC, and abort the automatic Restore with a ForceInterupt.
;  Set the Context in FDCState and FDCStateVal to the default values.
;  The diskChange bit in the drive is also cleared.
;  Returns status in A, after command completes.

FDCReset:
	mvi	l,3		; count = we will try three times to reset

FDCResetLoop:
        mvi     a,DisableFDC    ;  Clear state register, reset FDC, enable Waits
        out     FDCState
        mvi     a,9             ;  Wait for more than 50 usec (MR pulse)
        call    DelayV
        lda     DefaultFDCState ;  Get default values
        sta     FDCStateVal     ;  Get control byte for FDC
        out     FDCState
        call    Disable75       ;  Disable the Floppy interrupt
        mvi     a,ForceInt0Cmd  ;  Issue Force Interrupt command
        out     FDCCommand
        call    Delay           ; Busy status not available for 12 usec

	call	WaitFDCBusy	; wait for FDC busy
	jnc	FDCResetOkay

	dcr	l		; couldn't reset FDC
	jnz	FDCResetLoop
	stc
	ret

FDCResetOkay:
	stc
	cmc
	ret

;  Wait for Busy to reset.  Status returned in A and D.

; The loop here is where the DLion hangs occasionally when doing rapid floppy
; operations.

WaitFDCBusy:
	push	h
	lxi	h,5000		; or other count-down value.  ***

WaitFDCBusyLoop:
        in      FDCStatus       ;  Check for restore command completion
        mov     D,a
        ani     FDCBusyMask
        jz    	WaitFDCBusyExit

; count down for timeout
	dcr	l
	jnz	WaitFDCBusyLoop
	dcr	h
	jnz	WaitFDCBusyLoop

; timeout occured.
	pop	h
	stc
	ret
; normal exit.
WaitFDCBusyExit:
        mov     a,D             ;  Return status in A too
	pop	h
	stc
	cmc
        ret

;endnew - 		7-Oct-85 12:03:23

;  Subroutine:   WaitFDCCompletion.
;  Wait for the completion of the FDC command.
;  If the command is not complete, the task will Yield control.
;  Note the use of this routine and interrupt processing are
;  mutually exclusive.
;  On exit:
;	  D = internal status
;	  E = external status
;	  DiskStatus = full status

WaitFDCCompletion:
;  We save the return address because the stack is not saved over a Yield
	POP	H		;HL ← return address
	SHLD	WaitFDCCompletionRet
WaitFDC:
	lda	NoYield
	ora	a
	JNZ	CheckIntReqCompletion	;  nz => Don't do Yield
	LXI	H,CheckIntReqCompletion
	JMP	SaveFloppyTaskResumeAddressAndYield
CheckIntReqCompletion:
	in	IntReq		;  Check for completion in IntReq register
	xri	IntReqMask	;  Complement active low signals
	ani	FDCIntMask
	jz	WaitFDC		;  z => controller still busy	
;  Command has completed.  Get status.
	in	FDCStatus	;  Read internal status
	mov	D,a		;  Save in reg D
	sta	DiskStatus	;  Store status from chip
	in	FDCStatusReg	;  Get external status register.
	sta	DiskStatusHi	;  Store
	mov	E,a		;  Move to E
	DB	opJMP		;JMP WaitFDCCompletionRet
WaitFDCCompletionRet:
	DW	0		;  Return

ReadFDCStatus:
{This subroutine is just like the end of WaitFDCCompletion except that it uses the stack to return.}
	in	FDCStatus	;  Read internal status
	mov	D,a		;  Save in reg D
	sta	DiskStatus	;  Store status from chip
ReadFDCStatusHi:
	in	FDCStatusReg	;  Get external status register.
	sta	DiskStatusHi	;  Store
	mov	E,a		;  Move to E
	RET

;  Subroutine:   UpdateDataPCB.
;  Update the CP address, and IOP address in the DataPCB.
;  Update IOP address in DataPCB;
;  Note that the address update adds the count to the 3-byte address.
;  On Exit:
;     H,L = Address of Next IOP buffer

UpdateDataPCB:
	lda	FloppyIOCB+IocbCommandHi	;  Check Command [0]
	ora	a
	jm	UpdateIOPPtr		;  m => High bit is 1, no adjustment of Size
	lhld	FDDataPCB+CPCnt	;  H,L ← count in the DataPCB
	CALL	ByteToWord	;  H,L ← count in words
	xchg				;  D,E ← count in words
	lhld	FDDataPCB+CPAddr3	;  H,L ← low address word in the DataPCB
	dad	d			;  H,L ← Low CPAddress + TransferCount
	shld	FDDataPCB+CPAddr3	;  Store back low address word in the DataPCB
	lda	FDDataPCB+CPAddr1	;  A ← High address byte
	aci	0			;  Add in any possible carry from above addition
	sta	FDDataPCB+CPAddr1	;  Store back in PCB

;  Update IOP buffer pointer.
UpdateIOPPtr:
	lhld	CPBufPtr		;  get CP's pointer to IOP buffer
	call	IncrIOPPtr		;  increment it in ring buff fashion
	shld	CPBufPtr		;  and store it away
	ret


;  Subroutine:   IncrIOPPtr.
;  Set up an IOP data buffer pointer based on the value in HL.
;  On entry:
;	H,L = original contents of buffer pointer.
;  On exit:
;	H,L = New buffer pointer contents
;  Only A and HL are changed.
;  Note that the ring buffer wraps around if the END of the NEXT buffer lies
;  after the end of the ring buffer, not only if the beginning of the next buffer
;  does.  This protects us from sectors that are too large and from not being on
;  a proper buffer boundry.  The second case will happen when we switch from
;  transferring small sectors to transferring large ones.
IncrIOPPtr:
	push	d		;  save regs
	push	h
	xchg			;  DE ← orig buf ptr
	lhld	DiskDmaCB+DmaBufCnt	; HL ← length of buffer
	dad	h		;  HL ← length of two buffers
	dcx	h		;  HL ← distance to end of next buffer
	dad	d		;  HL ← pointer to end of next buffer area
	lxi	d,EndFDBuffer	;  get end of total buffer area
	call	HLcmpDE		;  Compare end of next buffer-End of Buffer area
	jc	FinIncrIOP	;  if end of next buffer<= End of buff area, incr this
				;  buffer pointer
	lxi	h,StartFDBuffer	;  else, start at beginning of buf area
	pop	d		; toss old pointer
	pop	d		; restore DE
	ret
; point to the next disk buffer area in the ring buffer
FinIncrIOP:
	pop	d		;  get original buffer pointer
	lhld	DiskDmaCB+DmaBufCnt	; HL ← length of disk buffer
	dad	d		;  HL ← ptr to next disk buffer
	pop	d		;  restore DE
	ret 



;  Subroutine   HLcmpDE.
;  Set the conditions flags according to the double register compare HL-DE.
;  Only the A reg and the flags are affected.

HLcmpDE:
	mov	a,h		;  compare the hi bytes first
	cmp	d
	rnz			;  only compare the lo bytes if the hi
	mov	a,l		;  bytes are equal
	cmp	e
	ret



;  Subroutines to insert the result bits from the disk operation into the IOCB.
;  The subroutines assume that the Result word was cleared at the start of the operation.
;  (In parenthesis is indicated what is done after a Type1, and Type2 command)

; Bit 0:	diskChange (copy, copy) 
; Bit 1:	0 (0, 0)
; Bit 2:	twoSided  (copy, copy)
; Bit 3:	DiskID  (copy, copy)
; Bit 4:	error  (error, error) - inserted externally to subroutine
; Bit 5:	0 (0, 0)
; Bit 6:	recalibrateError (RestoreError/0, 0)
; Bit 7:	dataLost (0, dataLost from bit 13)

; Bit 8:	notReady (copy, copy)
; Bit 9:	writeProtect  (copy, copy)
; Bit 10:	deletedData  (0, copy)
; Bit 11:	recordNotFound  (copy, copy)
; Bit 12:	crcError   (copy, copy)
; Bit 13:	track00 (copy, from Seek)
; Bit 14:	index (copy, from Seek)
; Bit 15:	busy  (copy, copy)


;  Subroutine:  SetErrorResult.
;  Set the Error bit in the IOCBResult.
;  Call at SetErrorResult1 with different error mask in A.

SetErrorResult:
	mvi	a,ErrorMask		;  Set the Error bit in Result
SetErrorResult1:
	lxi	h,FloppyIOCB+IocbResultHi
	ora	m
	mov	m,a
	ret


;  Subroutine:   MakeIOCBResultType1.
;  Insert the Result bits in D and E into the IOCB result.
;  D has low bits (internal status), E has high bits (external status).
MakeIOCBResultType1:
;  Process low part of Result.
	lxi	h,FloppyIOCB+IocbResult	;  Point to Low Result in IOCB
	mvi	a,nType1ZeroMaskLo	;  Clear values that should be zero
	ana	D
	ora	m
	mov	m,a 			;  Store back in IOCB 
;  Process high part of Result.
	inx	h			;  Point to High Result in IOCB
	mvi	a,nType1ZeroMaskHi	;  Clear values that should be zero
	ana	E
	ora	m
	mov	m,a 			;  Store back in IOCB 
	ret

; Subroutine: MakeBadIOCBResult - make the IOCB bad result flags
; HL -> would-be MP code.
MakeBadIOCBResult:
	shld	FloppyIOCB+IocbResult	; store MP code in result slot
	lda	FloppyIOCB+IocbResult+1	; load Hi byte
	ori	nIocbErrorMaskOr		; set error bit
	sta	FloppyIOCB+IocbResult+1
	ret
	

;  Subroutine:   MakeIOCBResultType2.
;  Insert the Result bits in D and E into the IOCB result.
;  D has low bits (internal status), E has high bits (external status).
MakeIOCBResultType2:
;  Process low part of Result.
	lxi	h,FloppyIOCB+IocbResult	;  Point to Low Result in IOCB
	mvi	a,nType2ZeroMaskLo	;  Clear values that should be zero
	ana	D
	ora	m
	mov	m,a 			;  Store back in IOCB 
;  Process high part of Result.
	inx	h			;  Point to High Result in IOCB
	mvi	a,nType2ZeroMaskHi	;  Clear values that should be zero
	ana	E
	ora	m
	mov	m,a  			;  Store back in IOCB
;  Fix up DataLost.  Move bit 5 in DiskStatus to bit 7 in Result. 
	lda	DiskStatus		;  Get original low status
	ani	FDCLostData		;  Isolate the DataLost bit
	rrc				;  Align appropriately
	rrc
	ora	m
	mov	m,a  			;  Store back in IOCB
	ret

;  Subroutine:  CheckDiskChange.
;  Check if the DiskChange bit is True.
;  If so, set the Error and DiskChange bits in IOCB.result.
;  On exit:
;	A = 0 :  No disk change
;	A # 0 :  Disk change,  IOCB result updated

CheckDiskChange:
;  Check the DiskChange bit.  If true, abort this IOCB, with Error, DiskChange in Result.
	call	ReadFDCStatusHi	;  Read external status (returned in C)
	mov	a,E
	ani	DiskChangeMask	;  Mask DiskChange bit
	rz				;  z =>  no DiskChange, proceed
;  There was a DiskChange.
	mvi	D,0			;  Fake a DE status
	call	SetErrorResult		;  Set Error bit
	call	MakeIOCBResultType1	;  Update Result location (from DE)
	mvi	a,-1			;  Set A nonzero
	ret

	
;  Subroutine:   SetIOCBContext.
;  Set the controller context from the values in the IOCB.
;  The parameters changed are Double/Single' density, Troy/IBM', Side select.
;  Both FDCState and FDCStateVal are changed.

SetIOCBContext:
	lxi	h,FDCStateVal	;  Point to FDCStateVal
	mvi	a,nMiscContextMask	;  Clear out old context bits in FDCStateVal
	ana	m
	mov	m,a		;  Store back in FDCStateVal
	lda	FloppyIOCB+IocbMiscContext
	ani	MiscContextMask	;  Mask out other bits
	ora	m		;  OR in Double/Single', Troy/IBM' bits
	mov	m,a		;  Store back in FDCStateVal
	lda	FloppyIOCB+IocbHead
	ani	HeadMask
	sta	DiskHead
	push	h		;  Save H,L temporarilly
	jz	ContextSide0	;  Side 0 specified

;  Side 1 specified.
	mvi	a,Side1Mask
	ora	m		;  OR in Side 1
	mov	D,a		;  Save FDCState value in D
;  Fix up Read/Write commands.
	lxi	h,SectorCmd	;  Set S bit in  current SectorCmd
	mvi	a,Type2SMask
	ora	m
	mov	m,a
	jmp	SetContext
;  Side 0 specified.
ContextSide0:
	mvi	a,nSide1Mask
	ana	m		;  Clear Side 1 bit
	mov	D,a		;  Save FDCState value in D
;  Fix up Read/Write commands.
	lxi	h,SectorCmd	;  Clear S bit in current SectorCmd
	mvi	a,nType2SMask
	ana	m
	mov	m,a
SetContext:
	pop	h		;  Restore H,L to point to FDCStateVal
	mov	a,D		;  Restore saved value from D
	mov	m,a		;  Store back saved value in FDCStateVal
	out	FDCState	;  Output to controller
	ret



;  Subroutine:	DoTransfer

{  This routine is used in all read and write sector commands.  The common code reduces the total size of Domino while providing a relatively clean procedure.
}
;  On Entry:
;   SectorCmd:   holds command word
;   DiskSectorDone: holds Boolean telling whether to start disk or CP
;		    transfer first
;   A:		    holds DMA function mask
;  Algorithm:
;  Set up the Dma control blocks
;  IF DiskChange, set ERROR and quit
;  seek to proper cylinder if necessary
;  WHILE DiskStatus=0 AND FloppyIOCB+IocbSectorCnt>0 DO
;    WHILE DiskActive DO
;      IF DiskSectorDone THEN
;        IF CPXferCnt>0 THEN
;          call DoCPBufService to transfer sectors between CP and IOP until
;          no IOP Vacancies exist or CPXferCnt=0;
;      call Yield;
;      ENDLOOP;
;    {Disk is now stopped}
;    IF DiskStatus=0 AND FloppyIOCB+IocbSectorCnt>0 THEN
;      DoSectorCmd to start or restart the disk
;    ENDLOOP;
;  Post disk status and quit
DoTransfer:
	sta	DiskDmaFunction	;  Store function in Dma CB
	lxi	h,StartFDBuffer		;  Setup the IOP buffer pointer
	shld	CPBufPtr		;  in DataPCB
	shld	DiskBufPtr		;  and in DiskDmaCB
	xra	a		;  
	sta	DiskActive	;  the disk is not transferring data now,
;  Initialize the DataPCB with CP buffer address, and count, and IOP buffer.
;  CP address from IOCB buffer address, transfer count from sector length in IOCB.
	lxi	h,FloppyIOCB	;  Source
	lxi	d,FDDataPCB	;  Destination
	mvi	A,3		;  No. of bytes (long address).  Note the flags in the MS byte are not
					;  changed.
	call	Copy		;  This copies the 3 bytes of address leaving flags untouched.
	lhld	FloppyIOCB+IocbSectorLen	;  Transfer count=sector length (words)
	CALL	WordToByte
	shld	FDDataPCB+CPCnt	;  Store in PCB count field
	shld	DiskDmaCB+DmaBufCnt	;  Store in Dma CB count field
;  Set the context for operation and update FDCState register. (Density, head, format)
	call	SetIOCBContext
;  Check the DiskChange bit.
	call	CheckDiskChange		;  is there a disk change?
	ora	a
	jnz	FinishCommand		;  nz =>  had a DiskChange, quit
;  Check whether we need to do a seek.  Compare desired cylinder with current value.
	lxi	h,Cylinder		;  Point to current value
	lda	FloppyIOCB+IocbCylinder	;  Assumes high part of cylinder is 0
	cmp	m
	jz	FinXferSetup		;  z => same, don't need a seek
	sta	DCylinder
	mov	d,a			;  Desired cylinder to D
	lda	SeekCmd
	mov	e,a			;  Command to E
	call	DoSeek
;  Nothing else to do, so wait for disk completion.  Yield if not complete.
	call	WaitFDCCompletion
;  Command is complete.  D has internal status, E has external status.
;  Check for error completion in D  (Not Ready, Seek error, CRC error, Busy)
	mov	a,D	
	ani	Type1ErrorMask		;  Check for error status
	jnz	HadXferSeekError	;  nz => there was an error, abort
;  Check and update the Cylinder location.
	call	UpdateCylinder
	
;  Successful seek.  Start the Transfer.
FinXferSetup:
	xra	a			;  clear the Disk status
	sta	DiskStatus		;  there have been no errors so far.
	lda	FloppyIOCB+IocbSectorCnt	; Set the length of the
	sta	CPXferCnt		;  CP transfer
	lda	FloppyIOCB+IocbSector	;  Sector ← IOCB.FirstSector
	sta	Sector			;  set initial sector number
	jmp	WaitForDisk		;  Choose whether to start the
					;  CP or disk transfer first

;  We either got behind the disk or it hasn't started yet.  Start it in either
;  case
StartDisk:
	call	DoSectorCmd		;  restart the disk
WaitForDisk:
	ei				;  make sure Disk can finish
	lda	NoYield
	ora	a
	JNZ	TestDiskSectorDone	;  nz => Don't do Yield
	LXI	H,TestDiskSectorDone
	JMP	SaveFloppyTaskResumeAddressAndYield
TestDiskSectorDone:
	lda	DiskSectorDone		;  should the CP service the IOP
	ora	a			;  buffer? (this is set when a write op
					;  begins, reset when a read op begins)
	jz	CheckDiskActive		;  no, see if the transfer is still
					;  going
	lda	CPXferCnt		;  yes, are there any more sectors
	ora	a			;  to transfer?
	cnz	DoCPBufService		;  yes, fill the IOP buffer
;  The IOP buffer needs no service from the CP.  Is the disk still running?
CheckDiskActive:
	di				;  make sure the disk status does
					;  not change while it is being
					;  investigated
	lda	DiskActive
	ora	a			;  nz => yes
	jnz	WaitForDisk		;  so wait for it
	lda	DiskStatus		;  no, did it stop because of an
					;  error?
	ora	a			;  nz => there was an error
	jnz	PostDkStatus		;  so stop now
	lda	FloppyIOCB+IocbSectorCnt	;  no, did it finish the
	ei				;  (it's ok for things to happen
					;  again)
	ora	a			;  transfer?
	jnz	StartDisk		;  transfer faltered, so start or 
					;  restart the disk.
;  Post the final Disk Status and release the CP port
PostDkStatus:
	ei			;  just in case we got here via an error
				;  posted in DiskStatus
	lda	CPXferCnt	;  Are there any sectors to finish up?
	ora	a		;  nz => disk just stopped, either ok or
	cnz	DoCPBufService	;  because of error.  Transfer all remaining
				;  sectors.
	lda	DiskStatusHi	;  get final external status
	mov	E,a		;  pass status word in DE
	lda	DiskStatus	;  get final internal status
	mov	D,a
;  See if an error result should be posted
	ora	a
	cnz	SetErrorResult		;  no, set the Error bit
	call	MakeIOCBResultType2	;  Update Result location (from B,C)
	jmp	FinishCommand	;  Finish up the command

;  Error branches:

;  Error in the Seek command.
HadXferSeekError:
	call	SetErrorResult		;  Set the Error bit
	call	MakeIOCBResultType1	;  Update Result location (from DE)
	jmp	FinishCommand		;  quit now




;  Subroutine:   DoCPBufService
{This routine is used to transfer data between the CP and IOP memories.  There is one large buffer area for the Floppy disk.  It is divided into multiple buffers each holding one sector.  The CP pointer to this buffer is in CPBufPtr.  The disk pointer is in DiskBufPtr.  This routine keeps transferring data, advancing CPBufPtr and decrmenting CPXferCnt until a) CPXfercnt reaches 0 or b) CPBufPtr=DiskBufPtr.  If it leaves the buffer full, it resets DiskSectorDone. }

DoCPBufService:
	POP	H		;HL ← return address
	SHLD	DoCPBufServiceReturn
WaitForPortForBufService:
	LDA	PortBusyFlag
	ORA	A
	JZ	StartBufTransfer
	LXI	H,WaitForPortForBufService
	JMP	SaveFloppyTaskResumeAddressAndYield

;  Transfer the data for the first sector
;  Is this a read or write operation?  The commands are 001=ReadSector,
;  010=WriteSector, 011=WriteDelectedSector and 100=ReadID.  Hence if bit
;  0000 0010 is off, it is a Read op so we should write to the CP.
StartBufTransfer:
	ei			;  let the interrupt routine in during
				;  CP transfers
	lxi	h,FDDataPCB	;  Pointer to FDDataPCB CPport control block
	lda	FloppyIOCB+IocbCommand	; get the command
	ani	2		;  Is this a read from the disk?
	jnz	ReadFromCP	;  no, so read from CP, write to disk
	call	StartCPWriteDMA	;  yes, start the Dma write to CP main memory
	jmp	WaitCPDmaCompletion
ReadFromCP:
	call	StartCPReadDMA	;  data going to Disk from CP
;  Wait for the transfer to complete
WaitCPDmaCompletion:
	call	CheckCPDMAComplete
	JNZ	CPComplete
	LXI	H,WaitCPDmaCompletion
	JMP	SaveFloppyTaskResumeAddressAndYield
CPComplete:
	call	UpdateDataPCB		;  Update CP address, IOP ptr in PCB
;  Was that the last CP transfer?
	di			;  don't let the interrupt routine change
				;  things now
	lda	CPXferCnt
	dcr	a
	sta	CPXferCnt
	jz	FinCPBufSrvc	;  yes, just finish up the buffer service
;  More sectors to transfer, is the IOP buffer area full?
	xchg			;  DE ← addr of next buffer to be transferred
				;  by CP
	lhld	DiskBufPtr	;  HL ← addr of buff being used by disk now
	call	HLcmpDE		;  is the buffer area full (z=>yes)
	jnz	StartBufTransfer	;  yes, finish the buffer service
;  Buffer is now full, reset DiskSectorDone, release CP port and finish
FinCPBufSrvc:
	xra	a		;  turn off DiskSectorDone
	sta	DiskSectorDone	;  note interrupts are disabled now
	ei			;  let the disk finish now
	DB	opJMP		;JMP DoCPBufServiceReturn
DoCPBufServiceReturn:
	DW	0		;RET



;  Interrupt Routine:   FloppyIntr
;  This routine is called whenever the disk finishes a transfer of one
;  sector.  It checks the status, the count of remaining sectors and the
;  buffer pointers.  Another sectors is started if and only if there was no
;  error on the last one, there are more sectors to do and there is either
;  data available to write or space available for data to be read into.

FloppyIntr:
	push	psw		;  Save the Regs
	push	d
	push	h
	ei			;  ok for other processes (RS232) to interrupt
				;  this one.
	in	FDCStatusReg	;  Get external status register.
	sta	DiskStatusHi	;  save it
	mov	E,a		;  Save for DMA test below
	in	FDCStatus	;  Read internal status
	mov	D,a		;  prepare to test disk status
	lda	DkErrorMask	;  get proper error mask for this operation
	ana	D		;  look for errors in that operation
	sta	DiskStatus	;  Store status from chip
	jnz	StopDiskService	;  if error, quit now
;  No disk error, check DMA completion, and clear the disk DMA channel.
;  If both DMA indicators agree the chip completed, no errors.  If they
;  disagree, we jump to ErrorReport showing which one failed.  If both
;  say DMA didn't complete, we post LostData and return.
	lxi	h,0		;  initialize offset into DMA error table
;  Check that DMA terminated.
	mov	a,E		;  Get external disk status
	ani	FDCEndCountMask
	jnz	GoodDmaEndCount1
	mvi	l,4
GoodDmaEndCount1:
	mvi	E,FloppyChannelMask
	call	ReadDMACompletion	;  Get completion
	jnz	EndDmaCheck	;  nz => channel completed, look at both
				;  indicators
	mvi	a,8		;  else set the error flag
	ora	l
	mov	l,a
EndDmaCheck:
	lxi	d,DmaCheckTable	;  load base of instruction table used to
				;  check the DMA completion
	dad	d		;  calculate the address of the code used to
				;  deal with the DMA completion or lack of it
	pchl			;  go deal with the DMA
DmaCheckTable:
	jmp	SectorCmdDone	;  Both DMAs ok
	nop			;  each table entry is 4 bytes
	jmp	NoDmaEndCount1	;  only the External DMA End Count failed
	nop
	jmp	NoDmaEndCount2	;  only internal DMA End Count failed
	nop
; both DMA endcounts agree the DMA did not finish.  Probably this means the
; sector length was wrong, so say LostData and quit
	mvi	a,FDCLostData	;  set the Lost Data bit
	sta	DiskStatus
	jmp	StopDiskService	;  stop the Transfer now
;  Only the External DMA end count indicator failed
NoDmaEndCount1:
	lxi	h,ErrorNoDmaEndCount1	;  ERROR:  External Dma End Count not set
	jmp	ErrorReport	;  Loop forever
;  Only the Internal DMA end count indicator failed
NoDmaEndCount2:
	lxi	h,ErrorNoDmaEndCount2	;  ERROR:  Internal Dma End Count
				;  not set
	jmp	ErrorReport	;  Loop forever
;  Decrement the Disk count for the operation that just
;  completed successfully
SectorCmdDone:
	lda	Sector		;  increment the Sector num so the CP
	inr	a		;  routine will know where to retry
	sta	Sector
	lda	FloppyIOCB+IocbSectorCnt	;  get the num of sectors left
	dcr	a		;  update it
	sta	FloppyIOCB+IocbSectorCnt
;  Update the IOP buffer address. Note this must be done even if that
;  was the last sector to allow DoCPBufService to transfer all sectors.
	lhld	DiskBufPtr
	call	IncrIOPPtr	;  Point to the next buffer location
	shld	DiskBufPtr	;  Update mem pointer
	xchg			;  has the disk caught up to the CP transfer?
	lhld	CPBufPtr	;  Compare the two ring buf ptrs
	call	HLcmpDE
	jz	StopDiskService	;  yes, can't do any more transfers
	lda	FloppyIOCB+IocbSectorCnt	;  get the num of sectors left
	ora	a		;  is it zero?
	jz	StopDiskService	;  quit if that was the last sector to
				;  be transferred
;  There were no errors, there are more sectors to do and there is buffer
;  space to use, do start another Sector
	call	DoSectorCmd
	jmp	FinDiskService	;  and finish up
;  There is some reason to stop now.  Disable the interrupts and stop
StopDiskService:
	xra	a		;  turn off DiskActive
	sta	DiskActive
	call	Disable75	;  turn off the Floppy interrupts
	mvi	a,FloppyChannelMask	;  turn off the DMA, just to make sure
	call	ClearDmaChannel
FinDiskService:
	mvi	a,0FFH		;  Tell DoTransfer something has happened
	sta	DiskSectorDone
	pop	h		;  restore the registers
	pop	d
	pop	psw
	ret			;  from interrupt

;  Subroutine:  UpdateCylinder.
;  After a type 1 command has executed check the Track register in the 1797.
;  Check if Track register matches DCylinder, and update Cylinder if so.
;  On entry:
;    DCylinder = Desired Cylinder 

UpdateCylinder:
	in	FDCTrack	;  Check track register
	lxi	h,DCylinder	;  Pointer to desired cylinder
	cmp	m
	jz	DoUpdateCylinder	;  Check if correct
CommandTrackError:
        pop	h		; balance return on stack
	lxi	h,ErrorCommandTrackError	;  ERROR:  Track register is not correct
        call	MakeBadIOCBResult
	jmp	FinishCommand

;  Track register is correct. Update Cylinder location.
DoUpdateCylinder:
	sta	Cylinder
	ret


;  Subroutine:  DoSeek.
;  Issue the Seek command.
;  Assumes FDC Track register is up-to-date.
;  Check validity of track number (**debugging only).
;  Load Data register, and issue command.
;  On entry:
;    D = Desired Track 
;    E = Command

DoSeek:
	mov	a,d		;  Check if desired Track OK
	cpi	77
	jc	SeekTrackOK	;  c =>  Cylinder number OK
TrackToBig:
	pop	h		; make stack balance
	lxi	h,ErrorTrackToBig		;  ERROR:  Track number is too large.
	call	MakeBadIOCBResult
	
	jmp	FinishCommand	;  return bad status

;  Note there is no need to check to see if the track number is negative.  In
;  two's complement negative numbers are greater than 77 so would be caught in the
;  test above.
;  Track number in A is OK.
SeekTrackOK:
	out	FDCData	;  Set desired track in Data Register
;  Check destination cylinder.
;  If greater than PreCompStart then set EnPreComp, else clear EnPreComp.
	lda	FDCStateVal	;  Check density in FDCStateVal
	ani	DDenMask	;  Mask out Double density mask
	jz	SetNoPreComp	;  z => single density
;  Double density.  Check if PreComp needed.
	mvi	H,PreCompStart	;  Check if WritePrecomp should be changed
	mov	a,d		;  Desired cylinder to A
	cmp	H		;  A has desired cylinder, H has start of PreComp
	jc	SetNoPreComp	;  c => present cylinder < PreComp boundry so no precomp	
;  Enable write Precomp.
SetPreComp:
	mvi	a,FDCEnPreCompMask
	lxi	h,FDCStateVal		;  Point to FDCStateVal
	ora	m			;  Set the bit
IssueSeek:
	mov	m,a 			;  Store in FDCStateVal
	out	FDCState		;  Store in FDCState
	mov	a,e		;  Get Command
	out	FDCCommand
	ret

;  Set write precomp.
SetNoPreComp:
	mvi	a,nFDCEnPreCompMask	;  Clear write precomp. bit
	lxi	h,FDCStateVal		;  Point to FDCStateVal
	ana	m			;  Clear the bit
	jmp	IssueSeek



;  Subroutine:  DoSectorCmd.
;  Implements Read commands: Read Sector, Read Address.
;  Implements Write commands: Write Sector, Write Deleted Sector.
;  Assumes Head at track and Track Register up to date.
;  On entry:
;    Sector = Desired sector 
;    SectorCmd = Command
;  Dma Control Block is set up in DiskDmaCB.
;  Call subroutine at DoReadAddressTrack, for Read Address or Read Track.

DoSectorCmd:
	lda	Sector		;  Set up sector for FDC dhip
	out	FDCSector	;  Set desired sector in FDCSector Register
ContSectorCmd:
;  Program channel 0 of Dma controller for memory writes.
	LDA	DiskDmaFunction
	lxi	h,DiskDmaCB	;  Point to DmaCB
	call	StartFloppyChannel	;  Enable the channel

;  Dma channel is now programmed and enabled.
GoodDmaChannel:
	lda	SectorCmd	;  Get FDC command
	out	FDCCommand	;  Issue command
;  Turn on the Floppy Interrupts so FloppyIntr can handle the end of the
;  transfer.  Clear 7.5 FF.
EnFloppyInterrupt:
	mvi	a,ResetRst75+Rst75DisableMsk	;  RST 7.5, clear 7.5 FF
	call	EnableRST	;  Enable the Floppy interrupt but let the
				;  calling routine actually enable interrupts
	mvi	A,0FFH		;  turn on DiskActive for DoTransfer
	sta	DiskActive
	ret


;  Subroutine:   Delay.
;  Floppy Delay subroutine.
;  Call at Delay (no A register setup) for delay of ~15.6 us.
;  Call at Delay9 (no A register setup) for delay of ~9.5 us.
;  Call at DelayV for variable wait:
;    If (A) = k, then Delay = 32+ 14k cycles
;			   =  10.67 + 4.67k usec at 3MHz CPU
;		    k=9 => > 50 us.
Delay:
	mvi	a,1
DelayV:
	dcr	a
	jnz	DelayV
Delay9:
	ret

SaveFloppyTaskResumeAddressAndYield:
	SHLD	FloppyTaskResumeAddress
FloppyTaskYield:
	DS	0
{Pass control to the next task specified in Domino.cfg}

	END	FloppyTask