		SUBTITLE	"<Floppy Disk Driver>"
		newpage
		scope
			
;		ORG		driver.FD

PREND		EQU		$-1

; FDCDVR/ASM - LS-DOS 6.2

; HL=> HL points to buffer of READ / WRITE.
; D=> track desired.
; E=> sector desired.
; C=> C contains drive #.
; B=> Function in register B.

FDCDVR		JR	FDCBGN				;Branch to entry code
		DW	FDCEND				;Last byte used
		DB	3,"$FD"				;Module name

PADDRESS	db	0,0,0				; This driver calculates physical beginning of DISK start.

ramdrv$		blkb	16,0				; Filled in @IPL with physical pointer to boot volume.

secreq$		db	0

fdc_no_sum	dw	0				; Hold sum of physical record w/o offset.
net_volume	db	0				; Number of network volume, filled in by binding.
net_cmd		db	"=00000",LF.asc,ETX.asc
chk_sum		db	0,0,0,0
rd_retries	db	0
wr_retries	db	0


;       Disk Driver Entry Point
; ____________________________________

FDCBGN		di
		ld	(IY+5),d			; Update current track.

		ld	a,c				; Get drive requested code.
		ld	(LDRV$),a			; Store in current logical drive.

		ld	a,e				; Get sector requested.  <<<-------
		ld	(secreq$),a			; Store for later.
	
		ld	a,(IY+4)			; Get drive select code entry.
		and	0Fh 				; Strip out drive entry code.
		ld	(PDRV$),a			; Store in Physical Drive code.

		LD	A,B				; P/u primitive request.
		push	hl				; Per Soltoff we save any registers we use.			
		push	bc				; Per Soltoff we save any registers we use.
		push	hl				; Per Soltoff we save any registers we use.
			
		ld	b,0	
		sla	c				; drive * 2.

		ld	hl,ramdrv$			; Get address of drive vectors. 
		add	hl,bc				; drive vector = vector + drive
		ld	hl,(hl)				; Get vector.
		ld	(RAMDRV$),hl			; Store here so driver can use on this call.

		LD	HL,funtable$			; Base address of function table.
		sla	a				; Function=function*2.
		ld	b,0
		ld	c,a				; BC contains table offset.
		add	hl,bc				; Pointer=base+offset.
		ld	bc,(hl)				; Get vector.
		ld	(vecvec$),bc			; Store in JP instruction below.

		POP	HL		
		pop	bc				; Recovered from save on entry to driver.
		push	bc				; Save to recover on exit of driver.		
			
		jp	$			
vecvec$		equ	$-2

funtable$	equ	$
		dw	TSTBSY				; *0 NOP.
		dw	SLCT				; *1 Drive select.
		dw	TSTBSY				; *2 initialize controller.
		dw	TSTBSY				; *3 Reset controller?
		dw	RESTOR				; *4 Restore drive to 0
		dw	STPIN				; *5 Step in
		dw	SEEKTRK				; *6 Jump on track seek.
		dw	TSTBSY				; *7 TEST BUSY.
		dw	notsupported			; *8 Read header.
		dw	RDIN				; *9 Read sector.
		dw	VERIFY				; *10 Verify sector readable, verify routine.
		dw	notsupported			; *11 Read cylinder?
		dw	TSTBSY				; *12 Format volume
		dw	DOWRIT				; *13 Write a sector.
		dw	DOWRIT				; *14 Write system directory.
		dw	notsupported			; *15 Write cylinder.
	
notsupported	jr	$


VERIFY		ld	a,d				; Get track we just read.
		cp	(IY+9)				; Compare directory track to track we just read.
		jr	nz,TSTBSY			; Not a directory read so clean up and exit no error.
		ld	a,6				; This was directory cylinder request so set error=6
		or	a				; NZ for return.
		pop	bc				; Recover saved register.		
		pop	hl				; Recovered from save on entry to driver.
		ret

;           FUNCTION 7 TEST BUSY.
; > Solid state drive we are never busy. <
; > Restore registers and exit with no error. <

TSTBSY		pop	bc				; Recovered from save on entry to driver.
		POP	HL				; Per Soltoff we restore on exit any registers we used.
		XOR	A				; Clear A & reset Z flag.
		ei
		RET					; We are solid state drive, were always ready.	

SEEKTRK		LD	(IY+5),D			; Update current cylinder
		JR	TSTBSY

STPIN		DEC	(iy+5)				; FUNCTION 5 STEP-IN.
		jr	TSTBSY
			
RESTOR		LD	(IY+5),0			; FUNCTION 4. RESTORE Set track to 0
		jr	TSTBSY
			
ICTRL		jr	TSTBSY				; Function 2, initialize controller.
	
SLCT		ld	a,c				; Get drive number.
		ld	(LDRV$),a			; Store in logical drive number.
		pop	bc				; Recovered from save on entry to driver.
		POP	HL				; FUNCTION 7 TEST BUSY.
		XOR	A
		ld	A,00000010b			; Bit one set meaning index pulse.
		ei
		RET					; We are solid state drive, were always ready.
								
;	I/O request handler read routine.

RDIN		push	de				; D > track. E > sector, save for later.
		PUSH	HL				; Save 16b pointer to buffer.

		call	calcsec				; Calculate address of sector to read.

		ld	a,(LDRV$)			; Is this i/o request a network drive?
		ld	b,a
		cp	6				; Drive 6 is reserved for network.
		IF_NE_GOTO not_net_rd 			; This is not network drive, bypass net_read.

		ld	a,(net_volume)			; Which volume if any is network?
		cp	a,b				; Which volume if any is network?
		IF_NE_GOTO not_net_rd 			; This is not network drive, bypass net read.

; Read sector from network connection.
; READ > NETWORK SECTOR HERE <

		xor	a
		ld	(rd_retries),a			; Start retry counter off at 0.

		LD	a,'<'				; Symbol used for READ command.
read_retry	ld	(net_cmd),a			; Stuff direction in command buffer.

		HEXDEC	(fdc_no_sum),net_cmd+1		; Convert 16 bit address to ascii in network command.
		ZEROS_RPL_SPACE	net_cmd+1		; Convert leading spaces in buffer to 0's.

		PUMP	DEVICE.serialnet,net_cmd 	; Send <XXXXX LF.asc packet.

		pop	hl				; Recover destination buffer address.
		push	hl				; Save again for checksum routine.

		UART1.get.bytes	0,hl,fdc_rd_err	 	; Get from server 256 byte sector requested.
		UART1.get.bytes	4,chk_sum,fdc_rd_err	; Get from server 4 byte checksum.

		pop	hl				; Get pointer to buffer.
		GOSUB	calc_cksum
		ld	b,a
		ld	a,(chk_sum+3)
		cp	b
		IF_EQ_GOTO rd_finish

; Here we have a error on read.
; Bump retry counter.
; Set server command to retry.
; If retries does not roll over to 0 go around again.

		ld	hl,rd_retries
		inc	(hl)				; Bump counter by 1.
		IF_EQ_GOTO	fdc_rd_err		; If counter rolls over to 0 we have 256 retries, abort i/o.
		ld	a,'\'				; Retry signal to server.
		GOTO	read_retry


rd_finish	POP	de				; Restore DE to track/sector.
		ld	a,d				; Get track we just read.
		cp	(IY+9)				; Compare directory track to track we just read.
		jp	nz,TSTBSY			; Not system sector? Return no error return & carry on.
		ld	a,6				; If we read system sector then set error to 6.
		or	a				; Flag to non-zero.			
		pop	bc				; Recovered from save on entry to driver.		
		POP	HL				; Per Soltoff we restore on exit any registers we used.
		ei
		RETURN					; Return with NZ flag set indicating error.

; Read sector from RAM drive.
; > READ MEMDISK SECTOR HERE <.

not_net_rd
		LD.L	hl,(PADDRESS)			; HL now contains 24b address pointing to sector in RAM drive.
		xor	a
		LD	(PADDRESS+2),a			; A contains 0 to zero out upper 8 bits of 24b address.
		POP	de				; Point DE to destination buffer.
		LD	(PADDRESS),de			; A-=0 from above code.
		LD.L	DE,(PADDRESS)			; DE now contains 24b destination pointer.
		LD.L	bc,100h				; Move one SECtor or 256B.		
		LDIR.L					; Move sector from RAM DRIVE to BUFFER.
		GOTO	rd_finish			; Received sector, continue to finish.

DOWRIT	; I/O request handler WRITE routine, write sector to RAM DRIVE.

		bit	7,(iy+3)			; Test WP flag in DCT.
		JR	Z,wp_ok				; If disk WP on then we skip following code.		
		pop	bc				; Recovered from save on entry to driver.
		POP	HL				; Per Soltoff we restore on exit any registers we used.
		LD	A,15				; Error disk WP. Return with NZ & error code 15.
		ret					; Return with NZ & disk WP error in A.

wp_ok		push	de				; D - contains track. E contains sector.						
		PUSH	HL				; Save 16b pointer to buffer.
			
		call	calcsec				; Calculate address of sector to write.

		ld	a,(LDRV$)			; Is this i/o request a network drive?
		ld	b,a
		cp	6				; Drive 6 is reserved for network.
		IF_NE_GOTO fdc_not_net_wr 		; This is not network drive, bypass net_read.

		ld	a,(net_volume)			; Which volume if any is network?
		cp	a,b				; Which volume if any is network?
		IF_NE_GOTO fdc_not_net_wr 		; This is not network drive, bypass net read.

; > WRITE sector to NETWORK CONNECTION HERE <

		HEXDEC	(fdc_no_sum),net_cmd+1		; Convert 16 bit address to ascii in network command.

		ZEROS_RPL_SPACE	net_cmd+1		; Convert leading spaces in buffer to 0's.

		xor	a
		ld	(wr_retries),a			; Start retry counter off at 0.

		LD	a,'>'
wr_retry	ld	(net_cmd),a			; Stuff direction in command buffer.

		PUMP	DEVICE.serialnet,net_cmd 	; Send @<XXXXX LF.asc packet.

		pop	hl				; Recover buffer address for read.
		push	hl				; Leaving one copy on stack.

		UART1.put.bytes	0,hl,fdc_wr_err 	; Write to server 256 byte sector.
		
		pop	hl				; Get buffer address.
		push	hl				; Leaving one copy on stack.
		GOSUB	calc_cksum			; Calculate checksum for buffer.
		ld	(chk_sum+3),a			; Store these 8 bits in little endian of 32 bit python int.
		push	af				; Save checksum for now.

		UART1.put.bytes	4,chk_sum,fdc_wr_err1	; Send server 4 byte int containing checksum.

		UART1.get.bytes	4,chk_sum,fdc_wr_err1	; Get 4 byte checksum returning from server.

		pop	bc				; Recover original checksum, B will have old A.
		ld	a,(chk_sum+3)			; Compare round trip checksum in buffer to original.
		cp	b
		IF_EQ_GOTO	net_wr_done		; Another job well done!

		ld	hl,wr_retries
		inc	(hl)				; Ooops bump counter +1.
		IF_EQ_GOTO	fdc_wr_err		; If rolling over to 0 it means 256 retries, abort this write.
		ld	a,'/'				; Signal server this is a write retry using retry char.
		GOTO	wr_retry
	
net_wr_done	pop	hl				; Discard buffer address.
		POP	de				; Restore DE to track/sector.
		jp	TSTBSY

fdc_not_net_wr	; > WRITE sector to MEMDISK HERE <

		LD.L	de,(PADDRESS)			; de now contains 24b address pointing to sector in RAM drive.

		POP	hl				; Recover pointer to source.
		LD	(PADDRESS),hl			; Place 16 bit destination address in buffer.
		xor	a
		LD	(PADDRESS+2),a			; A-=0 from above code. Zero out upper 8 bits of 24b address.

		LD.L	hl,(PADDRESS)			; HL now contains 24b source pointer.
		LD.Lil	BC,100h				; Move one SECtor or 256B.
		LDIR.L					; Move sector from BUFFER to RAM DRIVE.

		POP	de				; Restore DE to track/sector.
		jp	TSTBSY

; IY points do DCTx$
; D - contains track.
; E - contains sector.

; Calculate physical address based on track & sector.
; Convert TRACK / SECTOR to physical RAM drive address.


; 1st determine if sector on side 0 or side 1.

calcsec		LD	A,(IY+7)			; p/u alloc data
		AND	1FH				; Get highest # sector
		SUB	E				; Form req sector minus
		RES	4,(IY+3)			; init side select to 0
		JR	NC,FRCSID0			; NC if sector on side 0
		BIT	5,(IY+4)			; If not 2-sided media
		JR	Z,FRCSID0			; don't set side 1
		SET	4,(IY+3)			; Set side 1

; 2nd calculate sectors per cylinder.
; D-contains track, E contains sector.

FRCSID0		LD	hl,DE				; sector in lower 8 bits of HL.
		ld	h,0				; Zero upper 8 bits. HL now contains sector.
		LD	A,(IY+7)			; P/U sectors per track.
		AND	1Fh				; Mask out all but # SEC per TRK.
		INC	A				; Adjust to correct pointer.
		bit	5,(IY+4)			; Test if double sided drive?
		jr	z,stp23				; NO, skip doubling sectors per track.
		sla	a				; A = A * 2, double sided - double sectors per trk.

; 3rd perfom following math leaving results in HL.
; sector requested + (requested cylinder number * sectors per track).

stp23		LD	E,A				; Move # of SEC per CYL to E.
		MLT	DE				; we then multiply (# CYL * SEC per CYL) 16b result in DE.
		add	hl,de				; Add in sector number we saved before.

		ld	(fdc_no_sum),hl	; Save sum w/o offset for net driver.

; 4th, using upper 16 bits of RAM drive base address, add in offset using results of above calculation.
; Leave results in HL.
; RAMDRV$ below was populated at entry to driver by looking up using drive #.

		ld	de,$
RAMDRV$		equ	$-2				; Upper 16 bits of base RAM drive address.
		add	hl,de				; Sum them together with offset to data.

; Lastly, we stuff physical address of sector in PADDRESS.

		XOR	A				; A = 0 for below code.
		LD	(PADDRESS),a
		LD	(PADDRESS+1),hl
		ret

fdc_rd_err	pop	hl				; Discard return address.
		pop	hl				; Discard return address.
		ld	a,03h				; Data lost on read.
		or	a				; Make NZ, error.
		ei
		RETURN
	
fdc_wr_err1	pop	hl				; Discard extra data.
fdc_wr_err	pop	hl				; Buffer address.
		pop	de				; This vector used for cleanup of 1 extra item on stack.
		pop	bc				; Recovered from save on entry to driver.
		POP	HL				; Per Soltoff we restore on exit any registers we used.
		ld	a,14				; Write fault on disk drive.
		or	a				; Make NZ, error
		ei
		RETURN

calc_cksum	ld	b,0
		xor	a
		add	(hl)				; A= A + (HL)
		inc	hl
		LOOP	calc_cksum+3
		RETURN

FDCEND:		EQU	$-1