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