; FDC (floppy disk controller), version 0.13
;

;
; Based on FDCDEMO by John M. B. Wilson, <wilsonj@alumni.rpi.edu>, <wilson@tats.wizvax.net>
;
; -- Improved by W.H Scholten 8/9/17 april 1996 --
;	Bugs removed:	format now works with minsector<>1
;			drive is reinitialized after speed settings (eg DDinDD)
;	New commands:	READ0/1, WRITE0/1 to read/write a single side of DS disk
;				(useful for BBC)
;			STOP/CONT to control the action of read/verify errors.
; -- Improved even more by WHS 10/13 nov 1998 --
;	New commands:	bbc80, bbc640, bbc800 to enter all settings at once
;			for DFS 80 track, ADFS 640K, ADFS 800K (archimedes)
;

;
; The latter part of this file contains a set of public domain FDC driver
; routines.
;
; The first part of this program contains a simple command line decoder which
; allows the user to perform simple disk operations; it is intended to provide
; a demonstration of how to call the FDC routines, and can also be useful for
; examining foreign disks, obtaining disk image files etc.
;
; 03/22/94	JMBW	Created.
; 09/04/95	JMBW	Added double-stepping flag for using 48tpi disks in
;			96tpi drives.
; 09/06/95	JMBW	Added simple CLI to demonstrate FDC code.
; 09/12/95	JMBW	Head map (for funny headers on BBC etc.).
;
;-
	.radix 8		;of course!
;
lf=	12
cr=	15
;
bufl=	1024d			;length of sector buffer
;
; Define a keyword record for TBLUK, given a string containing exactly one
; hyphen to show the minimum allowable abbreviation.
;	db	length to match
;	db	total length
;	db	'KEYWORD'	;the keyword itself
;	dw	ADDR		;address returned from TBLUK on match
kw	macro	text,addr
kh=	0
ki=	0
	irpc	kc,text
ki=	ki+1
ifidn <kc>,<->
kh=	ki
endif
	endm ;; irpc
ife kh
	.err
	%out	No hyphen in string: &text
	exitm
endif ;; ife kh
	db	kh-1,ki-1
	irpc	kc,text
ifdif <kc>,<->
	db	'&kc'
endif
	endm ;; irpc
	dw	addr
	endm
;
; Print an in-line error message
error	macro	text
	local	a,b
	
	call	pdone	

	call	error1
a	db	b,&text
b=	$-a-1
	endm
;
; Cram in-line text into output buffer
cram	macro	text
	local	a,b
	call	cram1
a	db	b,&text
b=	$-a-1
	endm
;

.MODEL TINY
.CODE
	org	100h		;.COM file
;
start: cld			;DF=0
	; make sure we have enough RAM (SP is normally FFFE on entry, but...)
	cmp	sp,offset pdl	;make sure we got all the memory we need
	jae	gotmem	 ;yes
	mov	dx,offset nomem ;pt at msg
	mov	cx,lnomem	;length
	mov	bx,0002h	;stderr
	mov	ah,40h	 ;func=write
	int	21h
	mov	ax,4C01h	;func=punt
	int	21h
gotmem: ; make sure disk sector buffer doesn't span 64KB boundary
	mov	ax,ds		;get seg addr
	mov	cl,4		;bit count
	sal	ax,cl		;left 4 (lose high bits)
	add	ax,offset buf1 ;pt at buffer
	add	ax,bufl-1	;see if it spans (OK to stop just short)
	jnc	nospan	 ;no, skip
	mov	ds:buf,offset buf2 ;it does, well this one won't
	mov	ds:kbbuf,offset buf1 ;use BUF1 for keyboard buffer
nospan: ; trap ^Cs
	mov	dx,offset mloop ;set ^C vector to come back to prompt
	mov	ax,2523h	;func=set INT 23h vector
	int	21h
;	mov	al,ds:drive	;get drive #

	call	bbc		;set initial values (for BBC) [was ini..]
				; [but ini's don't set all parameters ]

	mov	dx,offset banner ;banner
	mov	ah,09h	 ;func=print
	int	21h		;say hello
;+
;
; Main command loop.
;
;-
mloop: ; re-init basic stuff in case we got here by ^C or ERROR
	mov	ax,cs		;copy cs
	mov	ds,ax		;to ds and es
	mov	es,ax
	cli			;ints off
	mov	sp,offset pdl	;;reinit stack (in case of ^C or ERROR)
	sti			;;(ints back on after next)
	mov	ss,ax
	cld			;DF=0
	call	close		;close any open file
	; prompt
	mov	dx,offset prompt ;command prompt
	mov	ah,09h	 ;func=print
	int	21h
	; read a command line
	mov	bx,ds:kbbuf	;get ptr to keyboard buf
	mov	dx,bx		;copy
	mov	byte ptr [bx],80d ;set length (must be <= BUFL)
	push	bx		;save for a while
	mov	ah,0Ah	 ;func=get line
	int	21h
	mov	dl,lf		;echo lf (DOS didn't)
	mov	ah,02h	 ;func=CONOUT
	int	21h
	pop	si		;restore KB buf ptr
	inc	si		;point at length actually read
	lodsb			;get it
	cbw			;ah=0 (can't be >80. so no sign)
	mov	cx,ax		;copy
mlp1:	; parse a keyword
	call	getw		;get keyword
	jc	mloop		;none, reprompt
	; look up and dispatch if valid command
	mov	ax,offset cmds ;pt at command list
	call	tbluk		;look it up
	jc	synerr	 ;failed
	call	ax		;execute command (update si, cx)
	jmp	short mlp1	;get next command on line
synerr: error	'?Syntax error'
;
cmds	label	byte
	kw	<1.2-MB>,mb12	;set 1.2MB parms
	kw	<1.4-4MB>,mb144 ;set 1.44MB parms
	kw	<18-0KB>,kb180	;set 180KB parms
	kw	<2-.88MB>,mb288	;set 2.88MB parms (doesn't work!)
	kw	<3-60KB>,kb360	;set 360KB parms
	kw	<7-20KB>,kb720	;set 720KB parms
	kw	<BB-C>,bbc	; set BBC parms, also sets HEADS=2, HEAD1ALIAS 0 (added by whs)
	kw	<BBC80->,bbc80	; set BBC parms for 80 track 5 1/4 drive.
	kw	<BBC640->,bbc640 ; set parms to read 640K ADFS 3.5 disks. 
	kw	<BBC800->,bbc800 ; set parms to read 800K ADFS 3.5 disks. 

	kw	<BY-TES>,setbyt ;set number of bytes per sector
	kw	<CONT->,cont	;continue after a read/verify error
	kw	<CY-LINDERS>,setcyl ;set number of cylinders
	kw	<DD->,setdd	;set double density mode
	kw	<DDIND-D>,ddindd ;set speed register for 360KB drive
	kw	<DDINH-D>,ddinhd ;set speed reg for 360KB disk in 1.2MB drive
	kw	<DO-UBLESTEP>,dblstp ;double-step seeks
	kw	<DS->,setds	;set double sided
	kw	<E-D>,ed	;set speed register for 2.88MB drive
	kw	<FD0->,fd0	;unix style floppy selection fd0
	kw	<FD1->,fd1	;unix style floppy selection fd1
	kw	<FD2->,fd2	;unix style floppy selection fd2
	kw	<FD3->,fd3	;unix style floppy selection fd3
	kw	<FI-LL>,fill	;set fill byte for formatting
	kw	<FORMAT->,format ;format disk (both sides, if double sided)
	kw	<FORMAT0->,format0	;format side 0
	kw	<FORMAT1->,format1	;format side 1
	kw	<GA-P3>,gap	;set gap 3 length
	kw	<GAP3F-ORMAT>,gapfmt ;set gap 3 length for format
	kw	<GE-T>,get	;get a sector from disk
	kw	<HD->,hd	;set speed register for 1.2MB or 1.44MB drive
	kw	<HEAD0-ALIAS>,h0als ;set alias for head 0
	kw	<HEAD1-ALIAS>,h1als ;set alias for head 1
;	kw	<H-EADS>,sethd ;set number of heads
	kw	<L-OAD>,load	;load sector buffer
	kw	<M-INSECTOR>,minsec ;set minimum sector # (usually 1)
	kw	<PUT->,put	;put a sector on disk (spelled out for safety)
	kw	<Q-UIT>,quit	;quit to DOS
	kw	<R-EAD>,read	;read entire disk to file (INTERLEAVED DISKIMAGE if sides=2, WHS)
	kw	<READ0->,read0	;read side 0 (Added by WHS)
	kw	<READ1->,read1	;read side 1 (Added by WHS)
	kw	<RX01->,rx01	;set RX01 parms
	kw	<RX02->,rx02	;set RX02 parms
	kw	<RX5-0>,rx50	;set RX50 parms
	kw	<SA-VE>,save	;save sector buffer in file
	kw	<SD->,setsd	;set single density mode
	kw	<SE-CTORS>,setsec ;number of sectors
	kw	<SH-OW>,show	;show sector buffer
	kw	<SI-NGLESTEP>,sngstp ;single-step seeks
	kw	<SS->,setss	;set single sided mode
	kw	<S-TATUS>,status ;display settings
	kw	<STOP->,stop	;stop if a read/verify error occurs
	kw	<W-RITE>,write	;write file to entire disk
	kw	<WRITE0->,write0	;write files to SIDE 0 (Added by WHS)
	kw	<WRITE1->,write1	;write files to SIDE 1 (Added by WHS)

	db	0
;+
;
; All these command routines are entered with:
;
; ds:si pointer to next byte in command line
; cx	# bytes left in command line
;
; On return, they should be preserved (or updated if the command parsed
; argument(s)) so that more commands can be read off of the same line.
;
;-
quit:	int	20h ;exit program
;

kb180: ; set 180KB disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	ini360	 ;init
	mov	byte ptr ds:numcyl,40d ;# cyls
;	mov	byte ptr ds:numhd,1 ;SS
	mov	byte ptr ds:sides,1 ;(WHS)

;	mov	byte ptr ds:fdeot,9d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;
kb360: ; set 360KB disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	ini360	 ;init
	mov	byte ptr ds:numcyl,40d ;# cyls
;	mov	byte ptr ds:numhd,2 ;DS
	mov	byte ptr ds:sides,2 ;(WHS)

;	mov	byte ptr ds:fdeot,9d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;
kb720: ; set 720KB disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	ini360	 ;init
	mov	byte ptr ds:numcyl,80d ;# cyls
;	mov	byte ptr ds:numhd,2 ;DS
	mov	byte ptr ds:sides,2 ;(WHS)

;	mov	byte ptr ds:fdeot,9d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	mov	byte ptr ds:fddbl,0 ;don't double-step
	pop	cx		;restore
	pop	si
	ret
;

mb12:	; set 1.2MB disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	ini12		;init
	mov	byte ptr ds:numcyl,80d ;# cyls
;	mov	byte ptr ds:numhd,2 ;DS
	mov	byte ptr ds:sides,2 ;(WHS)

;	mov	byte ptr ds:fdeot,15d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;
mb144: ; set 1.44MB disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	ini144	 ;init
	mov	byte ptr ds:numcyl,80d ;# cyls
;	mov	byte ptr ds:numhd,2 ;DS
	mov	byte ptr ds:sides,2 ;(WHS)

;	mov	byte ptr ds:fdeot,18d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;

mb288: ; set 2.88MB disk parms (not really!!!)
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	ini288	 ;init
	mov	byte ptr ds:numcyl,80d ;# cyls
;	mov	byte ptr ds:numhd,2 ;DS
	mov	byte ptr ds:sides,2 ;(WHS)

;	mov	byte ptr ds:fdeot,36d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	error	'?2.88MB support is not finished'
;;	ret
;

rx01:	; set RX01 disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	inidx		;init
	mov	byte ptr ds:numcyl,77d ;# cyls
;	mov	byte ptr ds:numhd,1 ;SS
	mov	byte ptr ds:sides,1 ;(WHS)

;	mov	byte ptr ds:fdeot,26d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;

rx02:	; set RX02 disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	inidy		;init
	mov	byte ptr ds:numcyl,77d ;# cyls
;	mov	byte ptr ds:numhd,1 ;SS
	mov	byte ptr ds:sides,1 ;(WHS)

;	mov	byte ptr ds:fdeot,26d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;

rx50:	; set RX50 disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	inidz		;init
	mov	byte ptr ds:numcyl,80d ;# cyls
;	mov	byte ptr ds:numhd,1 ;SS
	mov	byte ptr ds:sides,1 ;(WHS)

;	mov	byte ptr ds:fdeot,10d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;

bbc:	; set BBC disk parms
	mov	al,ds:drive	;get drive #
	push	si		;save
	push	cx
	call	inibbc	 	;init
	mov	byte ptr ds:numcyl,40d ;# cyls
;	mov	byte ptr ds:numhd,2 ;SS changed to DS (WHS)
	mov	byte ptr ds:sides,2 ;SS changed to DS (WHS)

;	mov	byte ptr ds:fdeot,9d ;max sector # [10 SECT, 0-9] STAAT AL IN inibbc
	mov	byte ptr ds:secbas,0 ;minimum sector=0
	pop	cx		;restore
	pop	si
	ret
;

bbc80:	; set BBC disk parms for 80 track
	call	bbc
	mov	al, 80d
	mov	ds:numcyl,al	; set 80 track
	ret
;
bbc640:	; set BBC disk parms for 640 K ADFS
	call	bbc
	mov	al, 80d
	mov	ds:numcyl,al	; set 80 track

	mov	bx, 1d		; head1
	mov	ax, 1d		; head alias
	mov	ds:fdhdm[bx],al ; set head1 alias =1

;	mov	ax, 256d
;	mov	dx, 0d
;	call	setbyt_direct
;not worth it, simply do:
	mov	ax, 1d
	mov	ds:fdlen,al	; set sector length
	mov	ax, 16d		; sectors
	add	al,ds:secbas	;add sector base
	dec	ax		;-1 to get max sector #
	mov	ds:fdeot,al	;OK, save
	call	setdd		; set double density
	ret
;
bbc800:	; set BBC disk parms for 800 K ADFS (archimedes)
	call	bbc
	mov	al, 80d
	mov	ds:numcyl,al	; set 80 track
	mov	ax, 3d		; sector len = 1024
	mov	ds:fdlen,al	; set sector length
	mov	al, 1d
	call	minsec_direct
	mov	ax, 5d		; sectors
	add	al,ds:secbas	;add sector base
	dec	ax		;-1 to get max sector #
	mov	ds:fdeot,al	;OK, save
	call	setdd		; set double density
	ret
;

fd0:	; select drive 0
	xor	al,al		;drive=0
	jmp	short seldrv
;
fd1:	; select drive 1
	mov	al,1		;drive=1
	jmp	short seldrv
;
fd2:	; select drive 2
	mov	al,2		;drive=2
	jmp	short seldrv
;
fd3:	; select drive 3
	mov	al,3		;drive=3
	;jmp	short seldrv
;

seldrv:	mov	ds:drive,al	;save
	mov	ds:fddrv,al	;in both places
	push	si		;save
	push	cx
	call	fdini		;reinit
	pop	cx		;restore
	pop	si
	ret
;

setbyt: ; set sector length
	call	getn		;get a number
	test	dx,dx		;high word should be 0
	jnz	sbyt2		;invalid
setbyt_direct: ; dx must be zero, ax size.
	cmp	ax,128d		;128?
	je	sbyt1
	inc	dx		;assume 256
	cmp	ax,256d		;right?
	je	sbyt1
	inc	dx		;assume 512
	cmp	ax,512d		;right?
	je	sbyt1
	inc	dx		;assume 1024
	cmp	ax,1024d	;right?
	jne	sbyt2
sbyt1: mov	ds:fdlen,dl	;save
	ret
sbyt2: error	'?Invalid sector length'
;

setcyl: ; set number of cylinders
	call	getn		;get number
	test	dx,dx		;too huge?
	jnz	scyl1
	test	ah,ah
	jnz	scyl1
	test	al,al		;must be non-zero
	jz	scyl1
	mov	ds:numcyl,al	;save
	ret
scyl1: error	'?Invalid number of cylinders'
;

setsd: ; set single density mode
	mov	byte ptr ds:fdden,fm ;FM
	ret
;
setdd: ; set double density mode
	mov	byte ptr ds:fdden,mfm ;MFM
	ret
;

ddindd: ; set speed register for 360KB drive
	mov	byte ptr ds:fddcr,2 ;250 kHz, 300 RPM
	jmp	fdini			;necessary!! (WHS)
;	ret
;

ddinhd: ; set speed register for 360KB disk in 1.2MB drive
	mov	byte ptr ds:fddcr,1 ;300 kHz, 360 RPM
	jmp	fdini			;necessary!! (WHS)
;	ret
;

hd:	; set speed register for 1.2MB or 1.44MB drive
	mov	byte ptr ds:fddcr,0 ;500 kHz, 300/360 RPM
	jmp	fdini			;necessary!! (WHS)
;	ret
;

ed:	; set speed register for 2.88MB drive
	mov	byte ptr ds:fddcr,3 ;1 MHz, 300 RPM
	jmp	fdini			;necessary!! (WHS)
;	ret
;

sngstp: ; single-step seeks
	mov	byte ptr ds:fddbl,0 ;clear flag
	ret
;
dblstp: ; double-step seeks
	mov	byte ptr ds:fddbl,1 ;set flag
	ret
;

setss: ; set single sided mode
;	mov	byte ptr ds:numhd,1 ;# heads=1
	mov	byte ptr ds:sides,1
	ret
;

setds: ; set double sided mode
;	mov	byte ptr ds:numhd,2 ;# heads=2
	mov	byte ptr ds:sides,2
	ret
;

;HEADS NO LONGER USED AS COMMAND
;sethd: ; set number of heads
;	call	getn		;get number
;	test	dx,dx		;too big?
;	jnz	shd1
;	cmp	ax,2		;must be 1 or 2
;	ja	shd1
;	test	ax,ax
;	jz	shd1
;	mov	ds:numhd,al	;OK, save
;	ret
;shd1:	error	'?Invalid number of heads'
;

minsec: ; set minimum sector number
	call	getn		;get number
	test	dx,dx		;should be 0
	jnz	msec1
	test	ah,ah
	jnz	msec1
minsec_direct:
	mov	ah,ds:fdeot	;get existing EOT
	sub	ah,ds:secbas	;subtract base (=# sectors -1)
	add	ah,al		;add new base
	jc	msec1		;overflow
	mov	ds:secbas,al	;OK, save
	mov	ds:fdeot,ah
	ret
msec1: error	'?Invalid minimum sector number'
;

setsec: ; set number of sectors per track
	call	getn		;get number
	test	dx,dx		;too big?
	jnz	ssec2
	test	ah,ah		;must be <256.
	jnz	ssec2
	test	ax,ax		;must be non-zero too
	jz	ssec2
	add	al,ds:secbas	;add sector base
	je	ssec1		;hitting 256. exactly is OK
	jc	ssec2		;otherwise carry is bad
ssec1: dec	ax		;-1 to get max sector #
	mov	ds:fdeot,al	;OK, save
	ret
ssec2: error	'?Invalid number of sectors'
;

fill:	; get fill byte for format
	call	geth		;get number
	test	ah,ah		;make sure valid
	jnz	fill1
	mov	ds:fdfil,al	;save
	ret
fill1: error	'?Invalid fill byte'
;

gap:	; set gap 3 length
	call	getn		;get number
	test	dx,dx		;too big?
	jnz	gap1
	test	ah,ah
	jnz	gap1
	test	al,al		;>0 (right?)?
	jz	gap1
	mov	ds:fdgpl,al	;no, save
	ret
gap1:	error	'?Invalid gap 3 length'
;

gapfmt: ; set gap 3 length for format
	call	getn		;get number
	test	dx,dx		;too big?
	jnz	gap1
	test	ah,ah
	jnz	gap1
	test	al,al		;>0 (right?)?
	jz	gap1
	mov	ds:fdgpf,al	;no, save
	ret
;

h0als: ; set head 0 alias (i.e. "H" value used in headers on side 0)
	call	getn		;get number
	xor	bx,bx		;head #
h0als1: test	dx,dx		;too big?
	jnz	h0als2
	test	ah,ah
	jnz	h0als2
	mov	ds:fdhdm[bx],al ;save
	ret
h0als2: error	'?Invalid alias for head number'
;
h1als: ; set head 1 alias (i.e. "H" value used in headers on side 1)
	call	getn		;get number
	mov	bx,1		;head #
	jmp	short h0als1
;

stop:	mov	byte ptr ds:ignore_errors,0
	ret

cont:	mov	byte ptr ds:ignore_errors,1
	ret

;STATUS
status: ; display current settings
	push	si		;save
	push	cx
	mov	di,offset lbuf ;point at line buffer
	cram	' DRIVE fd'		
	mov	al,ds:drive	;get drive #
	add	al,'0'	 ;add base
	stosb
	cram	' '
	call	flush

	cram	' CYLINDERS '
	mov	al,ds:numcyl	;get # cylinders
	call	putb		;print
	cram	' HEADS '
	mov	al,ds:numhd	;get # cylinders
	call	putb		;print
	cram	' SECTORS '
	mov	al,ds:fdeot	;get # sectors
	sub	al,ds:secbas	;subtract base
	inc	ax		;+1 to count both ends
	call	putb		;print
	cram	' BYTES '
	mov	ax,128d	 ;numbers start at 128.
	mov	cl,ds:fdlen	;get shift count
	sal	ax,cl
	call	putn		;print

	cmp	ds:fdden,fm	;FM?
	je	stat1
	cram	' DD'	 ;no
	jmp	short stat2
stat1: cram	' SD'
stat2: cmp	byte ptr ds:fddbl,0 ;double-step?
	jnz	stat3
	cram	' SINGLESTEP' ;no
	jmp	short stat4
stat3: cram	' DOUBLESTEP' ;yes
stat4: call	flush		;flush line
	cram	' MINSECTOR '
	mov	al,ds:secbas	;get base sector
	call	putb		;print
	cram	' GAP3 '	;gap 3 length
	mov	al,ds:fdgpl	;get it
	call	putb		;print
	cram	' GAP3FORMAT ' ;gap 3 length for format
	mov	al,ds:fdgpf	;get it
	call	putb		;print
	cram	' FILL '	;fill byte
	mov	al,ds:fdfil	;get it
	call	puth		;print
	mov	al,ds:fddcr	;get data rate reg
	test	al,al		;0?
	jne	stat5
	cram	' HD (500 kHz, 360 RPM)'
	jmp	short stat8
stat5: cmp	al,1		;1?
	jne	stat6
	cram	' DDinHD (300 kHz, 360 RPM)'
	jmp	short stat8
stat6: cmp	al,2		;2?
	jne	stat7
	cram	' DDinDD (250 kHz, 300 RPM)'
	jmp	short stat8
stat7: cram	' ED (1 MHz, 300 RPM)' ;must be 43h
stat8: call	flush		;flush line

;WHS: My addition:
	cmp	byte ptr ds:sides,1 ;is it? (WHS)
	jne	stat9
	cram	' SINGLE'
	jmp	short	stat10
stat9:	cram	' DOUBLE'
stat10:	cram	'SIDED '


	cram	' HEAD0ALIAS '
	mov	al,ds:fdhdm	;get alias for head 0
	call	putb		;print
	cram	' HEAD1ALIAS '
	mov	al,ds:fdhdm+1	;get alias for head 1
	call	putb		;print
	call	flush		;flush line

	cram	' After a read/verify error: '
	cmp	byte ptr ds:ignore_errors,1	;ignore errors?
	je	stat11
	cram	'STOP'
	jmp	short	stat12
stat11:	cram	'CONTINUE'
stat12:	call	flush


	pop	cx		;restore
	pop	si
	ret


;//STATUS


;NOW FOLLOW ACTUAL DISK READING/WRITING COMMANDS

;SECTOR EDITING: GET, PUT, SHOW, LOAD, SAVE
get:	; get a sector
	call	getput	 ;get parms
	; actually read the sector
	push	si		;save si, cx
	push	cx
	mov	bx,ds:buf	;get buf ptr
	mov	ch,ds:cyl	;cyl
	mov	cl,ds:sec	;sector
	mov	dh,ds:head	;head
	mov	dl,1		;sec cnt
	call	fdrds		;read the sector
	pop	cx		;[restore]
	pop	si
	jc	get1		;error
	ret
get1:	jmp	ioerr
;+
;
; Get parms for GET/PUT
;
; GET cyl head sec	-or-
; GET cyl sec		if single-sided disk
;
; si, cx updated on return, values set in DS:CYL, DS:HEAD, DS:SEC.
;
;-
getput: ; get GET/PUT parms (CYL [HEAD] SEC)
	call	getn		;get cyl
	test	dx,dx		;must be 0
	jnz	gtpt2
	test	ah,ah
	jnz	gtpt2
	cmp	al,ds:numcyl	;< # cyls?
	jae	gtpt2
	mov	ds:cyl,al		;save
	mov	ds:head,ah	;init head in case single sided
;	cmp	byte ptr ds:numhd,1 ;is it?
	cmp	byte ptr ds:sides,1 ;is it? (WHS)
	je	gtpt1		;yes, don't parse head # since it's always 0
	call	getn		;get head
	test	dx,dx		;must be 0
	jnz	gtpt3
	test	ax,not 1		;must be 0 or 1
	jnz	gtpt3
	mov	ds:head,al
gtpt1:	call	getn		;get sec
	test	dx,dx		;must be 0
	jnz	gtpt4
	test	ah,ah
	jnz	gtpt4
	mov	ds:sec,al	;save
	sub	al,ds:secbas	;subtract minimum sector #
	cmp	al,ds:fdeot	;too high?
	jae	gtpt4
	ret
gtpt2: error	'?Invalid cyl'
gtpt3: error	'?Invalid head'
gtpt4: error	'?Invalid sector'
;
put:	; put a sector
	call	getput	 ;get parms
	; actually write the sector
	push	si		;save si, cx
	push	cx
	mov	bx,ds:buf	;get buf ptr
	mov	ch,ds:cyl	;cyl
	mov	cl,ds:sec	;sector
	mov	dh,ds:head	;head
	mov	dl,1		;sec cnt
	call	fdwrs		;write the sector
	pop	cx		;[restore]
	pop	si
	jc	put1		;error
	ret
put1:	jmp	ioerr
;
load:	; load sector buffer from file
	call	openr		;open input file
	push	cx		;save
	mov	dx,ds:buf	;pt at buf
	mov	ax,128d	 ;starting length
	mov	cl,ds:fdlen	;shift count
	sal	ax,cl		;find real length
	mov	cx,ax		;copy
	call	fread		;read it
	pop	cx		;restore
	ret
;
save:	; save sector buffer in file
	call	openw		;create output file
	push	cx		;save
	mov	dx,ds:buf	;pt at buf
	mov	ax,128d	 ;starting length
	mov	cl,ds:fdlen	;shift count
	sal	ax,cl		;find real length
	mov	cx,ax		;copy
	call	fwrite	 ;write it
	pop	cx		;restore
	ret
;
show:	; show sector buffer in hex and ASCII
	push	si		;save
	push	cx
	mov	si,ds:buf	;pt at buffer
	mov	ax,128d/16d	;init # lines
	mov	cl,ds:fdlen	;get shift count
	sal	ax,cl
	mov	cx,ax		;put in cx
	mov	di,offset lbuf ;pt at buffer
show1: ; print address
	push	cx		;save count
	mov	ax,si		;get begn
	sub	ax,ds:buf		;find offset from begn
	push	ax		;save
	mov	al,ah		;get high nybble
	call	puthn		;print it
	pop	ax		;restore low byte
	call	puth		;print it
	mov	ax," "	 ;two blanks
	stosw
	; print hex data
	mov	cx,16d	 ;load count
show2: lodsb			;get next byte
	call	puth		;print it
	mov	al,'-'	 ;assume hyphen
	cmp	cl,9d		;only 8d to go?
	je	show3		;yes
	 mov	al,' '	 ;no, change to blank
show3: stosb			;save
	loop	show2		;loop
	mov	ax," "	 ;two blanks
	stosw
	; print ASCII data
	mov	cl,16d	 ;reload
	sub	si,cx		;back up
show4: lodsb			;get a byte
	and	al,127	 ;trim to ASCII, (WAS 177, fout!)
	cmp	al,127	 ;rubout? (WAS 177, fout!)
	je	show5
	cmp	al,' '	 ;control char?
	jae	show6
show5: mov	al,'.'	 ;change to .
show6: stosb			;save
	loop	show4
	call	flush		;print line
	pop	cx		;restore line count
	loop	show1
	pop	cx		;restore
	pop	si
	ret
;

;WHOLE DISK(SIDE): FORMAT, READ, WRITE.
;FORMAT[0,1]
format0:	;format side 0
	mov	byte ptr ds:head,0
	mov	byte ptr ds:numhd,1 ;# heads=1
	jmp	skip_f
format1:	;format side 1
	mov	byte ptr ds:head,1
	mov	byte ptr ds:numhd,1 ;# heads=1
	cmp	byte ptr ds:sides,1 ;is it? (WHS)
	jne	skip_f
	jmp	side_error

format: ; format disk
	mov	byte ptr ds:head,0
	mov	byte ptr ds:numhd,2 ;# heads=2
	cmp	byte ptr ds:sides,1 ;is it? (WHS)
;	jne	skip_f
;	mov	byte ptr ds:numhd,1 ;NO # heads=1
	je	format0

skip_f:
	push	si		;save
	push	cx
	mov	byte ptr ds:cyl,0 ;init posn
;	mov	byte ptr ds:head,0
	call	seczero		 ;compute skew
fmt1:	; start next track
	call	ptrack	 	;print track info
	; build format table
	mov	di,ds:buf	;point at buffer
	mov	bx,di		;copy
	mov	dl,ds:secbas	;starting sector #
	mov	bl,ds:head	;get head
	xor	bh,bh		;high byte=0
	mov	dh,ds:fdhdm[bx] ;map head #
fmt2:	mov	al,ds:cyl	;get cyl #
	mov	ah,dh		;head #ALIAS
	stosw			;C, H
	mov	al,dl		;sector #
	mov	ah,ds:fdlen	;size code
	stosw			;R, N
	inc	dx		;R+1
	jz	fmt3		;wrapped, must be done
	cmp	dl,ds:fdeot	;done?
	jbe	fmt2		;loop if not
fmt3:	; format the track
	mov	ch,ds:cyl	;cyl
	mov	cl,ds:secbas	;sector
	mov	dh,ds:head	;head
	mov	dl,1		;sec cnt (sets DMA size enough for CHRN table)
	mov	bx,ds:buf	;get buf ptr

	call	fdftk		;format track
	jc	fmt6		;error (IO error)
	
	; verify

;	jmp fmt5 		;To skip VERIFY

	mov	al,ds:secskw	;get sector-to-sector skew
	add	al,ds:secbas	;add sector base
	mov	ds:secoff,al	;set starting sector
	call	secini	 ;init sector interleave table
fmt4:	; write next sector
	call	secnxt	 ;get next sector #
	jc	fmt5		;none left, skip to next track
	mov	bx,ds:buf	;get buf ptr
	mov	ch,ds:cyl	;cyl
	mov	cl,ds:sec	;sector
	mov	dh,ds:head	;head
	mov	dl,1		;sec cnt
	call	fdrds		;read sector (to verify)
	jnc	fmt4		;do next sector

	jmp	short	fmt_verify_error

fmt5:	call	trknxt	 ;bump to next track
	jnc	fmt1		;loop if not done
	call	pdone		;clean up PTRACK mess
	pop	cx		;restore
	pop	si
	ret
fmt6:	push	ax		;save error code
	call	pdone		;clear up mess
	pop	ax		;restore
	jmp	ioerr		;I/O error

fmt_verify_error:
	mov	di,offset lbuf	;pt at line buffer

	call	cram1
	db	1	;1 byte
	db	10	;lf
	call	flush
	cram	'Verify error at sector '
	mov	al,ds:sec	;cylinder #
	call	putb		;print it
	call	flush
	cmp	byte ptr ds:ignore_errors,1	;ignore errors?
;	jmp	fmt4
	je	fmt4

	error	'?Verify error'
;

;READ DISK(SIDE)
read0: ;read side 0 of disk (WHS)
	mov	byte ptr ds:numhd,1 ;# heads=1
	mov	byte ptr ds:head,0 ;head =0
	jmp skip_r

read1: ;read side 1 of disk (WHS)
	mov	byte ptr ds:numhd,1 ;# heads=1
	mov	byte ptr ds:head,1 ;head =1

	cmp	byte ptr ds:sides,1 ;is it? (WHS)
	jne	skip_r
	jmp	side_error


read:	; read entire disk to file
	mov	byte ptr ds:numhd,2 ;# heads=2
	mov	byte ptr ds:head,0 ; initially head =0
	cmp	byte ptr ds:sides,1 ;is it? (WHS)
	je	read0
;	mov	byte ptr ds:numhd,1 ;NO # heads=1

skip_r:
	call	openw		;open file
	push	si		;save
	push	cx
	mov	byte ptr ds:cyl,0 ;init posn
;	mov	byte ptr ds:head,0
	call	seczero	 	;init for cyl 0
read_1: ; start next cyl
	call	ptrack		;print track info
	call	secini		;init sector interleave table
read_2: ; read next sector
	call	secnxt		;get next sector #
	jc	read_4		;none left, write to file
	push	ax		;save base addr
	mov	bx,ds:buf	;get buf ptr
	mov	ch,ds:cyl	;cyl
	mov	cl,ds:sec	;sector
	mov	dh,ds:head	;head
	mov	dl,1		;sec cnt
	call	fdrds		;read the sector
	pop	di		;[get ptr into track buffer]
	jc	read_3
	mov	si,ds:buf	;pt at sector just read
	mov	ax,128d/2	;base sector length in words
	mov	cl,ds:fdlen	;shift count
	sal	ax,cl		;find # words per block
	mov	cx,ax		;copy
	rep	movsw		;copy into appropriate spot in buffer
	jmp	short read_2	;loop
read_3:	cmp	al,2
;	je	skip_read_err
	je	read_error_ignore
	jmp	ioerr		;handle I/O error
skip_read_err:
;	jmp	read_error_ignore
read_4: ; write track buffer to file
	mov	dx,offset trkbuf	;pt at buffer
	xor	ah,ah		;ah=0
	mov	al,ds:fdeot	;get end of track
	sub	al,ds:secbas	;subtract base
	inc	ax		;total # sectors per track
	mov	cl,ds:fdlen	;get shift count
	add	cl,7		;add base
	sal	ax,cl		;find # bytes per track
	mov	cx,ax		;copy
	call	fwrite	 	;write to disk
	call	trknxt	 	;bump to next track
	jnc	read_1		;loop if not done
	call	pdone		;clean up PTRACK mess
	call	close		;close file
	pop	cx		;restore
	pop	si
	ret
;
read_error_ignore:
	mov	di,offset lbuf	;pt at line buffer

	call	cram1
	db	1	;1 byte
	db	10	;lf
	call	flush
	cram	'Read error at sector '
	mov	al,ds:sec	;cylinder #
	call	putb		;print it
	call	flush
	cmp	byte ptr ds:ignore_errors,1
	je	read_4
;	jmp	read_4
	mov	al,2
	jmp	ioerr

;WRITE DISK(SIDE)
write0: ;write file to side 0
	mov	byte ptr ds:numhd,1	;# heads=1
	mov	byte ptr ds:head,0	;head =0
	jmp skip_w

write1: ;write file to side 1
	mov	byte ptr ds:numhd,1	;# heads=1
	mov	byte ptr ds:head,1	;head =1
	cmp	byte ptr ds:sides,1 ;is it? (WHS)
	jne	skip_w
	jmp	side_error

write: ; write file to entire disk
	mov	byte ptr ds:numhd,2	;# heads=2
	mov	byte ptr ds:head,0	;head =0
	cmp	byte ptr ds:sides,1 ;is it? (WHS)
	je	write0
;	mov	byte ptr ds:numhd,1 ;NO # heads=1
skip_w:
	call	openr		;open file
	push	si		;save
	push	cx
	mov	byte ptr ds:cyl,0 ;init posn
;	mov	al,ds:sides	;0=side 0 only, 1=side 1 only, 2 =both
;	and	al,1
;	mov	byte ptr ds:head,al
	call	seczero		;init for cyl 0
wr1: ; start next cyl
	call	ptrack		;print track info
	mov	dx,offset trkbuf	;pt at buffer
	xor	ah,ah		;ah=0
	mov	al,ds:fdeot	;get end of track
	sub	al,ds:secbas	;subtract base
	inc	ax		;total # sectors per track
	mov	cl,ds:fdlen	;get shift count
	add	cl,7		;add base
	sal	ax,cl		;find # bytes per track
	mov	cx,ax		;copy
	call	fread		;read from disk
	call	secini		;init sector interleave table
wr2: ; write next sector
	call	secnxt		;get next sector #
	jc	wr3		;none left, skip to next track
	mov	si,ax		;point at file data
	mov	di,ds:buf	;disk buffer
	mov	bx,di		;save ptr
	mov	ax,128d/2	;base sector length in words
	mov	cl,ds:fdlen	;shift count
	sal	ax,cl		;find # words per block
	mov	cx,ax		;copy
	rep	movsw		;copy from appropriate spot in buffer
	mov	ch,ds:cyl	;cyl
	mov	cl,ds:sec	;sector
	mov	dh,ds:head	;head
	mov	dl,1		;sec cnt
	call	fdwrs		;write the sector
	jnc	wr2		;do next sector
	jmp	ioerr
wr3:	call	trknxt		;bump to next track
	jnc	wr1		;loop if not done
	call	pdone		;clean up PTRACK mess
	call	close		;close file
	pop	cx		;restore
	pop	si
	ret


side_error:
	error	'?Single sided disk!'


;+
;
; Print track and head.
;
;-
ptrack: mov	di,offset lbuf ;pt at line buffer
	cram	'Track '	;track
	mov	al,ds:cyl	;cylinder #
	call	putb		;print it
;	cmp	byte ptr ds:numhd,1 ;single-sided? OUD
	cmp	byte ptr ds:sides,1 ;single-sided?
	je	ptrk1		;yes, that's it

	cram	' head '	;head
	mov	al,ds:head	;get value
	call	putb		;print it

ptrk1: ; entry from below
	mov	al,cr		;just cr
	stosb
	jmp	flush1		;flush it w/o <CRLF>, return
;+
;
; Clear PTRACK mess when done.
;
;-
pdone: mov	di,offset lbuf	;pt at line buffer
;	mov	cx,6+3		;LEN('Track ')+3 digits max
;	cmp	byte ptr ds:numhd,1 ;single-sided?
;	je	pdone1		;yes, that's it
;	 add	cl,6+1		;LEN(' head ')+1 digit
	mov	cx,6+3+6+1
pdone1: mov	al,' '		;blank
	rep	stosb		;write that many blanks
	jmp	short ptrk1	;go flush line
;+
;
; Init sector interleave information before beginning transfer.
;
; Computes track-to-track skew and initializes sector offset.
;
;-
seczero:
	mov	al,ds:fdeot	;get end of track
	sub	al,ds:secbas	;subtract beginning of track
	xor	ah,ah		;ah=0
	inc	ax		;+1=# sectors/track
	mov	bx,ax		;copy
	mov	dl,5		;divide # sectors by 5
	div	dl		;=# sectors to skew per cylinder
	mov	ds:secskw,al	;save
	mov	al,ds:secbas	;get base sector #
	mov	ds:secoff,al	;init offset
	ret
;+
;
; Init sector interleave flag table.
;
; This routine sets things up for 2:1 software interleave.
; The SECFLG array contains a byte for every possible sector on the track
; (0-255), and this routine clears every byte that represents a sector that
; does exist (from SECBAS to FDEOT), and sets every byte for sectors that
; don't exist (from 0 to SECBAS-1). It also chooses the starting sector
; number based on the cylinder number (so we get a 1/5-track skew between
; tracks to allow time for the head to step).
;
;-
secini: mov	di,offset secflg ;pt at table
	mov	cl,ds:fdeot	;get count
	xor	ch,ch		;ch=0
	inc	cx		;count both ends [FOUT??]
	xor	al,al		;clear all
	rep	stosb
	mov	di,offset secflg ;back up
	inc	ax		;al=1
	mov	cl,ds:secbas	;find # sectors that don't exist (usually 1)

	cmp	cl,0		;WHS
	je	skip_stosb	;WHS

	rep	stosb
skip_stosb:
	mov	al,ds:fdeot	;get end of track
	sub	al,ds:secbas	;subtract beginning of track
	xor	ah,ah		;ah=0
	inc	ax		;+1=# sectors/track
	mov	ds:seccnt,ax	;save
	ret
;+
;
; Get next sector # to transfer.
;
; On return:
; ax	starting addr of sector (within TRKBUF)
; DS:SEC sector #
;
; Or CF=1 if all sectors of this track have been transferred.
;
;-
secnxt: cmp	ds:seccnt,0	;done?
	jz	snxt8		;yes, that's all
	mov	bl,ds:secoff	;get offset into SECFLG
	xor	bh,bh		;zero-extend
	mov	ax,bx		;copy
	inc	ds:secflg[bx]	;set it (known available)
	dec	ds:seccnt	;count it
	jz	snxt5		;last one, handle skew
	; find first available sector at least 2 sectors beyond this one
	; (wrap around end of track if necessary)
snxt1: add	bl,2		;skip 2 slots (2:1 interleave)
snxt2: jbe	snxt3		;overflowed
	cmp	bl,ds:fdeot	;off end of track?
	jbe	snxt4		;no
snxt3: sub	bl,ds:fdeot	;wrap around
	dec	bl		;should have subtracted FDEOT+1
	add	bl,ds:secbas	;add base of track
snxt4: cmp	ds:seccnt,0	;do we expect to find anything?
	jz	snxt7		;no, so we're happy (will be avail next time)
	cmp	ds:secflg[bx],bh ;is this slot available for next time?
	jz	snxt7		;yes
	inc	bl		;no, +1
	jz	snxt3		;overflowed
	cmp	bl,ds:fdeot	;off end of track?
	jbe	snxt4		;no, check this slot
	jmp	short snxt3	;yes, wrap around
snxt5: ; this will be last sector of track
	cmp	byte ptr ds:numhd,1 ;single-sided disk?
	je	snxt6		;yes
	cmp	byte ptr ds:head,0 ;no, finished second side?
	jz	snxt1		;no, just continue interleave (no step)
snxt6: add	bl,ds:secskw	;add skew (set CF, ZF)
	jmp	short snxt2	;go handle it
snxt7: ; ax=sector to use this time, bx=sector to use next time
	mov	ds:sec,al	;save sector we're using
	mov	ds:secoff,bl	;update sector to use next time
	sub	al,ds:secbas	;find offset from starting sector
	xchg	al,ah		;><
	shr	ax,1		;=sector *128
	mov	cl,ds:fdlen	;get shift count
	sal	ax,cl		;offset from TRKBUF
	add	ax,offset trkbuf ;add base (CF=0)
	ret
snxt8: stc			;no more sectors
	ret
;+
;
; Advance to next track.
;
; Updates CYL and HEAD, or returns CF=1 if off end of disk.
;
;-
trknxt: cmp	byte ptr ds:numhd,1 ;single-sided?
	je	tnxt1		;yes, don't advance head, just bump cyl
	xor	byte ptr ds:head,1 ;flip head
	jnz	tnxt2		;side 1, skip
tnxt1: inc	ds:cyl	 ;bump to next cylinder
	mov	al,ds:cyl	;get it
	cmp	al,ds:numcyl	;off end of disk?
	jae	tnxt3		;yes
tnxt2: clc			;happy
	ret
tnxt3: stc			;end of disk
	ret


;FILE, KEYBOARD, SCREEN & PARSE SUBROUTINES
;+
;
; Open file for input.
;
;-
openr: call	getf		;get filename
	mov	ax,3D00h	;func=open /RONLY
	int	21h
	jc	openr1	 ;error
	mov	ds:handle,ax	;save
	ret
openr1: error	'?Error opening file'
;+
;
; Read from a file. dx, cx set up for call.
;
;-
fread: mov	bx,ds:handle	;get handle
	mov	ah,3Fh	 ;func=read
	int	21h
	jc	fread1	 ;error
	test	ax,ax		;eof?
	jz	fread2
	; pad buffer out to expected size with zeros
	; purpose of doing this instead of just flagging an error is to ensure
	; that if a WRITE command is intentionally given a file that is too
	; short, we at least write all of the data we got before punting,
	; instead of losing the last partial track
	mov	di,dx		;get ptr to base
	add	di,ax		;skip to end of what we got
	sub	cx,ax		;find # bytes left to go (0 if we got all)
	xor	al,al		;load 0
	rep	stosb		;clear out to end of buffer (if needed)
	ret
fread1: error	'?File read error'
fread2: error	'[Input file shorter than expected]' ;NOT REALLY AN ERROR.
;+
;
; Open file for output.
;
;-
openw: call	getf		;get filename
	push	cx		;save
	xor	cx,cx		;mode=0
	mov	ah,3Ch	 ;func=create
	int	21h
	pop	cx		;[restore]
	jc	openw1	 ;error
	mov	ds:handle,ax	;save
	ret
openw1: error	'?Error creating file'
;+
;
; Write to a file. dx, cx set up for call.
;
;-
fwrite: mov	bx,ds:handle	;get handle
	mov	ah,40h	 ;func=write
	int	21h
	jc	fwrit1
	ret
fwrit1: error	'?File write error'
;+
;
; Close open file, if any.
;
;-
close: mov	bx,-1		;"closed" flag
	xchg	bx,ds:handle	;get handle, mark closed
	test	bx,bx		;negative?
	js	close1	 ;yes, valid handles never are
	mov	ah,3Eh	 ;func=close
	int	21h
close1: ret
;+
;
; Get filename.
;
;-
getf:	call	getw		;get filename
	jc	getf1
	xchg	bx,dx		;get ptr in dx
	add	bx,dx		;point to end
	mov	byte ptr [bx],0 ;mark it (GETW will skip NUL next time)
	ret
getf1: error	'?Missing filename'
;+
;
; Parse a word from the input line.
;
; ds:si current position
; cx	# chars left
;
; On return:
; si	points at posn after last char of word
; cx	updated
; bx	points at begn of word if CF=0
; dx	length of word
;
;-
getw:	jcxz	getw2		;eol already
getw1: mov	bx,si		;in case word starts here
	lodsb			;get a char
	cmp	al,' '	 ;blank or ctrl?
	ja	getw4		;no
	loop	getw1		;loop
getw2: stc			;no luck
	ret
getw3: lodsb			;get a char
getw4: cmp	al,' '	 ;blank or ctrl?
	jbe	getw6		;yes, end of word
	cmp	al,'a'	 ;lower case?
	jb	getw5
	cmp	al,'z'	 ;hm?
	ja	getw5
	and	al,not 40	;yes, convert
	mov	[si-1],al	;put back
getw5: loop	getw3		;loop
	inc	si		;compensate for next inst
getw6: dec	si		;unget
	mov	dx,si		;calc length
	sub	dx,bx		;CF=0
	ret
;+
;
; Parse a decimal number from the input line.
;
; si,cx input line descriptor (updated on return)
; dx:ax returns number
;
;-
getn:	call	getw		;parse it
	jc	cvtn3
cvtn:	; enter here to parse number from GETN
	mov	di,dx		;get length
	push	si		;save
	mov	si,bx		;point at it
	xor	bx,bx		;init #
	xor	dx,dx		;high word
cvtn1: lodsb			;get a digit
	sub	al,'0'	 ;convert to binary
	cmp	al,9d		;digit?
	ja	cvtn2
	cbw			;ah=0
	push	ax		;save new digit
	mov	ax,10d	 ;multiplier
	mul	dx		;high word *10
	test	dx,dx		;overflow?
	jnz	cvtn2
	push	ax		;save
	mov	ax,10d	 ;low word *10
	mul	bx
	pop	bx		;catch high word
	add	dx,bx		;add it in
	pop	bx		;catch new digit
	add	bx,ax		;add it in
	adc	dx,0
	jc	cvtn2		;overflow
	dec	di		;done all?
	jnz	cvtn1		;loop if not
	mov	ax,bx		;copy number
	pop	si		;restore
	ret
cvtn2: ; these two labels are ref'ed from above and below too
	error	'?Bad number'
cvtn3: error	'?Missing number'
;+
;
; Parse a hex number from the input line.
;
; si,cx input line descriptor (updated on return)
; ax	returns number
;
;-
geth:	call	getw		;parse it
	jc	cvtn3
cvth:	; enter here to parse number from GETN
	push	cx		;save
	mov	di,dx		;get length
	xor	dx,dx		;init #
	mov	cl,4		;shift count
	mov	al,[bx+di-1]	;get last char
	and	al,not 40	;convert to U.C. if letter
	cmp	al,'H'	 ;trailing H?
	jne	cvth1		;no
	dec	di		;yes, count it
	jz	cvtn2		;nothing left, complain
cvth1: mov	al,[bx]	 ;get a digit
	inc	bx
	sub	al,'0'	 ;convert to binary
	cmp	al,9d		;digit?
	jbe	cvth2
	sub	al,'A'-('9'+1)+10d ;no, see if in A-F
	cmp	al,5
	ja	cvtn2		;no, bad number
	add	al,0Ah	 ;convert back to 0A-0F
cvth2: cbw			;ah=0
	test	dh,0F0h	 ;is there space for another digit?
	jnz	cvtn2
	sal	dx,cl		;yes, slide over
	or	dl,al		;OR in new digit
	dec	di		;done all?
	jnz	cvth1		;loop if not
	mov	ax,dx		;copy number
	pop	cx		;restore
	ret
;+
;
; Look up a keyword in a table.
;
; ds:bx keyword } from GETW
; dx	length }
; cs:ax table
;
; Returns CF=1 if not found, otherwise ax=number from table.
;
; This routine doesn't require that DS=CS, so it may be used to parse
; environment strings.
;
; si,cx preserved either way.
;
;-
tbluk: push	cx		;save
	push	si
	push	ds
	mov	si,ax		;pt at table
	push	ds		;copy ds to es
	pop	es
	push	cs		;and cs to ds
	pop	ds
	xor	ch,ch		;ch=0
tbluk1: lodsw			;get length,,length to match
	or	al,al		;end?
	jz	tbluk4
	mov	cl,ah		;assume bad length
	cmp	al,dl		;is ours long enough?
	ja	tbluk2	 ;no
	sub	ah,dl		;too long?
	jc	tbluk2	 ;yes
	mov	cl,dl		;just right
	mov	di,bx		;point at keyword
	repe	cmpsb		;match?
	je	tbluk3
	add	cl,ah		;no, add extra length
tbluk2: add	si,cx		;skip to end
	inc	si		;skip jump addr
	inc	si
	jmp	short tbluk1	;loop
tbluk3: ; got it
	mov	cl,ah		;get extra length
	add	si,cx		;skip to end
	lodsw			;get dispatch addr
	stc			;makes CF=0 below
tbluk4: ; not found
	cmc			;CF=-CF
	pop	ds		;restore regs
	pop	si
	pop	cx
	ret
;+
;
; Print decimal number in ax at es:di (updated).
;
;-
putb:	; byte operand in al
	xor	ah,ah		;zero-extend
putn:	; word operand in ax
	mov	bx,10d	 ;divisor
putn1: cmp	ax,bx		;just one digit left?
	jb	putn2
	xor	dx,dx		;no, zero-extend
	div	bx		;divide
	push	dx		;save remainder
	call	putn1		;recurse
	pop	ax		;restore
putn2: add	al,'0'	 ;add base
	stosb			;save
	ret
;+
;
; Print hex number in al at es:di (updated).
;
;-
puth:	mov	ah,al		;save
	shr	al,1		;right 4 bits
	shr	al,1
	shr	al,1
	shr	al,1
	call	puthn		;print high digit
	mov	al,ah		;get low digit
puthn: ; print one nybble in hex
	and	al,0Fh	 ;isolate
	cmp	al,0Ah	 ;CF=1 if 0-9
	sbb	al,69h	 ;al=96-9F or A1-A6 (AF=1 if 0-9, CF=1 always)
	das			;low byte -6 if 0-9, high byte -60h
	stosb			;save
	ret
;+
;
; Cram in-line string into buffer at ES:DI.
;
;-
cram1: pop	si		;catch ptr
	lodsb			;get length byte
	cbw			;ah=0
	mov	cx,ax		;copy
	rep	movsb		;move the string
	jmp	si		;return
;+
;
; Flush line buffer, set up for next line.
;
;-
flush:	mov	ax,cr+(lf*400) ;get crlf
	stosw			;mark end
flush1: ; enter here for no <CRLF>
	mov	dx,offset lbuf 	;pt at begin of line
	sub	di,dx		;get length
	mov	cx,di		;copy
	mov	bx,0001h	;handle=stdout
	mov	ah,40h	 	;func=write
	int	21h
	mov	di,dx		;reset ptr
	ret
;+
;
; Print error message for floppy error code in al.
;
;-
ioerr: test	al,al		;0?
	jne	ioerr1
	error	'?FDC timeout' ;FDC chip not alive
ioerr1: cmp	al,1		;1?
	jne	ioerr2
	error	'?Seek error'	;error seeking to track
ioerr2: cmp	al,2		;2?
	jne	ioerr3
	error	'?I/O error'	;sector not found etc.
ioerr3: error	'?Disk is write protected' ;must be 200q
;+
;
; Print in-line error message and return to main loop.
;
;-
error1: pop	si		;catch ptr
	lodsb			;get length byte
	cbw			;ah=0
	mov	cx,ax		;copy
	mov	dx,si		;point at string
	mov	bx,0002h	;handle=stderr
	mov	ah,40h	 ;func=write
	int	21h
	mov	dx,offset crlf ;pt at crlf
	mov	cx,2		;length
	mov	ah,40h	 ;func=write (handle already set)
	int	21h
	jmp	mloop		;back to command prompt
;

	subttl FDC I/O code
;+
;
; Non-BIOS floppy disk controller driver code for NEC 765 and clones.
;
; Author:
;
; John M. B. Wilson
; 11 Bank Street
; Troy, NY 12180-4303 USA
; +1 (518) 271-1982
; <wilsonj@alumni.rpi.edu>
;
; This code is hereby released to the public domain. You are free to use it as
; you see fit in your own programs, whether for commercial gain or not; while
; I would appreciate being credited, especially if others receive credit for
; other parts of your program, this is not a prerequisite to using this code.
;
; The only ways in which this code uses BIOS are:
; (1) Turning off the motors (done by the BIOS timer ISR)
; (2) Uses the BIOS FDC ISR (which sets the high bit of SEEK_STATUS when done).
; (3) Uses BIOS timer ticks to detect timeout (PC BIOS uses software timing,
;	which is asking for trouble)
;
; These routines have two main goals: (1) The ability to access single density
; disks; BIOS is hard-coded for double density, and as a result most FDC's
; blow off support for single density anyway! At least *some* SMC and Goldstar
; FDC chips are known OK as well as the original NEC 765. (2) The ability to
; run asynchronously, with its own ISRs and with FDCWT rewritten to come back
; on the next FDC interrupt (or after a set number of timer interrupts in case
; of timeout) instead of polling SEEK_STATUS.AND.80h.
;
; Known bugs:
; (1) Single density doesn't work with many FDC chips (incomplete emulation).
; (2) On some FDCs, the first "format track" command after a hard FDC reset
;	returns an error, but the second one works OK. The floppy driver in
;	Linux, which has nothing whatsoever to do with this one, has this problem
;	too, which leads me to believe that it's a hardware bug. Yeah, that's
;	it! Workaround is to retry FDFTK calls once.
;
; 03/22/94	JMBW	Created.
; 09/04/95	JMBW	Added double-stepping flag for using 48tpi disks in
;			96tpi drives.
;
; Everything down to the next form feed is self-contained, i.e. it may be
; called from outside but it doesn't call anything outside, and is designed to
; be chopped out and incorporated into other programs. All multi-digit
; numerical constants have suffixes so this code is not dependent on the
; ".radix 8" at the top of the file.
;
; FDC I/O registers:
;
fdcdor= 3F2h	;FDC digital output register
fdcmsr= 3F4h	;FDC main status register
fdcdat= 3F5h	;FDC data register
fdcfcr= 3F6h	;FDC format control register (SMC FDC37C65C+ only)
		;(that addr is my choice, may differ on commercial FDCs)
fdcdcr= 3F7h	;FDC diskette control register (data rate, etc.)
fdcdir= 3F7h	;FDC digital input register (disk change line)
;
; SD/DD flag in first byte of NEC 765 commands:
mfm=	100q	;MF flag set (double density)
fm=	0	;MF flag clear (single density)
;
; Absolute addresses maintained by system BIOS:
;
seek_status equ byte ptr 043Eh ;int flag (b7), "recal needed" bits (b3-b0)
motor_status equ byte ptr 043Fh ;motor bits (b7 = spin-up first (write))
motor_count equ byte ptr 0440h ;# timer ticks until motor turnoff
timer_low equ word ptr 046Ch	;low word of system time
;
port	macro	new,old ;;macro to change dx from old to new
ifnb <old>
if new gt old
 if new le old+2
 if new eq old+2
	inc	dx
 endif
	inc	dx
 else
 if (high new) eq (high old)
	add	dl,new-old
 else
	mov	dx,new
 endif
 endif
endif
if new lt old
 if new ge old-2
 if new eq old-2
	dec	dx
 endif
	dec	dx
 else
 if (high new) eq (high old)
	sub	dl,old-new
 else
	mov	dx,new
 endif
 endif
endif
else
	mov	dx,new	 ;;no "old" specified
endif
	endm
;+
;
; Init floppy drive for a particular format.
;
; These particular routines init the drive to emulate DEC (-like) formats,
; other formats will require other parameters. The gap length is the only
; magic one, the others are either obvious or can be guessed, the geometry is
; what you're here for, and the timing numbers (in FDSPC, the "specify bytes")
; depend on the drive so they probably won't need to be changed (although it
; appears that they're obtained by counting down the currently selected data
; rate, so they may be off by a factor of 2). If they do need changing, you'll
; need FDC data sheets (or the old IBM PC tech ref manual) to fully understand
; them.
;
; al	unit #
;
;-
ini360: ; 360KB 5.25" disk
	mov	ds:fddrv,al	;save
	mov	word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA
	mov	byte ptr ds:fdlen,02h ;512. bytes/sec
	mov	byte ptr ds:fdeot,9d ;9. secs/trk
	mov	byte ptr ds:fdgpl,23h ;gap length=35.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdcur,-1 ;curr cyl is unknown
	mov	byte ptr ds:fdden,mfm ;double density
	mov	byte ptr ds:fdgpf,50h ;gap length (format)=80.
	mov	byte ptr ds:fdfil,0F6h ;fill=F6
	mov	byte ptr ds:fddcr,1 ;data rate = 250kHz/300RPM or 300kHz/360RPM
	mov	byte ptr ds:fddbl,1 ;probably double-step (they can change it)
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	fdini		;init, return
;
ini720: ; 720KB 3.5" or 5.25" disk
	mov	ds:fddrv,al	;save
	mov	word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA
	mov	byte ptr ds:fdlen,02h ;512. bytes/sec
	mov	byte ptr ds:fdeot,9d ;9. secs/trk
	mov	byte ptr ds:fdgpl,23h ;gap length=35.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdcur,-1 ;curr cyl is unknown
	mov	byte ptr ds:fdden,mfm ;double density
	mov	byte ptr ds:fdgpf,50h ;gap length (format)=80.
	mov	byte ptr ds:fdfil,0F6h ;fill=F6
	mov	byte ptr ds:fddcr,1 ;data rate = 250kHz/300RPM or 300kHz/360RPM
	mov	byte ptr ds:fddbl,1 ;probably double-step (they can change it)
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	fdini		;init, return
;
ini144: ; 1.44MB 3.5" disk (or DEC RX23)
	mov	ds:fddrv,al	;save
;;;	mov	word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA
;;; should be 02CFh according to Linux floppy.c, need to look up meaning
	mov	word ptr ds:fdspc,02CFh
	mov	byte ptr ds:fdlen,02h ;512. bytes/sec
	mov	byte ptr ds:fdeot,18d ;18. secs/trk
	mov	byte ptr ds:fdgpl,1Bh ;gap length=27.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdden,mfm ;double density
	mov	byte ptr ds:fdgpf,6Ch ;gap length (format)=108.
	mov	byte ptr ds:fdfil,0F6h ;fill=F6
	mov	byte ptr ds:fddcr,0 ;data rate = 500kHz/300RPM
	mov	byte ptr ds:fddbl,0 ;don't double-step
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	fdini		;init, return
;
ini288: ; 2.88MB 3.5" disk (or DEC RX26)
;;; doesn't really work yet, it seems things aren't this simple
	mov	ds:fddrv,al	;save
;;;	mov	word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA
;;; should be 02AFh according to Linux floppy.c, need to look up meaning
	mov	word ptr ds:fdspc,02AFh
	mov	byte ptr ds:fdlen,02h ;512. bytes/sec
	mov	byte ptr ds:fdeot,36d ;36. secs/trk
	mov	byte ptr ds:fdgpl,1Bh ;gap length=27.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdden,mfm ;double density
	mov	byte ptr ds:fdgpf,54h ;gap length (format)=84.
	mov	byte ptr ds:fdfil,0F6h ;fill=F6
	mov	byte ptr ds:fddcr,3 ;data rate = 1MHz/300RPM
	mov	byte ptr ds:fddbl,0 ;don't double-step
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	fdini		;init, return
;
ini12: ; 1.2MB 5.25" disk (or DEC RX33)
	mov	ds:fddrv,al	;save
	mov	word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA
	mov	byte ptr ds:fdlen,02h ;512. bytes/sec
	mov	byte ptr ds:fdeot,15d ;15. secs/trk
	mov	byte ptr ds:fdgpl,1Bh ;gap length=27.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdden,mfm ;double density
	mov	byte ptr ds:fdgpf,54h ;gap length (format)=84.
	mov	byte ptr ds:fdfil,0F6h ;fill=F6
	mov	byte ptr ds:fddcr,0 ;data rate = 500kHz/360RPM
	mov	byte ptr ds:fddbl,0 ;don't double-step
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	fdini		;init, return
;
inidx: ; RX01 (8" SS SD) equiv in 1.2MB drive
	mov	ds:fddrv,al	;save
	mov	word ptr ds:fdspc,06BFh ;SRT=10ms, HUT=480ms, HLT=16ms, DMA
	mov	byte ptr ds:fdlen,00h ;128. bytes/sec
	mov	byte ptr ds:fdeot,26d ;26. secs/trk
	mov	byte ptr ds:fdgpl,07h ;gap length=7
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdcur,-1 ;curr cyl is unknown
	mov	byte ptr ds:fdden,fm ;single density
	mov	byte ptr ds:fdgpf,1Bh ;gap length (format)=27.
	mov	byte ptr ds:fdfil,0E5h ;fill=E5
	mov	byte ptr ds:fddcr,0 ;data rate = 250kHz/360RPM
	mov	byte ptr ds:fddbl,0 ;don't double-step
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	fdini		;init, return
;
inidy: ; RX02 (8" SS DD) or so-called "RX03" (8" DS DD) equiv in 1.2MB drive
	mov	ds:fddrv,al	;save
	mov	word ptr ds:fdspc,06BFh ;SRT=10ms, HUT=480ms, HLT=16ms, DMA
	mov	byte ptr ds:fdlen,01h ;256. bytes/sec
	mov	byte ptr ds:fdeot,26d ;26. secs/trk
	mov	byte ptr ds:fdgpl,0Eh ;gap length=14.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdcur,-1 ;curr cyl is unknown
	mov	byte ptr ds:fdden,mfm ;double density
	mov	byte ptr ds:fdgpf,36h ;gap length (format)=54.
	mov	byte ptr ds:fdfil,0E5h ;fill=E5
	mov	byte ptr ds:fddcr,0 ;data rate = 500kHz/360RPM
	mov	byte ptr ds:fddbl,0 ;don't double-step
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	fdini		;init, return
;
inidz: ; RX50 (5.25" SS DD 96tpi) in 1.2MB drive
	mov	ds:fddrv,al	;save
	mov	word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA
	mov	byte ptr ds:fdlen,02h ;512. bytes/sec
	mov	byte ptr ds:fdeot,10d ;10. secs/trk
	mov	byte ptr ds:fdgpl,14h ;gap length=20.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdcur,-1 ;curr cyl is unknown
	mov	byte ptr ds:fdden,mfm ;double density
	mov	byte ptr ds:fdgpf,18h ;gap length (format)=24.
	mov	byte ptr ds:fdfil,0E5h ;fill=E5
	mov	byte ptr ds:fddcr,1 ;data rate = 250kHz/300RPM or 300kHz/360RPM
	mov	byte ptr ds:fddbl,0 ;don't double-step
	mov	word ptr ds:fdhdm,100h ;head map={0,1} (identity map)
	jmp	short fdini	;init, return
;
inibbc: ; BBC DFS (5.25" DS SD 48tpi 10x256) in 360K drive
	mov	ds:fddrv,al	;save
	mov	word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA
	mov	byte ptr ds:fdlen,01h ;256. bytes/sec
	mov	byte ptr ds:fdeot,9d ;10. secs/tr		[want secbas=0 (later)]
	mov	byte ptr ds:fdgpl,0Ah ;gap length=10.
	mov	byte ptr ds:fddtl,0FFh ;max xfr=255. secs
	mov	word ptr ds:fdmtr,18d ;1 second motor spin-up time
	mov	byte ptr ds:fdcur,-1 ;curr cyl is unknown
	mov	byte ptr ds:fdden,fm ;single density
	mov	byte ptr ds:fdgpf,0Eh ;gap length (format)=14.
	mov	byte ptr ds:fdfil,0E5h ;fill=E5
	mov	byte ptr ds:fddcr,2 ;data rate = 250kHz/300RPM or 300kHz/360RPM
;	mov	byte ptr ds:fddbl,1 ;double-step (40-trk disk in 80-trk drv)
	mov	byte ptr ds:fddbl,0 ;single-step (40-trk disk in 40-trk drv, 80 in 80)

	mov	word ptr ds:fdhdm,0000h ;head map={0,0} (identity map)
			;NOTE: I've set head1alias =0 (WHS)
	
	;jmp	short fdini	;init, return
;+
;
; Init floppy I/O.
;
;-
fdini: ; build digital output register value
	push	es		;save
	xor	ax,ax		;load 0 into es
	mov	es,ax
	mov	al,es:motor_status ;get motor bits
	pop	es		;restore
	and	al,17q	 ;isolate
	mov	cx,4		;loop count
	mov	ah,10q	 ;assume drive 0, set IE
fdini1:	ror	al,1		;right a bit
	jnc	fdini2
	mov	ah,14q	 ;calc unit # (leave IE set)
	sub	ah,cl
fdini2:	loop	fdini1	 ;loop through all 4 bits
	or	al,ah		;OR it in (with IE)
	; hard reset FDC (it might be in some new mode we don't know about)
	port	fdcdor	 ;digital output register
	cli			;ints off (so timer ISR doesn't screw with us)
	out	dx,al		;;hard reset
	jmp	short $+2	;;miniscule I/O delay
	or	al,4		;;reenable
	sti			;;(ints on after next)
	out	dx,al		;;enable FDC
	; set data rate
	port	fdcdcr,fdcdor	;pt at port
	mov	al,ds:fddcr	;set value
	out	dx,al
	; send specify command (set timing for this drive)
	mov	si,offset necbuf	;pt at buf
	mov	byte ptr [si],3 	;cmd=specify
	mov	ax,ds:fdspc	;get specify bytes
	mov	[si+1],ax	;save
	mov	cx,3		;length
	call	sfdc		;send to FDC
	ret
;
fdrds: ; read sector(s)
	mov	byte ptr ds:fdspn,0 ;no spin-up needed (retry instead)
	mov	ax,0A646h	;multitrack, skip deleted data, read data
	jmp	short fdxfr	;(doesn't work w/o multitrack bit!)
;
fdwrs: ; write sector(s)
	mov	byte ptr ds:fdspn,-1 ;spin up before writing
	mov	ax,854Ah	;multitrack, write sector,,DMA cmd
	jmp	short fdxfr	;write, return
;
if 0 ; this is never used
fdvfs: ; verify sector(s)
	mov	byte ptr ds:fdspn,0 ;no spin-up needed (retry instead)
	mov	ax,0A642h	;same as read w/different cmd to DMAC
	jmp	short fdxfr	;verify, return
endif
;
fdftk: ; format track
	mov	byte ptr ds:fdspn,-1 ;spin up before writing
	mov	ax,0D4Ah	;format track,,DMA cmd
	;jmp	short fdxfr	;do it, return
;+
;
; Perform a floppy transfer.
;
; al	DMA command byte
; ah	FDC command byte
; es:bx buffer (must not span 64KB boundary -- not checked)
; cl	sector
; ch	cyl
; dh	head
; dl	block count
;
; Returns CF=0 on success, otherwise CF=1 and al contains error code:
; 0	controller timeout (hardware failure)
; 1	seek error
; 2	some kind of transfer error (sector not found, fault)
; 200q write protect
;
; ** N.B. **
; It is *ESSENTIAL* that you guarantee that the buffer addressed by ES:BX is
; located in memory such that the disk data (or format info for FDFTK) being
; read or written do not cross a 64KB address boundary. Which is to say, when
; the absolute addresses of the first and last bytes transferred are expressed
; as flat 5-digit hex numbers, the first digits must be the same. So you must
; check the buffer before using it, and if it spans a 64KB boundary, throw it
; away and allocate another buffer.
;
; The reason for this is that the PC DMA controllers (8237) don't have carry
; between bits 15 and 16 of the address, since bits 0-15 are in the 8237 and
; bits 16 and up are in an external 74LS612. So for example, after a byte at
; 7FFFF is transferred, instead of proceeding to 80000 the address wraps around
; to 70000, resulting in corruption of memory or of the disk.
;
; ** YOU HAVE BEEN WARNED **
; If you don't worry about this you'll probably get lucky and your program will
; work most of the time (like RAWRITE.EXE which comes with Linux and has had
; this bug for years). But then if you run it on a system with a different
; version of DOS, or with different device drivers and TSRs loaded, you lose.
;
;-
fdxfr:	mov	ds:fdcmd,ax	;save FDC cmd
	mov	word ptr ds:fdsec,cx ;and cyl,,sec
	mov	word ptr ds:fdnum,dx ;and head,,blk count
	mov	ds:fdadr,bx	;and addr
	mov	ds:fdtry,5	;init retry counter
fxfr1: ; compute length
	mov	cl,ds:fdlen	;get sector length (0=128., 1=256., 2=512.)
	add	cl,7		;correct
	mov	dl,ds:fdnum	;get sector count
	xor	dh,dh		;dh=0
	sal	dx,cl		;find byte count
	mov	cx,dx		;copy
	mov	al,byte ptr ds:fdcmd ;get DMA command
	mov	bx,ds:fdadr	;get addr (ES is still correct)
	call	dmaset	 ;set up DMA controller
				;(count will be too high if formatting, but...)
	; start motor unless already running
	push	es		;save
	xor	bx,bx		;load 0 into es
	mov	es,bx
	mov	es:motor_count,377q ;don't time out yet
	mov	cl,ds:fddrv	;get drive #
	mov	al,1		;1 bit
	sal	al,cl		;shift into place
	mov	cl,es:motor_status ;get bits
	test	cl,al		;already on?
	jnz	fxfr4		;yes, skip this whole bit
	and	cl,not 17q	;turn off other bits
	or	cl,al		;set this one
	mov	es:motor_status,cl ;save
	mov	cl,4		;need to shift 4 more bits
	sal	al,cl
	or	al,14q	 ;set DMAEN, /DSELEN, clear SRST
	or	al,ds:fddrv	;OR in drive #
	port	fdcdor	 ;digital output register
	out	dx,al		;turn on motor
	; wait for spin-up if writing
	cmp	byte ptr ds:fdspn,0 ;need to spin up?
	jz	fxfr4		;no
	mov	cx,ds:fdmtr	;get spin-up time
fxfr2: mov	bx,es:timer_low ;get time
fxfr3: cmp	bx,es:timer_low ;has it changed?
	je	fxfr3		;spin until it does
	loop	fxfr2		;then count the tick
fxfr4: mov	es:motor_count,18d*2+1 ;2 sec motor timeout
	; see whether a seek is needed
	mov	al,ds:fdcyl	;get desired cyl
	mov	cl,1		;assume double-stepping
	cmp	byte ptr ds:fddbl,1 ;set CF if not
	sbb	cl,0		;1 if doubling, 0 if not
	sal	al,cl		;cyl *2 if double-stepping
	cmp	al,ds:fdcur	;are we there already?
	je	fxfr9		;yes
	xchg	al,ds:fdcur	;save if not
	cmp	al,-1		;do we even know where we are?
	jne	fxfr6		;yes
	; recal
	mov	si,offset necbuf ;pt at buf
	mov	ah,ds:fddrv	;get unit #
	mov	al,7		;cmd=recal
	mov	[si],ax	 ;save
	mov	cx,2		;length
	call	fdsek		;do recal operation
	jnc	fxfr6
	; recal failed, try once more before signaling error, since older FDCs
	; give up after 77 pulses and these days most drives have 80 tracks
	cmp	al,1		;seek error?
	jne	fxfr5		;no, give up
	mov	si,offset necbuf ;pt at buf
	mov	ah,ds:fddrv	;get unit #
	mov	al,7		;cmd=recal
	mov	[si],ax	 ;save
	mov	cx,2		;length
	call	fdsek		;do recal operation
	jnc	fxfr6
fxfr5: jmp	fxfr13	 ;failed
fxfr6: ; seek to cyl
	mov	si,offset necbuf ;pt at buffer
	mov	byte ptr [si],17q ;command=seek
	mov	al,ds:fdhd	;get head
	sal	al,1		;left 2
	sal	al,1
	or	al,ds:fddrv	;OR in drive #
	mov	ah,ds:fdcur	;cyl #
	mov	[si+1],ax	;save
	mov	cx,3		;length
	call	fdsek		;do seek operation
	jc	fxfr5		;failed
	; wait for head to settle
	mov	cx,2		;wait >=1 timer tick for head to settle
fxfr7: mov	bx,es:timer_low ;get time
fxfr8: cmp	bx,es:timer_low ;has it changed?
	je	fxfr8		;spin until it does
	loop	fxfr7		;then count the tick
fxfr9: ; send I/O command to FDC
	mov	al,byte ptr ds:fdcmd+1 ;get FDC command
	or	al,ds:fdden	;get density flag
	mov	si,offset necbuf ;point at where string goes
	mov	ah,ds:fdhd	;get head
	sal	ah,1		;left 2
	sal	ah,1
	or	ah,ds:fddrv	;OR in drive #
	mov	[si],ax	 ;head/drive,,cmd
	test	al,10q	 ;format cmd?
	jnz	fxfr10	 ;yes
	; read or write
	mov	al,ds:fdcyl	;cyl
	mov	[si+2],al
if 0
	mov	al,ds:fdhd	;head
else
	mov	bl,ds:fdhd	;head
	xor	bh,bh
	mov	al,ds:fdhdm[bx] ;look up in head map
endif
	mov	[si+3],al
	mov	al,ds:fdsec	;rec (sector)
	mov	[si+4],al
	mov	ax,word ptr ds:fdlen ;get EOT,,N
	mov	[si+5],ax
	mov	ax,word ptr ds:fdgpl ;get DTL,,GPL
	mov	[si+7],ax
	mov	cx,9d		;length
	jmp	short fxfr11
fxfr10: ; format
	mov	ax,word ptr ds:fdlen ;get secs/trk,,bytes/sec
;AANPASSING: SECTORS/TRACK=fdeot-secbas+1
	sub	ah,ds:secbas	;subtract base
;	add	ax,256d		;total # sectors per track
	add	ah,1		;total # sectors per track
;EINDE AANPASSING
	mov	[si+2],ax
	mov	ax,word ptr ds:fdgpf ;get filler byte,,gap 3 length
	mov	[si+4],ax
	mov	cx,6		;length
fxfr11: ; whichever, send command
	call	sfdc		;write it
	jc	fxfr13	 ;timeout
	call	fdcwt		;wait for interrupt
	jc	fxfr13	 ;timeout
	call	rfdc		;read status
	jc	fxfr13	 ;timeout
	test	byte ptr ds:necbuf,300q ;happy completion?
	jnz	fxfr12	 ;no
	pop	es
	ret
fxfr12: mov	al,2		;assume general error
	test	byte ptr ds:necbuf+1,2 ;write protect?
	jz	fxfr13
	mov	al,200q	 ;yes, no point in retrying
	pop	es		;return
	stc
	ret
fxfr13: ; timeout or error
	pop	es		;restore
	mov	byte ptr ds:fdcur,-1 ;force recal
	dec	ds:fdtry	;retry?
	jz	fxfr14	 ;no, fail
	jmp	fxfr1		;yes
fxfr14:	stc
	ret
;+
;
; Send a seek, recal, or reset command, and sense int status.
;
; Enter with ds:si, cx set up for SFDC.
;
;-
fdsek:	call	sfdc		;send command
	jc	fdsek1	 ;timeout
	call	fdcwt		;wait for completion
	jc	fdsek1	 ;timeout
	mov	si,offset necbuf ;pt at buffer
	mov	byte ptr [si],10q ;cmd=sense int status
	mov	cx,1		;length
	call	sfdc		;send
	jc	fdsek1	 ;timeout
	call	rfdc		;get result
	jc	fdsek1
	test	byte ptr ds:necbuf,300q ;happy termination?
	jz	fdsek1
	mov	al,1		;no, say seek error
	stc
fdsek1: ret
;+
;
; Set up DMA controller.
;
; al	mode (see 8237 (or NEC uPD71037) data sheet)
; es:bx address
; cx	byte count
;
; We don't check for spanning 64KB boundaries here because we checked our
; buffer when we allocated it
;
;-
dmaset: cli			;ints off
	out	0Bh,al	 ;;set mode reg
	out	0Ch,al	 ;;say low byte coming next
	; compute 20-bit absolute address
	mov	dx,es		;;get addr
	mov	ah,cl		;;save cl
	mov	cl,4		;;bit count
	rol	dx,cl		;;left 4 bits, catch high 4 in low 4
	mov	cl,ah		;;restore cl
	mov	al,dl		;;get them
	and	dl,360q	 ;;zero them out
	and	al,17q	 ;;isolate (not really needed in 20-bit sys)
	add	dx,bx		;;find base addr
	adc	al,0		;;add them in
	out	81h,al	 ;;set high 4 bits
	mov	al,dl		;;write low byte of addr
	out	04h,al	 ;;for channel 2
	mov	al,dh		;;high byte
	out	04h,al
	; write 16-bit byte count
	dec	cx		;;length -1
	mov	al,cl		;;write low byte of length
	out	05h,al	 ;;for channel 2
	mov	al,ch		;;get high byte
	out	05h,al
	mov	al,2		;;init DMA channel 2
	out	0Ah,al
	sti			;;ints back on
	ret
;+
;
; Send a string to the floppy disk controller.
;
; si,cx addr, length of string
;
; Returns CF=1 on timeout.
;
;-
sfdc:	push	es		;save es
	xor	ax,ax		;pt at BIOS data with es
	mov	es,ax
	and	es:seek_status,177q ;clear int bit
	port	fdcmsr	 ;main status register
	mov	ah,2		;timeout loop count (.GE. 1 timer tick)
sfdc1:	mov	bx,es:timer_low ;get time
sfdc2:	in	al,dx		;check it
	and	al,300q	 ;isolate high 2 bits
	jns	sfdc3		;skip if not ready for xfr
	test	al,100q	 ;ready for us to write?
	jnz	sfdc4		;no, it has something to say first
	port	fdcdat,fdcmsr	;data port
	lodsb			;get a byte
	out	dx,al
	port	fdcmsr,fdcdat	;restore
	loop	sfdc2		;loop
	pop	es		;restore
	clc			;happy
	ret
sfdc3: ; not ready, check for timeout
	cmp	bx,es:timer_low ;has time changed?
	je	sfdc2		;loop if not
	dec	ah		;-1
	jnz	sfdc1		;loop if not done (get new bx)
	pop	es		;restore
	xor	al,al		;error=timeout
	stc
	ret
sfdc4: ; FDC has something to say
	; (won't let us write anything until we've read it)
	port	fdcdat,fdcmsr	;data port
	in	al,dx		;that's nice dear
	port	fdcmsr,fdcdat	;status reg
	jmp	short sfdc2	;as I was saying...
;+
;
; Read FDC result data into NECBUF.
;
; CF=1 on timeout.
;
;-
rfdc:	push	es		;save
	xor	cx,cx		;pt at BIOS data
	mov	es,cx
	mov	cl,7		;should be only 7 bytes
	mov	di,offset necbuf ;pt at buf
	port	fdcmsr	 ;main status reg
	mov	ah,2		;timeout tick counter
rfdc1: mov	bx,es:timer_low ;get time
rfdc2: in	al,dx		;get status bits
	and	al,300q	 ;isolate request, direction
	jns	rfdc4		;not ready, see if timeout
	test	al,100q	 ;is it ready to squeal?
	jz	rfdc3		;no, guess it thinks we're done
	port	fdcdat,fdcmsr	;point at data port
	in	al,dx		;get data
	mov	[di],al	 ;save it
	inc	di
	port	fdcmsr,fdcdat	;back to status port
	loop	rfdc2		;loop
rfdc3: pop	es		;restore
	ret			;CF=0 from TEST above (either way)
rfdc4: ; not ready, see if timeout
	cmp	bx,es:timer_low ;has clock ticked
	je	rfdc2		;keep trying until it does
	dec	ah		;give up yet?
	jnz	rfdc1		;no, update bx and continue
	pop	es		;restore
	xor	al,al		;error=timeout
	stc
	ret
;+
;
; Wait for FDC completion interrupt.
;
; This is SOOO stupid, why not just poll the FDC?
;
; Return CF=1 if it takes more than 2 seconds.
;
;-
fdcwt: push	es		;save
	xor	cx,cx		;load 0 into es
	mov	es,cx
	mov	cl,18d*2+1	;tick count (2 sec)
fdcwt1: mov	ax,es:timer_low ;get timer
fdcwt2: test	es:seek_status,200q ;has the int happened?
	jnz	fdcwt3	 ;yes
	cmp	ax,es:timer_low ;has time changed?
	je	fdcwt2
	loop	fdcwt1	 ;yes, count the tick
	xor	al,al		;error=timeout
	stc
fdcwt3: pop	es		;[restore]
	ret
;
; Normally I like to stick all the data sections at the end of the program but
; I'm leaving the stuff having directly to do with FDC I/O here for ease in
; hacking it out and using it for other purposes.
;
; Below, when I say that something "MUST FOLLOW" something else, the reason is
; that somewhere in the code it saved some code to access two consecutive bytes
; as a word. So it's important that they stay consecutive, unless you fix the
; code (which will refer only to the first of the two labels).
;
necbuf db	9d dup(?)	;765 I/O buffer
fdden	db	1 dup(?)	;MFM for DD, FM for SD
fddbl	db	1 dup(?)	;NZ => double-step (40-trk disk on 80-trk drv)
fdcmd	dw	1 dup(?)	;FDC read/write/verify cmd,,DMA cmd
fdadr	dw	1 dup(?)	;offset of buffer addr
fdtry	db	1 dup(?)	;retry count
fdhdm	db	2 dup(?)	;head map (val to use in header for side 0,1)
;
fddrv	db	1 dup(?)	;drive # (0-3)
fdsec	db	1 dup(?)	;sector # (e.g. 1-26., format-dependent)
fdcyl	db	1 dup(?)	;cyl # (0-79.) (MUST FOLLOW FDSEC)
fdnum	db	1 dup(?)	;number of sectors to transfer
fdhd	db	1 dup(?)	;head # (0-1) (MUST FOLLOW FDNUM)
fdlen	db	1 dup(?)	;sector length (0=128., 1=256., 2=512.)
fdeot	db	1 dup(?)	;sectors/track (MUST FOLLOW FDLEN)
fdgpl	db	1 dup(?)	;gap 3 length
fddtl	db	1 dup(?)	;max transfer length (MUST FOLLOW FDGPL)
fdspc	dw	1 dup(?)	;"specify" cmd argument bytes (timing)
fdspn	db	1 dup(?)	;NZ => wait for motor to spin up before xfr
fdcur	db	1 dup(?)	;curr cyl or -1 if unknown
fdmtr	dw	1 dup(?)	;# ticks to wait for motor to turn on (write)
fdgpf	db	1 dup(?)	;gap 3 length for format
fdfil	db	1 dup(?)	;filler byte for format (MUST FOLLOW FDGPF)
fddcr	db	1 dup(?)	;value for disk control reg (3F7)
				;0 => 500kHz/360RPM (HD)
				;1 => 250kHz/300RPM or 300kHz/360RPM (DD in HD)
				;2 => 250kHz/300RPM (DD in DD)
				;3 => 1MHz/300RPM (ED)
;
; END OF PD FDC I/O CODE
;

	subttl pure data
;
banner db	'PC FDC program, version 0.13',cr,lf
	db	'Improved version of FDCDEMO by WHS',cr,lf,'$'

nomem	db	'?Not enough free memory',cr,lf
lnomem= $-nomem
prompt db	'FDC>$'
crlf	db	cr,lf
;

	subttl impure data
;
;My addition (WHS)
sides		db	2	;1=single sided disks (side 0), 2=both sides
errorflag	db	0	;NO errors to start
double_sided	db	1	;double sided to start with
ignore_errors	db	0

;
drive	db	1		;drive # (B: is usually 1.2MB these days)
buf	dw	buf1		;pointer to sector buffer
kbbuf	dw	buf2		;use the other one for the KB buffer
;
handle dw	-1		;-1, or handle for open file
;
numcyl db	80d		;# cylinders
numhd	db	1		;# heads
secbas db	1		;first sector #

;
; sector buffers:
buf1	db	bufl-1 dup(?)	;hopefully this buf won't span 64KB boundary
buf2	db	bufl dup(?)	;but if it does, then this one doesn't
;
lbuf	db	82d dup(?)	;line output buf (including 2 bytes for crlf)
;
cyl	db	1 dup(?)	;current cyl
head	db	1 dup(?)	;current head
sec	db	1 dup(?)	;current sector
;
secflg db	256d dup(?)	;sector flags (while figuring soft interleave)
secoff db	1 dup(?)	;current offset into SECFLG
secskw db	1 dup(?)	;# sectors to skew between cylinders
				;(to allow for stepping time)
secinc db	1 dup(?)	;amount by which to increment sector
seccnt dw	1 dup(?)	;sectors left to do (up to 256)
;
trkbuf db	25000d dup(?)	;track buffer (max possible)
				;(200,000 raw bits on a 2.88MB track)
;
	dw	100h dup(?)	;stack
pdl	label	word		;stack ends here (last loc in program)
;
;code	ends
	END	start


