;+
;
; FDC demo program.
;
; By John M. B. Wilson, <wilsonj@alumni.rpi.edu>, <wilson@tats.wizvax.net>.
;
; 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.
;
;-
	.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	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
;
code	segment
	assume	cs: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	inidz		;set initial values (for RX50, why not)
	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
	kw	<3-60KB>,kb360	;set 360KB parms
	kw	<7-20KB>,kb720	;set 720KB parms
	kw	<A:->,drva	;use first floppy
	kw	<B:->,drvb	;use second floppy
	kw	<BB-C>,bbc	;set BBC parms
	kw	<BY-TES>,setbyt	;set number of bytes per sector
	kw	<C:->,drvc	;use third floppy
	kw	<CY-LINDERS>,setcyl ;set number of cylinders
	kw	<D:->,drvd	;use fourth floppy
	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 (= "HEADS 2")
	kw	<E-D>,ed	;set speed register for 2.88MB drive
	kw	<FI-LL>,fill	;set fill byte for formatting
	kw	<FORMAT->,format ;format disk
	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	<HE-ADS>,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	<RE-AD>,read	;read entire disk to file
	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 (= "HEADS 1")
	kw	<ST-ATUS>,status ;display settings
	kw	<WRITE->,write	;write file to entire disk
	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.
;
;-
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: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:fdeot,18d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;
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:fdeot,9d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	ret
;
mb288:	; set 2.88MB disk parms
	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:fdeot,36d ;# 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: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: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
;
drva:	; select drive 0
	xor	al,al		;drive=0
	jmp	short seldrv
;
drvb:	; select drive 1
	mov	al,1		;drive=1
	jmp	short seldrv
;
drvc:	; select drive 2
	mov	al,2		;drive=2
	jmp	short seldrv
;
drvd:	; 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
;
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,1 ;SS
	mov	byte ptr ds:fdeot,9d ;max sector #
	mov	byte ptr ds:secbas,0 ;minimum sector=0
	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
	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'
;
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
	ret
;
ddinhd:	; set speed register for 360KB disk in 1.2MB drive
	mov	byte ptr ds:fddcr,1 ;300 kHz, 360 RPM
	ret
;
dblstp:	; double-step seeks
	mov	byte ptr ds:fddbl,1 ;set flag
	ret
;
setds:	; set double sided mode
	mov	byte ptr ds:numhd,2 ;# heads=2
	ret
;
ed:	; set speed register for 2.88MB drive
	mov	byte ptr ds:fddcr,43h ;1 MHz, 300 RPM
	ret
;
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'
;
format:	; format disk
	push	si		;save
	push	cx
	mov	byte ptr ds:cyl,0 ;init posn
	mov	byte ptr ds:head,0
	call	seczer		;compute skew
fmt1:	; start next track
	call	ptrack		;print track info
	; build format table
	mov	di,ds:buf	;point at buffer
	mov	bx,di		;save a copy
	mov	dl,ds:secbas	;starting sector #
fmt2:	mov	al,ds:cyl	;get cyl #
	mov	ah,ds:head	;head #
	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)
	call	fdftk		;format track
	jc	fmt5		;error
	; 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	fmt6		;none left, skip to next track
	mov	bx,ds:buf	;buffer
	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 fmt7
fmt5:	push	ax		;save error code
	call	pdone		;clear up mess
	pop	ax		;restore
	jmp	ioerr		;I/O error
fmt6:	call	trknxt		;bump to next track
	jnc	fmt1		;loop if not done
	call	pdone		;clean up PTRACK mess
	pop	cx		;restore
	pop	si
	ret
fmt7:	call	pdone		;clear up mess
	error	'?Verify error'
;
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
;
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	cl,ds:sec	;sector
	mov	ch,ds:cyl	;cyl
	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?
	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'
;
hd:	; set speed register for 1.2MB or 1.44MB drive
	mov	byte ptr ds:fddcr,0 ;500 kHz, 300/360 RPM
	ret
;
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'
;
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
;
minsec:	; set minimum sector number
	call	getn		;get number
	test	dx,dx		;should be 0
	jnz	msec1
	test	ah,ah
	jnz	msec1
	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'
;
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	cl,ds:sec	;sector
	mov	ch,ds:cyl	;cyl
	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
;
quit:	int	20h
;
read:	; read entire disk to file
	call	openw		;open file
	push	si		;save
	push	cx
	mov	byte ptr ds:cyl,0 ;init posn
	mov	byte ptr ds:head,0
	call	seczer		;init for cyl 0
read1:	; start next cyl
	call	ptrack		;print track info
	call	secini		;init sector interleave table
read2:	; read next sector
	call	secnxt		;get next sector #
	jc	read4		;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	read3
	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 read2	;loop
read3:	jmp	ioerr		;handle I/O error
read4:	; 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	read1		;loop if not done
	call	pdone		;clean up PTRACK mess
	call	close		;close file
	pop	cx		;restore
	pop	si
	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: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: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:fdeot,10d ;# sectors
	mov	byte ptr ds:secbas,1 ;minimum sector=1
	pop	cx		;restore
	pop	si
	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
;
setsd:	; set single density mode
	mov	byte ptr ds:fdden,fm ;FM
	ret
;
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'
;
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,177		;trim to ASCII
	cmp	al,177		;rubout?
	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
;
sngstp:	; single-step seeks
	mov	byte ptr ds:fddbl,0 ;clear flag
	ret
;
setss:	; set single sided mode
	mov	byte ptr ds:numhd,1 ;# heads=1
	ret
;
status:	; display current settings
	push	si		;save
	push	cx
	mov	di,offset lbuf	;point at line buffer
	mov	al,ds:drive	;get drive #
	add	al,'A'		;add base
	stosb
	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
	pop	cx		;restore
	pop	si
	ret
;
write:	; write file to entire disk
	call	openr		;open file
	push	si		;save
	push	cx
	mov	byte ptr ds:cyl,0 ;init posn
	mov	byte ptr ds:head,0
	call	seczer		;init for cyl 0
write1:	; 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
write2:	; write next sector
	call	secnxt		;get next sector #
	jc	write3		;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	write2		;do next sector
	jmp	ioerr
write3:	call	trknxt		;bump to next track
	jnc	write1		;loop if not done
	call	pdone		;clean up PTRACK mess
	call	close		;close file
	pop	cx		;restore
	pop	si
	ret
;+
;
; 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?
	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
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.
;
;-
seczer:	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
	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)
	rep	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
;+
;
; 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'
;+
;
; 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 begn 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)
	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)
	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
	jmp	fdini		;init, return
;
ini288:	; 2.88MB 3.5" disk (or DEC RX26)
	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,43h ;data rate = 1MHz/300RPM, vertical
	mov	byte ptr ds:fddbl,0 ;don't double-step
	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
	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
	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
	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
	jmp	short fdini	;init, return
;
inibbc:	; BBC DFS (5.25" SS SD 48tpi 10x256) 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,01h ;256. bytes/sec
	mov	byte ptr ds:fdeot,10d ;10. secs/trk
	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,1 ;data rate = 250kHz/300RPM or 300kHz/360RPM
	mov	byte ptr ds:fddbl,1 ;double-step (40-trk disk in 80-trk drv)
	;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
	mov	al,ds:fdhd	;head
	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
	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
;
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)
				;43h => 1MHz/300RPM (ED)
;
; END OF PD FDC I/O CODE
;
	subttl	pure data
;
banner	db	'IBM PC FDC demo program',cr,lf
	db	'By John Wilson <wilsonj@alumni.rpi.edu>',cr,lf,'$'
nomem	db	'?Not enough free memory',cr,lf
lnomem=	$-nomem
prompt	db	'FDCDEMO>$'
crlf	db	cr,lf
;
	subttl	impure data
;
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
