;+ ; ; FDC demo program. ; ; By John M. B. Wilson, . ; ; The latter part of this file contains a set of public domain FDC driver ; routines. ; ; The first part of this program contains a simple command line decoder which ; allows the user to perform simple disk operations; it is intended to provide ; a demonstration of how to call the FDC routines, and can also be useful for ; examining foreign disks, obtaining disk image files etc. ; ; 03/22/94 JMBW Created. ; 09/04/95 JMBW Added double-stepping flag for using 48tpi disks in ; 96tpi drives. ; 09/06/95 JMBW Added simple CLI to demonstrate FDC code. ; 09/12/95 JMBW Head map (for funny headers on BBC etc.). ; ;- .radix 8 ;of course! ; lf= 12 cr= 15 ; bufl= 1024d ;length of sector buffer ; ; Define a keyword record for TBLUK, given a string containing exactly one ; hyphen to show the minimum allowable abbreviation. ; db length to match ; db total length ; db 'KEYWORD' ;the keyword itself ; dw ADDR ;address returned from TBLUK on match kw macro text,addr kh= 0 ki= 0 irpc kc,text ki= ki+1 ifidn ,<-> 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 ,<-> 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 (doesn't work!) kw <3-60KB>,kb360 ;set 360KB parms kw <7-20KB>,kb720 ;set 720KB parms kw ,drva ;use first floppy kw ,drvb ;use second floppy kw ,bbc ;set BBC parms kw ,setbyt ;set number of bytes per sector kw ,drvc ;use third floppy kw ,setcyl ;set number of cylinders kw ,drvd ;use fourth floppy kw ,setdd ;set double density mode kw ,ddindd ;set speed register for 360KB drive kw ,ddinhd ;set speed reg for 360KB disk in 1.2MB drive kw ,dblstp ;double-step seeks kw ,setds ;set double sided (= "HEADS 2") kw ,ed ;set speed register for 2.88MB drive kw ,fill ;set fill byte for formatting kw ,format ;format disk kw ,gap ;set gap 3 length kw ,gapfmt ;set gap 3 length for format kw ,get ;get a sector from disk kw ,hd ;set speed register for 1.2MB or 1.44MB drive kw ,h0als ;set alias for head 0 kw ,h1als ;set alias for head 1 kw ,sethd ;set number of heads ;; kw ,itable ;set filename of interleave table kw ,load ;load sector buffer kw ,minsec ;set minimum sector # (usually 1) kw ,put ;put a sector on disk (spelled out for safety) kw ,quit ;quit to DOS kw ,read ;read entire disk to file kw ,rx01 ;set RX01 parms kw ,rx02 ;set RX02 parms kw ,rx50 ;set RX50 parms kw ,save ;save sector buffer in file kw ,setsd ;set single density mode kw ,setsec ;number of sectors kw ,show ;show sector buffer kw ,sngstp ;single-step seeks kw ,setss ;set single sided mode (= "HEADS 1") kw ,status ;display settings kw ,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 (not really!!!) mov al,ds:drive ;get drive # push si ;save push cx call ini288 ;init mov byte ptr ds:numcyl,80d ;# cyls mov byte ptr ds:numhd,2 ;DS mov byte ptr ds:fdeot,36d ;# sectors mov byte ptr ds:secbas,1 ;minimum sector=1 pop cx ;restore pop si error '?2.88MB support is not finished' ;; ret ; 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 mov byte ptr ds:fddcr,2 ;data rate = 250kHz/300RPM 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,3 ;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 ;copy mov dl,ds:secbas ;starting sector # mov bl,ds:head ;get head xor bh,bh ;high byte=0 mov dh,ds:fdhdm[bx] ;map head # fmt2: mov al,ds:cyl ;get cyl # mov ah,dh ;head # stosw ;C, H mov al,dl ;sector # mov ah,ds:fdlen ;size code stosw ;R, N inc dx ;R+1 jz fmt3 ;wrapped, must be done cmp dl,ds:fdeot ;done? jbe fmt2 ;loop if not fmt3: ; format the track mov ch,ds:cyl ;cyl mov cl,ds:secbas ;sector mov dh,ds:head ;head mov dl,1 ;sec cnt (sets DMA size enough for CHRN table) mov bx,ds:buf ;get buf ptr call fdftk ;format track jc fmt6 ;error ; verify mov al,ds:secskw ;get sector-to-sector skew add al,ds:secbas ;add sector base mov ds:secoff,al ;set starting sector call secini ;init sector interleave table fmt4: ; write next sector call secnxt ;get next sector # jc fmt5 ;none left, skip to next track mov bx,ds:buf ;get buf ptr mov ch,ds:cyl ;cyl mov cl,ds:sec ;sector mov dh,ds:head ;head mov dl,1 ;sec cnt call fdrds ;read sector (to verify) jnc fmt4 ;do next sector jmp short fmt7 fmt5: call trknxt ;bump to next track jnc fmt1 ;loop if not done call pdone ;clean up PTRACK mess pop cx ;restore pop si ret fmt6: push ax ;save error code call pdone ;clear up mess pop ax ;restore jmp ioerr ;I/O error 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 ch,ds:cyl ;cyl mov cl,ds:sec ;sector mov dh,ds:head ;head mov dl,1 ;sec cnt call fdrds ;read the sector pop cx ;[restore] pop si jc get1 ;error ret get1: jmp ioerr ;+ ; ; Get parms for GET/PUT ; ; GET cyl head sec -or- ; GET cyl sec if single-sided disk ; ; si, cx updated on return, values set in DS:CYL, DS:HEAD, DS:SEC. ; ;- getput: ; get GET/PUT parms (CYL [HEAD] SEC) call getn ;get cyl test dx,dx ;must be 0 jnz gtpt2 test ah,ah jnz gtpt2 cmp al,ds:numcyl ;< # cyls? jae gtpt2 mov ds:cyl,al ;save mov ds:head,ah ;init head in case single sided cmp byte ptr ds:numhd,1 ;is it? 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 ; h0als: ; set head 0 alias (i.e. "H" value used in headers on side 0) call getn ;get number xor bx,bx ;head # h0als1: test dx,dx ;too big? jnz h0als2 test ah,ah jnz h0als2 mov ds:fdhdm[bx],al ;save ret h0als2: error '?Invalid alias for head number' ; h1als: ; set head 1 alias (i.e. "H" value used in headers on side 1) call getn ;get number mov bx,1 ;head # jmp short h0als1 ; 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 ch,ds:cyl ;cyl mov cl,ds:sec ;sector mov dh,ds:head ;head mov dl,1 ;sec cnt call fdwrs ;write the sector pop cx ;[restore] pop si jc put1 ;error ret put1: jmp ioerr ; 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 3 stat8: call flush ;flush line cram 'HEAD0ALIAS ' mov al,ds:fdhdm ;get alias for head 0 call putb ;print cram ' HEAD1ALIAS ' mov al,ds:fdhdm+1 ;get alias for head 1 call putb ;print call flush ;flush line 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 , 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 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 ; 139 Stafford Road ; Monson, MA 01057-9315 USA ; +1 (413) 267-4197 ; ; ; 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: ; fdcfcr= 3F1h ;FDC format control register (SMC FDC37C65C+ only) ;(3F6 on my homebrew multi-I/O board) fdcdor= 3F2h ;FDC digital output register fdcmsr= 3F4h ;FDC main status register fdcdat= 3F5h ;FDC data register 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 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,2Ah ;gap length=42. mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdcur,-1 ;curr cyl is unknown mov byte ptr ds:fdden,mfm ;double density mov byte ptr ds:fdgpf,50h ;gap length (format)=80. mov byte ptr ds:fdfil,0F6h ;fill=F6 mov byte ptr ds:fddcr,1 ;data rate = 250kHz/300RPM or 300kHz/360RPM mov byte ptr ds:fddbl,1 ;probably double-step (they can change it) mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp fdini ;init, return ; ini720: ; 720KB 3.5" or 5.25" disk mov ds:fddrv,al ;save mov word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA mov byte ptr ds:fdlen,02h ;512. bytes/sec mov byte ptr ds:fdeot,9d ;9. secs/trk mov byte ptr ds:fdgpl,23h ;gap length=35. mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdcur,-1 ;curr cyl is unknown mov byte ptr ds:fdden,mfm ;double density mov byte ptr ds:fdgpf,50h ;gap length (format)=80. mov byte ptr ds:fdfil,0F6h ;fill=F6 mov byte ptr ds:fddcr,1 ;data rate = 250kHz/300RPM or 300kHz/360RPM mov byte ptr ds:fddbl,1 ;probably double-step (they can change it) mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp fdini ;init, return ; ini144: ; 1.44MB 3.5" disk (or DEC RX23) mov ds:fddrv,al ;save ;;; mov word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA ;;; should be 02CFh according to Linux floppy.c, need to look up meaning mov word ptr ds:fdspc,02CFh mov byte ptr ds:fdlen,02h ;512. bytes/sec mov byte ptr ds:fdeot,18d ;18. secs/trk mov byte ptr ds:fdgpl,1Bh ;gap length=27. mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdden,mfm ;double density mov byte ptr ds:fdgpf,6Ch ;gap length (format)=108. mov byte ptr ds:fdfil,0F6h ;fill=F6 mov byte ptr ds:fddcr,0 ;data rate = 500kHz/300RPM mov byte ptr ds:fddbl,0 ;don't double-step mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp fdini ;init, return ; ini288: ; 2.88MB 3.5" disk (or DEC RX26) ;;; doesn't really work yet, it seems things aren't this simple mov ds:fddrv,al ;save ;;; mov word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA ;;; should be 02AFh according to Linux floppy.c, need to look up meaning mov word ptr ds:fdspc,02AFh mov byte ptr ds:fdlen,02h ;512. bytes/sec mov byte ptr ds:fdeot,36d ;36. secs/trk mov byte ptr ds:fdgpl,1Bh ;gap length=27. mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdden,mfm ;double density mov byte ptr ds:fdgpf,54h ;gap length (format)=84. mov byte ptr ds:fdfil,0F6h ;fill=F6 mov byte ptr ds:fddcr,3 ;data rate = 1MHz/300RPM mov byte ptr ds:fddbl,0 ;don't double-step mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp fdini ;init, return ; ini12: ; 1.2MB 5.25" disk (or DEC RX33) mov ds:fddrv,al ;save mov word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA mov byte ptr ds:fdlen,02h ;512. bytes/sec mov byte ptr ds:fdeot,15d ;15. secs/trk mov byte ptr ds:fdgpl,1Bh ;gap length=27. mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdden,mfm ;double density mov byte ptr ds:fdgpf,54h ;gap length (format)=84. mov byte ptr ds:fdfil,0F6h ;fill=F6 mov byte ptr ds:fddcr,0 ;data rate = 500kHz/360RPM mov byte ptr ds:fddbl,0 ;don't double-step mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp fdini ;init, return ; inidx: ; RX01 (8" SS SD) equiv in 1.2MB drive mov ds:fddrv,al ;save mov word ptr ds:fdspc,06BFh ;SRT=10ms, HUT=480ms, HLT=16ms, DMA mov byte ptr ds:fdlen,00h ;128. bytes/sec mov byte ptr ds:fdeot,26d ;26. secs/trk mov byte ptr ds:fdgpl,07h ;gap length=7 mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdcur,-1 ;curr cyl is unknown mov byte ptr ds:fdden,fm ;single density mov byte ptr ds:fdgpf,1Bh ;gap length (format)=27. mov byte ptr ds:fdfil,0E5h ;fill=E5 mov byte ptr ds:fddcr,0 ;data rate = 250kHz/360RPM mov byte ptr ds:fddbl,0 ;don't double-step mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp fdini ;init, return ; inidy: ; RX02 (8" SS DD) or so-called "RX03" (8" DS DD) equiv in 1.2MB drive mov ds:fddrv,al ;save mov word ptr ds:fdspc,06BFh ;SRT=10ms, HUT=480ms, HLT=16ms, DMA mov byte ptr ds:fdlen,01h ;256. bytes/sec mov byte ptr ds:fdeot,26d ;26. secs/trk mov byte ptr ds:fdgpl,0Eh ;gap length=14. mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdcur,-1 ;curr cyl is unknown mov byte ptr ds:fdden,mfm ;double density mov byte ptr ds:fdgpf,36h ;gap length (format)=54. mov byte ptr ds:fdfil,0E5h ;fill=E5 mov byte ptr ds:fddcr,0 ;data rate = 500kHz/360RPM mov byte ptr ds:fddbl,0 ;don't double-step mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp fdini ;init, return ; inidz: ; RX50 (5.25" SS DD 96tpi) in 1.2MB drive mov ds:fddrv,al ;save mov word ptr ds:fdspc,02DFh ;SRT=6ms, HUT=480ms, HLT=8ms, DMA mov byte ptr ds:fdlen,02h ;512. bytes/sec mov byte ptr ds:fdeot,10d ;10. secs/trk mov byte ptr ds:fdgpl,14h ;gap length=20. mov byte ptr ds:fddtl,0FFh ;max xfr=255. secs mov word ptr ds:fdmtr,18d ;1 second motor spin-up time mov byte ptr ds:fdcur,-1 ;curr cyl is unknown mov byte ptr ds:fdden,mfm ;double density mov byte ptr ds:fdgpf,18h ;gap length (format)=24. mov byte ptr ds:fdfil,0E5h ;fill=E5 mov byte ptr ds:fddcr,1 ;data rate = 250kHz/300RPM or 300kHz/360RPM mov byte ptr ds:fddbl,0 ;don't double-step mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) jmp short fdini ;init, return ; inibbc: ; BBC DFS (5.25" 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) mov word ptr ds:fdhdm,100h ;head map={0,1} (identity map) ;jmp short fdini ;init, return ;+ ; ; Init floppy I/O. ; ;- fdini: ; build digital output register value push es ;save xor ax,ax ;load 0 into es mov es,ax mov al,es:motor_status ;get motor bits pop es ;restore and al,17q ;isolate mov cx,4 ;loop count mov ah,10q ;assume drive 0, set IE fdini1: ror al,1 ;right a bit jnc fdini2 mov ah,14q ;calc unit # (leave IE set) sub ah,cl fdini2: loop fdini1 ;loop through all 4 bits or al,ah ;OR it in (with IE) ; hard reset FDC (it might be in some new mode we don't know about) port fdcdor ;digital output register cli ;ints off (so timer ISR doesn't screw with us) out dx,al ;;hard reset jmp short $+2 ;;miniscule I/O delay or al,4 ;;reenable sti ;;(ints on after next) out dx,al ;;enable FDC ; set data rate port fdcdcr,fdcdor ;pt at port mov al,ds:fddcr ;set value out dx,al ; send specify command (set timing for this drive) mov si,offset necbuf ;pt at buf mov byte ptr [si],3 ;cmd=specify mov ax,ds:fdspc ;get specify bytes mov [si+1],ax ;save mov cx,3 ;length call sfdc ;send to FDC ret ; fdrds: ; read sector(s) mov byte ptr ds:fdspn,0 ;no spin-up needed (retry instead) mov ax,0A646h ;multitrack, skip deleted data, read data jmp short fdxfr ;(doesn't work w/o multitrack bit!) ; fdwrs: ; write sector(s) mov byte ptr ds:fdspn,-1 ;spin up before writing mov ax,854Ah ;multitrack, write sector,,DMA cmd jmp short fdxfr ;write, return ; if 0 ; this is never used fdvfs: ; verify sector(s) mov byte ptr ds:fdspn,0 ;no spin-up needed (retry instead) mov ax,0A642h ;same as read w/different cmd to DMAC jmp short fdxfr ;verify, return endif ; fdftk: ; format track mov byte ptr ds:fdspn,-1 ;spin up before writing mov ax,0D4Ah ;format track,,DMA cmd ;jmp short fdxfr ;do it, return ;+ ; ; Perform a floppy transfer. ; ; al DMA command byte ; ah FDC command byte ; es:bx buffer (must not span 64KB boundary -- not checked) ; cl sector ; ch cyl ; dh head ; dl block count ; ; Returns CF=0 on success, otherwise CF=1 and al contains error code: ; 0 controller timeout (hardware failure) ; 1 seek error ; 2 some kind of transfer error (sector not found, fault) ; 200q write protect ; ; ** N.B. ** ; It is *ESSENTIAL* that you guarantee that the buffer addressed by ES:BX is ; located in memory such that the disk data (or format info for FDFTK) being ; read or written do not cross a 64KB address boundary. Which is to say, when ; the absolute addresses of the first and last bytes transferred are expressed ; as flat 5-digit hex numbers, the first digits must be the same. So you must ; check the buffer before using it, and if it spans a 64KB boundary, throw it ; away and allocate another buffer. ; ; The reason for this is that the PC DMA controllers (8237) don't have carry ; between bits 15 and 16 of the address, since bits 0-15 are in the 8237 and ; bits 16 and up are in an external 74LS612. So for example, after a byte at ; 7FFFF is transferred, instead of proceeding to 80000 the address wraps around ; to 70000, resulting in corruption of memory or of the disk. ; ; ** YOU HAVE BEEN WARNED ** ; If you don't worry about this you'll probably get lucky and your program will ; work most of the time (like RAWRITE.EXE which comes with Linux and has had ; this bug for years). But then if you run it on a system with a different ; version of DOS, or with different device drivers and TSRs loaded, you lose. ; ;- fdxfr: mov ds:fdcmd,ax ;save FDC cmd mov word ptr ds:fdsec,cx ;and cyl,,sec mov word ptr ds:fdnum,dx ;and head,,blk count mov ds:fdadr,bx ;and addr mov ds:fdtry,5 ;init retry counter fxfr1: ; compute length mov cl,ds:fdlen ;get sector length (0=128., 1=256., 2=512.) add cl,7 ;correct mov dl,ds:fdnum ;get sector count xor dh,dh ;dh=0 sal dx,cl ;find byte count mov cx,dx ;copy mov al,byte ptr ds:fdcmd ;get DMA command mov bx,ds:fdadr ;get addr (ES is still correct) call dmaset ;set up DMA controller ;(count will be too high if formatting, but...) ; start motor unless already running push es ;save xor bx,bx ;load 0 into es mov es,bx mov es:motor_count,377q ;don't time out yet mov cl,ds:fddrv ;get drive # mov al,1 ;1 bit sal al,cl ;shift into place mov cl,es:motor_status ;get bits test cl,al ;already on? jnz fxfr4 ;yes, skip this whole bit and cl,not 17q ;turn off other bits or cl,al ;set this one mov es:motor_status,cl ;save mov cl,4 ;need to shift 4 more bits sal al,cl or al,14q ;set DMAEN, /DSELEN, clear SRST or al,ds:fddrv ;OR in drive # port fdcdor ;digital output register out dx,al ;turn on motor ; wait for spin-up if writing cmp byte ptr ds:fdspn,0 ;need to spin up? jz fxfr4 ;no mov cx,ds:fdmtr ;get spin-up time fxfr2: mov bx,es:timer_low ;get time fxfr3: cmp bx,es:timer_low ;has it changed? je fxfr3 ;spin until it does loop fxfr2 ;then count the tick fxfr4: mov es:motor_count,18d*2+1 ;2 sec motor timeout ; see whether a seek is needed mov al,ds:fdcyl ;get desired cyl mov cl,1 ;assume double-stepping cmp byte ptr ds:fddbl,1 ;set CF if not sbb cl,0 ;1 if doubling, 0 if not sal al,cl ;cyl *2 if double-stepping cmp al,ds:fdcur ;are we there already? je fxfr9 ;yes xchg al,ds:fdcur ;save if not cmp al,-1 ;do we even know where we are? jne fxfr6 ;yes ; recal mov si,offset necbuf ;pt at buf mov ah,ds:fddrv ;get unit # mov al,7 ;cmd=recal mov [si],ax ;save mov cx,2 ;length call fdsek ;do recal operation jnc fxfr6 ; recal failed, try once more before signaling error, since older FDCs ; give up after 77 pulses and these days most drives have 80 tracks cmp al,1 ;seek error? jne fxfr5 ;no, give up mov si,offset necbuf ;pt at buf mov ah,ds:fddrv ;get unit # mov al,7 ;cmd=recal mov [si],ax ;save mov cx,2 ;length call fdsek ;do recal operation jnc fxfr6 fxfr5: jmp fxfr13 ;failed fxfr6: ; seek to cyl mov si,offset necbuf ;pt at buffer mov byte ptr [si],17q ;command=seek mov al,ds:fdhd ;get head sal al,1 ;left 2 sal al,1 or al,ds:fddrv ;OR in drive # mov ah,ds:fdcur ;cyl # mov [si+1],ax ;save mov cx,3 ;length call fdsek ;do seek operation jc fxfr5 ;failed ; wait for head to settle mov cx,2 ;wait >=1 timer tick for head to settle fxfr7: mov bx,es:timer_low ;get time fxfr8: cmp bx,es:timer_low ;has it changed? je fxfr8 ;spin until it does loop fxfr7 ;then count the tick fxfr9: ; send I/O command to FDC mov al,byte ptr ds:fdcmd+1 ;get FDC command or al,ds:fdden ;get density flag mov si,offset necbuf ;point at where string goes mov ah,ds:fdhd ;get head sal ah,1 ;left 2 sal ah,1 or ah,ds:fddrv ;OR in drive # mov [si],ax ;head/drive,,cmd test al,10q ;format cmd? jnz fxfr10 ;yes ; read or write mov al,ds:fdcyl ;cyl mov [si+2],al if 0 mov al,ds:fdhd ;head else mov bl,ds:fdhd ;head xor bh,bh mov al,ds:fdhdm[bx] ;look up in head map endif mov [si+3],al mov al,ds:fdsec ;rec (sector) mov [si+4],al mov ax,word ptr ds:fdlen ;get EOT,,N mov [si+5],ax mov ax,word ptr ds:fdgpl ;get DTL,,GPL mov [si+7],ax mov cx,9d ;length jmp short fxfr11 fxfr10: ; format mov ax,word ptr ds:fdlen ;get secs/trk,,bytes/sec mov [si+2],ax mov ax,word ptr ds:fdgpf ;get filler byte,,gap 3 length mov [si+4],ax mov cx,6 ;length fxfr11: ; whichever, send command call sfdc ;write it jc fxfr13 ;timeout call fdcwt ;wait for interrupt jc fxfr13 ;timeout call rfdc ;read status jc fxfr13 ;timeout test byte ptr ds:necbuf,300q ;happy completion? jnz fxfr12 ;no pop es ret fxfr12: mov al,2 ;assume general error test byte ptr ds:necbuf+1,2 ;write protect? jz fxfr13 mov al,200q ;yes, no point in retrying pop es ;return stc ret fxfr13: ; timeout or error pop es ;restore mov byte ptr ds:fdcur,-1 ;force recal dec ds:fdtry ;retry? jz fxfr14 ;no, fail jmp fxfr1 ;yes fxfr14: stc ret ;+ ; ; Send a seek, recal, or reset command, and sense int status. ; ; Enter with ds:si, cx set up for SFDC. ; ;- fdsek: call sfdc ;send command jc fdsek1 ;timeout call fdcwt ;wait for completion jc fdsek1 ;timeout mov si,offset necbuf ;pt at buffer mov byte ptr [si],10q ;cmd=sense int status mov cx,1 ;length call sfdc ;send jc fdsek1 ;timeout call rfdc ;get result jc fdsek1 test byte ptr ds:necbuf,300q ;happy termination? jz fdsek1 mov al,1 ;no, say seek error stc fdsek1: ret ;+ ; ; Set up DMA controller. ; ; al mode (see 8237 (or NEC uPD71037) data sheet) ; es:bx address ; cx byte count ; ; We don't check for spanning 64KB boundaries here because we checked our ; buffer when we allocated it ; ;- dmaset: cli ;ints off out 0Bh,al ;;set mode reg out 0Ch,al ;;say low byte coming next ; compute 20-bit absolute address mov dx,es ;;get addr mov ah,cl ;;save cl mov cl,4 ;;bit count rol dx,cl ;;left 4 bits, catch high 4 in low 4 mov cl,ah ;;restore cl mov al,dl ;;get them and dl,360q ;;zero them out and al,17q ;;isolate (not really needed in 20-bit sys) add dx,bx ;;find base addr adc al,0 ;;add them in out 81h,al ;;set high 4 bits mov al,dl ;;write low byte of addr out 04h,al ;;for channel 2 mov al,dh ;;high byte out 04h,al ; write 16-bit byte count dec cx ;;length -1 mov al,cl ;;write low byte of length out 05h,al ;;for channel 2 mov al,ch ;;get high byte out 05h,al mov al,2 ;;init DMA channel 2 out 0Ah,al sti ;;ints back on ret ;+ ; ; Send a string to the floppy disk controller. ; ; si,cx addr, length of string ; ; Returns CF=1 on timeout. ; ;- sfdc: push es ;save es xor ax,ax ;pt at BIOS data with es mov es,ax and es:seek_status,177q ;clear int bit port fdcmsr ;main status register mov ah,2 ;timeout loop count (.GE. 1 timer tick) sfdc1: mov bx,es:timer_low ;get time sfdc2: in al,dx ;check it and al,300q ;isolate high 2 bits jns sfdc3 ;skip if not ready for xfr test al,100q ;ready for us to write? jnz sfdc4 ;no, it has something to say first port fdcdat,fdcmsr ;data port lodsb ;get a byte out dx,al port fdcmsr,fdcdat ;restore loop sfdc2 ;loop pop es ;restore clc ;happy ret sfdc3: ; not ready, check for timeout cmp bx,es:timer_low ;has time changed? je sfdc2 ;loop if not dec ah ;-1 jnz sfdc1 ;loop if not done (get new bx) pop es ;restore xor al,al ;error=timeout stc ret sfdc4: ; FDC has something to say ; (won't let us write anything until we've read it) port fdcdat,fdcmsr ;data port in al,dx ;that's nice dear port fdcmsr,fdcdat ;status reg jmp short sfdc2 ;as I was saying... ;+ ; ; Read FDC result data into NECBUF. ; ; CF=1 on timeout. ; ;- rfdc: push es ;save xor cx,cx ;pt at BIOS data mov es,cx mov cl,7 ;should be only 7 bytes mov di,offset necbuf ;pt at buf port fdcmsr ;main status reg mov ah,2 ;timeout tick counter rfdc1: mov bx,es:timer_low ;get time rfdc2: in al,dx ;get status bits and al,300q ;isolate request, direction jns rfdc4 ;not ready, see if timeout test al,100q ;is it ready to squeal? jz rfdc3 ;no, guess it thinks we're done port fdcdat,fdcmsr ;point at data port in al,dx ;get data mov [di],al ;save it inc di port fdcmsr,fdcdat ;back to status port loop rfdc2 ;loop rfdc3: pop es ;restore ret ;CF=0 from TEST above (either way) rfdc4: ; not ready, see if timeout cmp bx,es:timer_low ;has clock ticked je rfdc2 ;keep trying until it does dec ah ;give up yet? jnz rfdc1 ;no, update bx and continue pop es ;restore xor al,al ;error=timeout stc ret ;+ ; ; Wait for FDC completion interrupt. ; ; This is SOOO stupid, why not just poll the FDC? ; ; Return CF=1 if it takes more than 2 seconds. ; ;- fdcwt: push es ;save xor cx,cx ;load 0 into es mov es,cx mov cl,18d*2+1 ;tick count (2 sec) fdcwt1: mov ax,es:timer_low ;get timer fdcwt2: test es:seek_status,200q ;has the int happened? jnz fdcwt3 ;yes cmp ax,es:timer_low ;has time changed? je fdcwt2 loop fdcwt1 ;yes, count the tick xor al,al ;error=timeout stc fdcwt3: pop es ;[restore] ret ; ; Normally I like to stick all the data sections at the end of the program but ; I'm leaving the stuff having directly to do with FDC I/O here for ease in ; hacking it out and using it for other purposes. ; ; Below, when I say that something "MUST FOLLOW" something else, the reason is ; that somewhere in the code it saved some code to access two consecutive bytes ; as a word. So it's important that they stay consecutive, unless you fix the ; code (which will refer only to the first of the two labels). ; necbuf db 9d dup(?) ;765 I/O buffer fdden db 1 dup(?) ;MFM for DD, FM for SD fddbl db 1 dup(?) ;NZ => double-step (40-trk disk on 80-trk drv) fdcmd dw 1 dup(?) ;FDC read/write/verify cmd,,DMA cmd fdadr dw 1 dup(?) ;offset of buffer addr fdtry db 1 dup(?) ;retry count fdhdm db 2 dup(?) ;head map (val to use in header for side 0,1) ; fddrv db 1 dup(?) ;drive # (0-3) fdsec db 1 dup(?) ;sector # (e.g. 1-26., format-dependent) fdcyl db 1 dup(?) ;cyl # (0-79.) (MUST FOLLOW FDSEC) fdnum db 1 dup(?) ;number of sectors to transfer fdhd db 1 dup(?) ;head # (0-1) (MUST FOLLOW FDNUM) fdlen db 1 dup(?) ;sector length (0=128., 1=256., 2=512.) fdeot db 1 dup(?) ;sectors/track (MUST FOLLOW FDLEN) fdgpl db 1 dup(?) ;gap 3 length fddtl db 1 dup(?) ;max transfer length (MUST FOLLOW FDGPL) fdspc dw 1 dup(?) ;"specify" cmd argument bytes (timing) fdspn db 1 dup(?) ;NZ => wait for motor to spin up before xfr fdcur db 1 dup(?) ;curr cyl or -1 if unknown fdmtr dw 1 dup(?) ;# ticks to wait for motor to turn on (write) fdgpf db 1 dup(?) ;gap 3 length for format fdfil db 1 dup(?) ;filler byte for format (MUST FOLLOW FDGPF) fddcr db 1 dup(?) ;value for disk control reg (3F7) ;0 => 500kHz/360RPM (HD) ;1 => 250kHz/300RPM or 300kHz/360RPM (DD in HD) ;2 => 250kHz/300RPM (DD in DD) ;3 => 1MHz/300RPM (ED) fdvrt db 1 dup(?) ;NZ => Toshiba vertical mode (2.88MB) ; ; END OF PD FDC I/O CODE ; subttl pure data ; banner db 'IBM PC FDC demo program',cr,lf db 'By John Wilson ',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