title 'Tarbell double density diskette handler' ; Copyright (c) 1983 Tarbell Electronics ; version 1.1 , 6-29-83 ; cp/m-80 version 3 -- modular bios ;**************************************************************** ; 6-29-83 changed non-D.R. Z-80 opcodes to match Z80.LIB * ; 6-29-83 added write protect feature reporting to bdos * ; 6-29-83 made @dskbf public access * ;**************************************************************** true equ -1 false equ 0 ;note - these routines are implied to be and must be in physical ;memory 00000h to 0ffffh and no translation is permitted ;ensure that bank 0 of the memory select table is contiguous dseg ; disk drive dispatching tables for linked bios. public fd0,fd1,fd2,fd3 ; local 512 byte buffer for use by other modules. public @dskbf ; variables containing parameters passed by bdos. extrn @adrv,@rdrv extrn @dma,@trk,@sect extrn @dbnk ; system control block variables. extrn @ermde ; bdos error mode ; utility routines in standard bios. extrn ?wboot ; warm boot vector. extrn ?pmsg ; print message @ up to 00. extrn ?pdec ; print number in from 0 to 99. extrn ?pderr ; print bios disk error header. extrn ?conin,?cono ; console in and out. extrn ?const ; get console status. ; routines in move module extrn ?move ; data block move extrn ?xmove ; interbank move setup extrn ?gndma ; generates real dma address extrn ?ax16 ; multiplies A by 16 ; port address equates. stprat equ 0 ;0=3ms,1=6ms,2=15ms,3=20ms(stp rate) hlab equ 8 ;0=no head load at beginning of seek dual equ false ;true if using a persci drive ercnt equ 10 ;error retry count ; ; dmap equ 0e0h ;dma port base address wct0 equ dmap+1 ;dma word count register cmnd equ dmap+8 ;dma command register dadr equ 0f8h ;disk address dcom equ dadr ;disk command port dstat equ dadr ;disk status port track equ dadr+1 ;disk track register sectp equ dadr+2 ;disk sector register ddata equ dadr+3 ;disk data register wait equ dadr+4 ;in=wait for data-interrupt ddsel equ dadr+4 ;out=drive and density select dmachk equ dadr+5 ;in=non-wait status port extdma equ dadr+5 ;out=extended dma address ; dmar equ 01000000b ;dma read command dmaw equ 10000000b ;dma write command fread equ 10000000b ;floppy read command fwrit equ 10100000b ;floppy write command fwritd equ 10100100b ;floppy write with 15ms delay rdid equ 11000000b ;read id command b128 equ 128 ;sgl / dbl den. sect size b512 equ 512 ;deblock sector size ; ; cp/m 3 disk definition macros. maclib cpm3 ; z80 macro library instruction definitions. maclib z80 ; common control characters. cr equ 13 lf equ 10 bell equ 7 ; extended disk parameter headers (xpdhs). dw fd$write dw fd$read dw fd$login dw fd$init db 0,0 ; relative drive 0. fd0: dph dbtrans,dd512,44,78 dw fd$write dw fd$read dw fd$login dw fd$init db 1,0 ; relative drive 1. fd1: dph dbtrans,dd512,44,78 dw fd$write dw fd$read dw fd$login dw fd$init db 2,0 ; relative drive 2. fd2: dph dbtrans,dd512,44,78 dw fd$write dw fd$read dw fd$login dw fd$init db 3,0 ; relative drive 3. fd3: dph dbtrans,dd512,44,78 ; dpb must be resident in common. cseg ss128: dpb 128,26,77,1024,64,2 ;single density single sided ;128 bytes per sector ds128: ;double density single sided ;128 bytes per sector dw 51 ;sectors per track db 4 ;block shift factor db 15 ;block mask db 0 ;extent mask dw 237 ;disk size-1 dw 127 ;directory size-1 db 11000000b ;al0 db 00000000b ;al1 dw 32 ;check size dw 2 ;track offset db 0 ;psh db 0 ;phm ds512: ;double density single sided ;512 bytes per sector dw 64 ;sectors per track db 4 ;block shift factor db 15 ;block mask db 0 ;extent mask dw 299 ;disk size-1 dw 127 ;directory size-1 db 11000000b ;al0 db 00000000b ;al1 dw 32 ;check size dw 2 ;track offset db 2 ;psh db 3 ;phm sd128: ;single density double sided ;128 bytes per sector dw 26 ;sectors per track db 4 ;block shift factor db 15 ;block mask db 0 ;extent mask dw 242 ;disk size-1 dw 95 ;directory size-1 db 11000000b ;al0 db 00000000b ;al1 dw 24 ;check size dw 2 ;track offset db 0 ;psh db 0 ;phm dd128: ;double density double sided ;128 bytes per sector dw 51 ;sectors per track db 5 ;block shift factor db 31 ;block mask db 0 ;extent mask dw 237 ;disk size-1 dw 95 ;directory size-1 db 11000000b ;al0 db 00000000b ;al1 dw 24 ;check size dw 2 ;track offset db 0 ;psh db 0 ;phm dd512: ;double density double sided ;512 bytes per sector dw 64 ;sectors per track db 5 ;block shift factor db 31 ;block mask db 0 ;extent mask dw 303 ;disk size-1 dw 169 ;directory size-1 db 11000000b ;al0 db 00000000b ;al1 dw 42 ;check size dw 2 ;track offset db 2 ;psh db 3 ;phm dseg ; rest is banked trans skew 26,6,1 ;generate translate table for single density ddtrans skew 51,1,1 ;generate translate table for double density dbtrans skew 16,1,1 ;generate translate table for 512 x 16 ; disk i/o routines for standardized bios interface ; initialization entry point. ; called for first time initialization. fd$init: ; all initialization done by drive 0 ret fd$login: ; this entry is called when a logical drive is about to ; be logged into for the purpose of density determination. ; it may adjust the parameters contained in the disk ; parameter header pointed at by push d ;save pointer to dph call gethf ;get hardware format pointer inx h ;get disk pointer address lda @rdrv ;get relative drive # call ?ax16 ;shift to upper nibble mov m,a ;clear disk select byte for this drive push h ;save disk pointer address call get$id ;get the id byte from @rdrv jnz id$error ;return select error mov e,a ;save id in E lda @rdrv ;get drive # lxi h,drive$ids ;point to drive id storage mov c,a mvi b,0 ;generate offset dad b ;add in offset mov a,e ;get disk id back mov m,a ;save for future reference lxi h,disk$ids ;hl points to list of disk id's lxi b,6 ;six of them to check ccir ;check untill we find one lxi h,dph$patches ;hl points to patch words for the dph dad b ;and disk format word dad b dad b ;add b in 6 times for 6 bytes dad b dad b dad b ; hl now points to selected format word ; ; 0 = unlogged ; 1 = single density ; 2 = double density (51 secs - 128 b) ; 3 = double density (16 secs - 512 b) ; ;example: ;for double density 512x16: ; ; db 3 * 256 + 8 ; ;3 = use 512 byte sector size (hl+1) ;8 = set bit 3 to a '1' for doub density (hl) ; ;for double density 512x16 doub sided: ; ; db 3 * 256 + 48h ; ;3 = use deblock part of bios (hl+1) ;48h = set bit 3 '1' for doub den ; set bit 6 '1' for doub sided (hl) ; mov a,m ;get disk density mask inx h ;point hl at disk format flag xthl ;get pointer to disk hardware byte ora m ;or in density mask mov m,a ;save sta diskhs ;value for selected disk dcx h ;point hl at format flag xthl ;get table pointer back mov a,m ;get format type flag inx h ;point hl at translate vector xchg ;save pointer in de pop h ;get pointer to format byte mov m,a ;save sta diskhf ;value for selected disk xchg ;get table pointer back pop d ;get address of selected dph ;move translate vector from table to dph lxi b,2 ;2 bytes to move ldir ;patch translate vector ;move dpb vector to dph xchg ;swap dph pointer to hl lxi b,10 ;offset to dpb dad b ;add offset to hl xchg ;swap in table address again lxi b,2 ;2 bytes to move ldir ;patch dpb vector ;now we can return to root module to finish select ret ; ;id read error couldn't read first sector on disk ; id$error: pop h ;discard disk pointer pop h ;discard dph pointer pop d ;get-save return address pop h ;discard dph pointer (saved by root) lxi h,0 ;indicate select error push h ;push 0 on stack for root push d ;restore return address ret ;return select error ; ; read the id byte from the first sector of @rdrv ; return it in A with zero flag set if read error ; get$id: lda @rdrv ;get relative drive # call ?ax16 ;shift it to upper nibble out ddsel ;select the drive call rdydrv ;check for drive ready mvi a,stprat+hlab ;restore drive call execc1 ;restore to track 0 mvi a,1 out sectp ;address sector 1 lxi b,fread shl 8 or wct0 lxi d,dmar shl 8 or (b128-1) lxi h,@dskbf ;use local buffer for read xra a out extdma ;use bank 0 call dmarw ;do the read ani 9dh ;check for errors lda @dskbf+7eh ;get disk format id byte ret ; disk$ids ;table of six disk id bytes db 0deh ;double density 16 secs of 512? db 0dch ;512 x 16 doub sided? db 0dfh ;128 x 51 doub sided? db 0ddh ;double density 51 secs? db 0e7h ;doub sided single density db 0e5h ;single sided single density ; dph$patches ;table of patch bytes for the disk parameter headers ;also the hardware setting byte and the format byte dw 1*256 + 0 ;format flag and density mask dw trans ;single density translate table dw ss128 ;single density disk parameter block dw 1*256 + 40h ;=single density double sided dw trans ;single density translate table dw sd128 ;single density double sided dpb dw 2*256 + 8 ;=double density single sided dw ddtrans ;double density translate table dw ds128 ;double density single sided dpb dw 2*256 + 48h ;=double density double sided dw ddtrans ;double density translate table dw dd128 ;double density double sided dpb dw 3*256 + 48h ;=deblocked double sided dw dbtrans ;512 x 16 translate table dw dd512 ;deblocked double sided dpb dw 3*256 + 8 ;=deblocked single sided dw dbtrans ;512 x 16 translate table dw ds512 ;deblocked single sided dpb ; ;end of disk format table ; ; gethf: ;return hardware format for selected drive lda @rdrv ;get relative drive code add a ;double for indexing mov c,a mvi b,0 ;generate offset lxi h,dsklog ;point to disk log dad b ;add in offset mov a,m ;get format sta diskhf ;save for future reference inx h ;point at select byte mov a,m ;get it sta diskhs ;save for future reference dcx h ;point at format byte for @rdrv ret ; disk read and write entry points. ; these entries are called with the following arguments: ; relative drive number in @rdrv (8 bits) ; absolute drive number in @adrv (8 bits) ; disk transfer address in @dma (16 bits) ; disk transfer bank in @dbnk (8 bits) ; disk track address in @trk (16 bits) ; disk sector address in @sect (16 bits) ; pointer to xdph in ; they transfer the appropriate data, perform retries ; if necessary, then return an error code in fd$read: ;read - read a sector call ckdrv ;check to see if new drive call gethf ;get hardware format lda diskhs ;get disk density mov b,a ;put density in reg b lda @trk call seekls ;seek @rdrv to @trk lda seeker ;get seek error flag ora a ;test for seek error jrz skok call check$id ;see if format has changed mvi a,0ffh ;show media change rnz ;return to cp/m if different format lda diskhs mov b,a ;put density in reg b lda @trk call seekls ;try again lda seeker ;check for error ora a jrz skok mvi a,1 ;show error for cp/m ret skok: call ckdma ;check and generate real dma address out extdma ;setup extended address lda use$local$buffer ora a ;see if we use the local buffer jrz readls ;if not skip the move lxi d,@dskbf ;now use local buffer xra a out extdma ;clear extended address call readls ;do the read push psw ;save the status for cp/m lda @dbnk ;get dma bank mov b,a ;use as destination bank mvi c,0 ;use bank 0 as source di ;disable interrupts just in case call ?xmove ;setup for extended move lxi d,@dskbf ;local buffer is source lhld @dma ;hl=destination for move lxi b,128 ;setup for 128 bytes lda diskhf ;check for 512 byte sector cpi 3 jrnz read$not$512$move ;jump if not lxi b,512 ;then setup for 512 byte move read$not$512$move: call ?move ;do the move ; (an extended move enables interrupts) pop psw ;restore status of read ret ;return to cp/m ; ; -read logical sector- ;de is dma address ; readls: xchg ;put dma address in hl call rdydrv ;check for drive ready ; mvi a,ercnt ;retry count rrtry: sta rtcnt ;save it lxi b,fread shl 8 or wct0 lda diskhf ;check if 512 byte sectors cpi 3 ;if = 3, 512 bytes lxi d,dmar shl 8 or (b512-1) jrnc rset lxi d,dmar shl 8 or (b128-1) rset: push h ;save dma address call dmarw ;use common read/write pop h ani 9dh ;check for errors during read rz ;return to cp/m if no read errors lda rtcnt ;get retry count dcr a ;decrement it jrnz rrtry ;retry again comext: call check$id ;see if density has changed mvi a,0ffh ;return media change flag rnz ;to cp/m mvi a,1 ; return to cp/m error code ora a ;set flags ret ; ; we have a disk error check if disk format has been changed ; check$id lda @rdrv ;get the reletive drive # mov c,a mvi b,0 ;generate offset lxi h,drive$ids ;point to drive id bytes dad b ;add in offset push h ;save pointer call get$id ;get the id byte of @rdrv pop h ;get pointer back cmp m ;test disk with previous ret ; ; check for new drive access ; ckdrv: lda olddrv ;get old drive # mov b,a lda @rdrv ;get relative drive # cmp b ;same ? rz ;return if no new drive sta olddrv ;save new drive # mvi a,0ffh sta chngfl ;set change flag ret ; ;write - write a sector from @dma ; fd$write: call ckdrv ;check if new drive call gethf ;get hardware format lda diskhs ;get disk density mov b,a lda @trk call seekls ;seek @rdrv to @trk lda seeker ;get seek error flag ora a ;test for seek error jrz skok1 call check$id ;see if format has changed mvi a,0ffh ;show media change rnz ;return to cp/m if different format lda diskhs mov b,a ;put density in reg b lda @trk call seekls ;try again lda seeker ;check for error ora a jrz skok1 mvi a,1 ;show error for cp/m ret skok1: call ckdma ;check and generate real dma address out extdma ;setup extended address lda use$local$buffer ora a ;see if we use the local buffer jrz writel ;if not skip the move lda @dbnk ;get dma bank mov c,a ;use as source bank mvi b,0 ;use bank 0 as destination di ;disable interrupts just in case call ?xmove ;setup for extended move lded @dma ;de=source for mowe lxi h,@dskbf ;local buffer is destination lxi b,128 ;setup for 128 bytes lda diskhf ;chech for 512 byte sector cpi 3 jrnz not$512$move ;jump if not lxi b,512 ;then setup for 512 byte move not$512$move: call ?move ;do the move ; (an extended move enables interrupts) lxi d,@dskbf ;now use local buffer xra a out extdma ;clear extended address ; ;-write logical sector- ;de is dma address ; writel: xchg ;put dma address in hl call rdydrv ;check for drive ready ; mvi a,ercnt ;retry count wrtry: sta rtcnt ;save it lxi b,fwrit shl 8 or wct0 lda diskhf ;check if 512 byte sector cpi 3 ;if = 3, 512 byte lxi d,dmaw shl 8 or (b512-1) jrnc wset lxi d,dmaw shl 8 or (b128-1) wset: push h ;save dma address call dmarw ;use common read/write pop h ani 0fdh ;check for errors during write rz ;return if no write errors ani 40h ;test for write protect mvi a,2 ;setup to show r/o disk rnz ;return to cp/m if write protect lda rtcnt ;get retry count dcr a ;decrement it jrnz wrtry ;retry again jmp comext ;use common exit routine ; ;check to see if selected drive is ready ;routine is also good for slim line drives. ; rdydrv: call statck ;check for drive ready rz ;return if ready mvi b,50 ;50 * 16ms = 800 ms rdydv1: call dlylop call statck ;check drive status rz ;return if ready djnz rdydv1 ;loop for 800 ms for slim line drives exx lda @adrv adi 'a' ;ascii adjust sta drv ;save for message lxi h,ready ;point to ready message call ?pmsg ;print it exx drvrdy: call statck jrnz drvrdy ;loop till ready ret ; ;selected drive is ready now ; statck: in dstat ;get drive status bit 7,a ;check ready bit ret ; dlylop: mvi a,16 ;16 ms count value dlylp1: mvi c,210 ;1 ms time constant wexitl: nop dcr c jnz wexitl dcr a jnz dlylp1 ret ; ;logical sector seek ; ;a=track, b=hardware and density for fdc ;d=sector (for future read) ; seekls: push psw ;save track xra a sta seeker ;reset seek error flag mvi a,10 ;retrys sta seekrty ;save it push b ;save density lda @rdrv ;get relative drive # if dual ani 0eh ;strip off lsb endif ; ;compute index into track table ; mov c,a ;put disk number into reg c. mvi b,0 lxi h,trktab ;point to start of trk table dad b ;get correct drive byte pop b mov a,m ;get old track value out track ;force into 1793 track reg. pop psw ;restore new track value cpi 1 mov c,a ;save track in reg c mov a,b ;get copy of drive and density jrnc seekok ;we are > than track 1? ani 30h ;clear density & side bit jr seek2 ;going to trk < 1 ; seekok: bit 6,b ;doing doub sided? jrz seek1 ;< > 1, doing single sided rrcr c ;divide track by 2 jrc seekk1 ;side bit set,doing odd trk res 6,b ;reset side bit, doing even trk seekk1: res 7,c ;clear msb of track value seek1: mov a,b ;get drive select code seek2: out ddsel ;select disk and density push b call rdydrv ;check for drive ready pop b call dlysel ;delay on selects lda @sect ;get sector number out sectp ;tell the controoller chip in track ;read 1793 track register cmp c ;is track the same? rz ;not necessary to seek mov a,c ;not same, get new track number out ddata ;seek to track with head load ; ;execute seek command ; mvi a,14h+stprat+hlab ;seek command execc1: out dcom ;output disk command call sloop ;use common polling ani 90h ;mask out junk bits jrz savtrk ;save track for next time ; ;we have a seek error ;save primary registers ;and execute a read id. ; exx ;save primary regs lxi h,@dskbf ;use local buffer lxi b,rdid shl 8 or wct0 lxi d,04006h ;dma read, length call dmarw ;read id ani 9dh ;check for errors jrnz rdider ;error don't change track reg in sectp ;get real position of head out track ;inform the disk controller rdider: lda seekrty ;get the retry count dcr a sta seekrty ;update count exx ;restore regs jrnz seek1 ;reseek to track mvi a,0ffh sta seeker ;set seek error flag ret ; ;delay on new drive select ; dlysel: lda chngfl ;get drive change flag ora a ;was there a change to new drv? rz ;return if not, else xra a ;reset the drv chng flag sta chngfl ; ;drives are different, so get the present track ;number and seek to it to unload the head. ; in track ;get the present track number out ddata ;place into data register mvi a,10h + stprat ;get seek command out dcom ;send seek command jmp sloop ;wait till done ; savtrk: lda @rdrv ;get relative drive code if dual ani 0eh ;strip off lsb endif mov c,a mvi b,0 ;generate offset lxi h,trktab ;point to track table dad b ;add offset in track ;get 1793 track mov m,a ;save in table ret ; ;dma read / write routine ; dmarw: xra a ;clear accum out cmnd ;clear dma chip mvi a,0d0h ;force intrp cmnd (1793) out dcom outp e outp d ;read/write with length dcr c ;reg -> adr0 port now outp l outp h ;address for dma set up mvi a,41h ;request chan 0 operation out cmnd ;tell dma chip call hdld ;check head load bit ora b ;add in command for floppy out dcom ;send it to floppy ; ;general purpose wait routine ; sloop: in dmachk ;check bit 7 rlc ;and rotate it left jrc sloop ;and loop if carry xra a ;get a zero out cmnd ;clear dma chip in dstat ;get status of disk ret ; and return it to caller ; ;check for head load bit on in 1793 ; hdld: in dstat ;get status from 1793 bit 5,a ;check head load bit mvi a,4 ;get head load set bit rz ;return if not loaded xra a ;get a zero ret ;head is load already ; ; this routine called to setup the address and extended ; address for direct dma disk transfer. ; If the requested transfer extends over a 4k boundry ; the memory select table is checked to insure continuity. ; If memory is not continuous or ; If the requested transfer extends over a 64k boundry ; a local buffer must be used and xmove is used to move ; data to/from its proper place. ; in any case the real starting address is returned in de ; and A. ckdma: call ?gndma ;generate real dma address push psw ;save extended address push d ;save real dma address lda diskhf ;get disk hardware format lxi b,127 ;load bc with 128 byte offset cpi 3 ; check for 512byte sector jrnz not512 lxi b,511 ;load bc with 512 byte offset not512: xchg ;put real dma address in hl mov a,h ;put msb of address in A ani 0f0h ;mask upper nibble push psw ;save for compare dad b ;add in offset hl=finish address mov a,h ani 0f0h ;mask upper nibble of finish address mov h,a ;put back in h pop psw ;get starting block back cmp h ;did block address change? jrz transok ;nope transfer is ok xchg ;get memory select table pointer mov a,m ;get the select byte inx h ;bump to the next byte inr a ;bump the first select byte cmp m ;is memory contiguous ? jrnz tnotok ;jump if not ; memory is contiguous but is it in the same 64K dcr a ;unbump first select byte ani 0f0h ;mask extended address nibble mov b,a ;put it in B mov a,m ;get next memory select byte ani 0f0h ;mask extended address nibble cmp b ;compare jrz transok ;jump if same 64k ; memory is not contiguous or is not in same 64k tnotok: mvi a,0ffh jr dcret ;jump to common return ; transfer does not extend over limits of hardware transok: xra a dcret: ;common return point sta use$local$buffer ;save flag pop d ;restore real dma address pop psw ;restore extended address ret ; ;data tables ; ready: db 0dh,0ah,7,'drive ' drv: db ' : not ready ',0 dsklog: dw 0000h ;disk 0 log flag/den dw 1000h ;disk 1 " dw 2000h ;disk 2 " dw 3000h ;disk 3 " diskhf: ds 1 ;disk hardware format (1, 2, 3) diskhs: ds 1 ;disk/density setting = hardware trktab: ds 4 ;track table for 4 drives drive$ids ds 4 ;id bytes of 4 drives ; rtcnt: ds 1 ;disk retry counter seekrty:ds 1 ;seek retry counter seeker: ds 1 ;seek error flag chngfl: ds 1 ;non-0 if new drive olddrv: ds 1 ;old relative drive # use$local$buffer ds 1 ;=ff if local buffer to be used @dskbf: ds 512 ;512 byte buffer for internal operations end