title 'compupro disk1 driver for cp/m+' ; by dick lieber ; cp/m-80 version 3 -- modular bios ; must be linked with BIOSKRNL and entered ; into the DRVTBL version equ 10 ;5/22/83 add version display support ;verdk db 09h ;5/5/83 remove stuff not needed for ldr ;verdk db 08h ;5/5/83 full buffers for loader version ;verdk db 07h ;version (banked) maclib cpm3cnfg dseg ver$dk: db 'fdsk ', version / 10 + '0', version mod 10 + '0', 0 ; ; non-critical value to adjust delay for clock speed ; delcnt: equ 5*1000 ;delay count for 5 mhz cpu ; ; disk drive constants ; stepr is probably the only one that will require ; alteration (value is in milliseconds) ; stepr: equ 3 ;REMEX srt: equ 16-stepr ;controller value hut: equ 240/16 ;head unload = 240 ms hdlt: equ 35 ;head load = 35 ms nd: equ 0b ;set dma mode ; disk drive dispatching tables for linked bios public verdk public fdsd0,fdsd1,fdsd2,fdsd3 public drv$type, ciopb, exec, rd$write, doseek, fd$read ; variables containing parameters passed by bdos extrn @adrv,@rdrv extrn @dma,@trk,@sect extrn @dbnk extrn @cnt ;<--- ; 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, saves & extrn ?pdec ; print binary number in from 0 to 99. extrn ?pderr ; print bios disk error header extrn ?conin,?cono ; con in and out extrn ?const ; get console status ; ; cp/m 3 disk definition macros ; maclib cpm3 ; ; z80 macro library instruction definitions ; maclib z80 ; ; disk input / output port assignments. (compupro disk 1) ; fdport equ 0c0h ;base port address for controller fdcs equ fdport ;status register fdcd equ fdport+1 ;data register dma equ fdport+2 ;dma address (when written) ints equ fdport+2 ;status register (when read) ser equ fdport+3 ;serial port ; controller function definitions (intel 8272) f$spec equ 03 ;specify f$dsts equ 04 ;drive status f$wrt equ 05 ;write data f$rdat equ 06 ;read data f$reca equ 07 ;recalibrate f$rsts equ 08 ;read status f$drid equ 10 ;read id f$seek equ 15 ;seek mfm equ 40h ;bit to add to make command mfm ; ; common control characters ; cr equ 13 lf equ 10 ; ; cp/m disk control block equates which defines the ; disk types and maximum storage capability of each ; disk type. ; dbl sided has bit 0 set ; bits 2 and 1 are 8272 n value ;128 byte sectors: dsks1: equ 0 ;single density, single sided. dsks2: equ 1 ;single density, double sided. ;256 byte sectors: dskd1: equ 2 ;double density, single sided. dskd2: equ 3 ;double density, double sided. ;512 byte sectors: dskd3: equ 4 ;double density, single sided dskd4: equ 5 ;double density, double sided ;1024 byte sectors: dskd5: equ 6 ;double density, single sided dskd6: equ 7 ;double density, double sided desg ; ; command buffer disk type dependent values. ; bits 2 and 3 from above table index into this ; to find values for use in command phase of ; fdc operation ; cmdtyp: ; gpl dtl db 007h,128 ;single density db 00eh,255 ;double density 256 bytes db 01bh,255 ;double density 512 bytes db 035h,255 ;double density 1024 bytes ; ; extended disk parameter headers (xpdhs) ; these tables are always refered to at the ; start of the actual DPH (XDPH+0) each header ; starts at XDPH-10 ; ;drive a: dw fd$write dw fd$read dw fd$login ;called if type must be determined dw fd$init ;not used for compupro db 0 ;relative drive number dbh$base: db dskd6 ; relative drive zero if loader fdsd0: DW XLTD3 ;+TRANSLATE TABLE ADDRESS DB 0,0,0,0,0,0,0,0,0 ;+BDOS SCRATCH AREA DB 0 ; MEDIA FLAG DW DPBD6 ;+DISK PARAMETER BLOCK DW 0FFFEH ; CHECKSUM VECTOR ALLOCATED BY DW 0FFFEH ; ALLOC VECTOR ALLOCATED BY GENCPM DW dirbcb ;+dirbcb dw dtabcb ;+dtabcb dw 0FFFEH ; HASH ALLOC'D DB 0 ; HASH BANK ; entries marked with a + are ; actually used by cpmldr else fdsd0 dph xltd3,dpbd6 ;drive b: dw fd$write dw fd$read dw fd$login dw fd$init db 1,dskd6 ; relative drive one fdsd1 dph xltd3,dpbd6 ;drive c: dw fd$write dw fd$read dw fd$login dw fd$init db 2,dskd6 ; relative drive two fdsd2 dph xltd3,dpbd6 ;drive d: dw fd$write dw fd$read dw fd$login dw fd$init db 3,dskd6 ; relative drive three fdsd3 dph xltd3,dpbd6 endif if loader ; ; buffer control blocks -- for loader version only ; gencpm will make these automatically ; for the cpm+ version of disk ; dirbcb: db 0ffh ;drv db 0,0,0 ;rec# db 0 ;wflg db 0 ;scratch dw 0 ;track dw 0 ;sector dw dirbuffer ;actual buffer (only this is actually used ; by the cpmldr) db 0 ;bank dw 0 ;link dirbuffer: ds 1024 ;largest sector size dtabcb: db 0ffh ;drv db 0,0,0 ;rec# db 0 ;wflg db 0 ;scratch dw 0 ;track dw 0 ;sector dw dtabuffer ;actual buffer (only this is actually used ; by the cpmldr) db 0 ;bank dw 0 ;link dtabuffer: ds 1024 ;largest sector size endif ; ; xlate index table ; ; bits 2 and 3 of the drive type are used ; to index into this table to find ; the proper sector translation table ; xtable: dw xlts ;single 128 dw xltd1 ;double 256 dw xltd2 ;double 512 dw xltd3 ;double 1024 ; ; sector translation tables. ; remember these are physical not logical sectors ; xlts: skew 26,6,0 ;fm 128 xltd1: skew 26,9,0 ;mfm 256 xltd2: skew 15,4,0 ;mfm 512 xltd3: skew 8,3,0 ;mfm 1024 ; ; disk type definition blocks for each particular mode. ; each dph has a pointer to one of these disk parameter ; blocks (dpb). fd$login will insert the pointer into ; the proper XDPH ; cseg dpbs1: ;single density, single sided. dpb 128, 26, 77, 1024, 64, 2 dpb$len: equ $ - dpbs1 dpbs2: ;single density, double sided. dpb 128, 26, 154, 2048, 128, 4 dpbd1: ;double density, single sided. dpb 256, 26, 77, 2048, 128, 2 dpbd2: ;double density, double sided. dpb 256, 26, 154, 2048, 256, 4 dpbd3: ;double density, single sided. dpb 512, 15, 77, 2048, 128, 2 dpbd4: ;double density, double sided. dpb 512, 15, 154, 2048, 256, 4 dpbd5: ;double density, single sided. dpb 1024, 8, 77, 2048, 128, 2 dpbd6: ;double density, double sided dpb 1024,8,154,2048,256,4 dseg ; rest is banked ; ; initialization entry point. ; called for first time initialization. ; fd$init: ret ;not needed for disk 1 ;any initialization is done at ;login time (fd$login) ; ; 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+0 in ; they transfer the appropriate data, perform retries ; if necessary, then return an error code in ; ; read a sector (physical) ; fd$read: dcx d ;point to drive type in xdph ldax d ;get drive type sta drv$type cpi dskd1 ;first double density type mvi a,f$rdat ;single density read command for now jc rd$write ;is really single ori mfm ;make double density jmp rd$write ; ; write a sector (physical) ; fd$write: if not loader dcx d ;point to drive type in xdph ldax d ;get drive type sta drv$type cpi dskd1 ;first double density type mvi a,f$wrt ;single density write command for now jc rd$write ;is really single ori mfm ;make double density jmp rd$write endif ; ; login ; ; this entry is called when a logical drive is about to ; be logged into for the purpose of density determination. ; it will adjust the parameters contained in the disk ; parameter header pointed at by as required. ; ; entry de = xdph+0 ; ; exit nothing (bioskrnl handles pointing to dph) ; ; dph is intialized for the appropriate floppy ; disk format: ; 1. determine drive type (drv$type) by calling TREAD ; 2. store this in drv$type & in the xdph ; 3. find the proper xlate table and insert pointer ; into dph. ; 4. find the proper dpb and insert pointer into dph. ; fd$login: push d ;save xdph address call tread ;determine disk type pop d ;restore xdph address jnz no$type ;if disk type not determined sta drv$type ;save disk type push d ;pointing to xdph+0 dcx d stax d ;insert drive type into xdph ; ; find xtranslate table address by indexing into ; xtable with n value of command type ; ani 0feh ;remove two sided bit ;leaving n value times 2 mvi h,0 mov l,a lxi d, xtable ;table of xlate table addresses dad d ;index into table xchg ;de points to xlate table address pop h ;get xdph (at xlate table posistion) ;(this was pushed from de above) push h ;save pointer to xdph+0 ldax d ;get low byte from xlate index mov m,a ;store in DPH inx h inx d ldax d ;get low byte from xlate index mov m,a ;store in DPH ; ; now get DPB address and insert into DPH ; ; first find proper DPH lxi h, dpbs1 ;get start of disk parameter blocks lxi d, dpb$len lda drv$type ora a ;set flags, could have been 0 dpb$loop: jz got$dpb dad d ;add length of one DPB dcr a ;count down jr dpb$loop ; ; hl now points to dpb ; got$dpb: xchg ;dpb in de pop h ;get xdph, pointing to xdph+0 lxi b,12 ;offset from xdph+0 to dpb address dad b ;pointing to dpb entry in dph mov m,e ; ld (hl), de inx h mov m,d ; ; all done - back to BIOSKRNL ; ret ; ; disk type not determined ; no$type: ; ; check with scb to find if errors are being displayed ; lda @ermde ;in scb inr a jrz no$message call ?pderr lxi h,no$type$mess call ?pmsg no$message: lxi h,0 mov a,l sta 4 ret no$type$mess: db cr,lf,'Can''t determine disk type.',0 ; ; determine floppy disk type. ; ; exit zbit set = no error ; a = disk type (n value and sided bit) ; tread: lda @adrv ;absolute drive code for message adi 'A' ;make drive letter from number sta nrdym2 ;set drive into message call specify ;set disk parameters lxi b,240 ;time delay for selecting sides call delay lxi h,dsts ;drive status command mvi b,dstsl mvi c,0 ;no status bytes inx h ;unit select lda @rdrv ;set drive into command buffer mov m,a ;stash it dcx h ;point back to start of command buffer call exec ;perform command mvi b,1 ;one byte to get call gcmps ;get the one status byte ani 020h ;mask ready bit jnz drvrdy ;if drive is ready ; ; drive not ready, print message ; lxi h,nrdymess call ?pmsg ;via BIOSKRNL ori 0ffh ;clear zero flag ret drvrdy: lda tempbf ;get status byte ani 8h ;select two sided flag rrc ;move to lsb, which is where rrc ;it will become part of drivetype rrc sta drv$type ;save sided flag lxi h,recal ;re-calabrate command mvi b,lrecal ;command length call moveto ;process command rnz ;if error mvi a,2 ;seek to track two call doseek ;do seek rnz ;if error mvi a,f$drid ;read ID command sta drid tst$rid: lxi h,drid ;point to command table mvi b,dridl ;length of table mvi c,7 ;result phase size execx: inx h lda @rdrv ;set drive into command buffer mov m,a dcx h call exec ;process command jz rid$ok ;if read valid lda drid ;get command xri 040h ;compliment mfm bit sta drid ;stash it back ani 040h ;see if we've already tried mfm jnz tst$rid ;if mfm not tried ori 0ffh ret rid$ok: lda tempbf+6 ;get n value from result add a ;shift left (for type value) mov b,a lda drv$type ;has two sided flag only ora b ;combine n with sided flag ;making it the drive type cmp a ;set zero flag ret ; ; commands used by tread ; dsts: db f$dsts,0 ;sense drive status dstsl: equ $-dsts recal: db f$reca,0 ;recalibrate drive lrecal: equ $-recal drid: db f$drid,0 ;read id (first correct C,H,R,N etc) dridl: equ $-drid ; ; message to print if not ready ; nrdymess: db cr,lf,'Drive ' nrdym2: db 'x' ;drive letter inserted by TREAD db ' not ready.',0 ; ; common read/write -- preform actual transfer ; ; entry a = command. ; rd$write: call prcdch ;process command, drive, cylinder lxi h,ciopb+0 ;set buffer address mov m,c ;set command inx h mov m,b ;set drive inx h mov m,e ;set cylinder inx h mov m,d ;set head inx h mov e,a ;save n field lda @sect ;get sector inr a ;cause tables say sector 0 is 1st mov c,a ;save for ending sector mov m,a ;set beginning sector inx h mov a,e ;get n field (type bits 3,2,1) mov m,a ;set n field inx h add a ;cmdtyp table is 2 bytes wide (*2) ; ; point into cmdtyp table ; push h ;save pointer to command table lxi h, cmdtyp ;point to begining of table mvi d,0 mov e,a ;position in table dad d ;index into table xchg pop h ;point back into command table ;de = points to entry in type table ; ; ending sector ; mov m,c ;start is end inx h ; ; gpl & dtl ; ldax d ;get gpl mov m,a ;stash into table inx d ;point to dtl inx h ldax d ;get dtl mov m,a ;stash it mvi a,mrtry ;set retry count fnl1: sta rtry ;clear retry count lda ciopb+2 ;get cylinder number call doseek ;seek to proper track jnz rwerror ;if seek error ; ; send dma address (3 bytes) to hardware ; lda @dbnk ;extended address out dma lhld @dma ;get low 16 bits of address mov a,h ;msb of dma out dma mov a,l ;lsb of dma out dma ;to dma hardware ; ; data is in ciopb ; lxi h,ciopb mvi b,ciopl ;set command buffer length mvi c,7 ;result phase size call exec ;perform operation cpi 40h ;ck for abnormal termination jnz rwerror lda tempbf+1 ;result status register 1 sui 80h ;end of cylinder flag sta erflag rz ;if no errors ; ; error ; rwerror: lda rtry ;get retry counter dcr a jnz fnl1 ;if not permanent error ori 01h sta erflag ;set error flag ret rtry: db 0 mrtry: equ 10 ;maximum retry count ; prcdch -- process command, drive, cylinder, and head. ; ; entry a = command. ; ; exit a = n field (0..4). ; b = drive. ; c = command. ; d = head. ; e = cylinder. prcdch: mov c,a ;save command lda @rdrv mov b,a ;drive set in b lhld @trk xchg ;cylinder (trk) in e lda drv$type ;get type mov h,a ;save type ani 1 ;bit 0 set = dbl sided jz cdch1 ;if single sided ; ; set head & adjust cylinder ; mov a,e ;get track again ani 1 ;even tr=head 0, odd tr=head 1 mov d,a ;set head done with head rlc rlc ora b ;combine head with drive mov b,a mov a,e ;adjust track for cylinder rar mov e,a ;cylinder in e ; ; now put n value in a ; cdch1: mov a,h ;get type back again ani 0feh ;remove sided bit rrc ;n was bits 3,2,1 of type ret ; ; specify disk drive characteristics. ; called when drive is logged in ; specify: lxi h,spec+1 ;point to command string mvi b,lspec ;bytes to output mvi c,0 ;bytes of status to receive lda stepms ;step rate value ori hut ;head unload time mov m,a ;modify original step & hd unload dcx h ;point back to start of cmd phase data jmp exec ;specify disk command ; ; command string for specify command ; spec: db f$spec ;specify command db (srt shl 4)+hut ;step rate & head unload time db (hdlt shl 1)+nd ;head load time & dma mode lspec: equ $-spec ;length of command phase stepms: db (srt shl 4)+0 ; ; seek to specified track/sector ; ; entry a = track ; doseek: sta dsekc+2 ;put cylinder into command table lxi h,dsekc ;address of command buffer mvi b,dsekl ;length of commnad ; ; move head according to command. ; ; entry hl = address of command buffer. ; b = length of command buffer. ; ; exit z bit set if no error. ; moveto: mvi c,0 ;zero status inx h ;point to unit select lda @rdrv ;get relative drive mov m,a ;stash it dcx h ;back to start of command call exec ;perform seek mvto1: in ints ;wait for controller to ora a ;go not busy jp mvto1 ;if not complete mvi a,f$rsts ;read status command out fdcd ;request status mvi b,2 ;number of status bytes to get call gcmps ;get status cpi 20h rz ;if seek complete lda tempbf ;get true status byte ani 3h ;mask disk unit mov c,a lda @rdrv ;actdsk cmp c jnz mvto1 ;if not proper unit ori 001h ;clear zero flag ret ; ; data for seek command phase ; dsekc: db f$seek ;seek command db 0 ;hds,ds1,ds0 (by seek routine) db 0 ;new cylinder number (by seek rtne) dsekl: equ $-dsekc ; ; execute a command ; ; entry hl = begining of command buffer. ; b = # of bytes to output ; c = # of bytes for result phase ; ; exit if c <> 0 then see gcmps. ; exec: in fdcs ;wait for controller to go ready ora a jp exec ;if no master ready bit mov a,m ;command byte out fdcd ;to controller inx h dcr b jnz exec ;if more bytes mov a,c ;# of status bytes+1 ora a rz ;if no status bytes mov b,c ;# of status bytes ; ; wait for 8272 interupt signal ; execlp: in ints ora a jp execlp ;if operation not complete ; get completion status. ; ; entry b= # of status bytes to read ; ; exit tempbf = status bytes read in. ; a = [tempbf] and 0f8h. ; flags set according to above value in a. ; gcmps: lxi h,tempbf ;set status buffer address gcmps2: in fdcs ora a jp gcmps2 ;if not ready in fdcd ;get status byte mov m,a inx h dcr b ;decrement counter jnz gcmps2 ;wait until all done lda tempbf ;get first status byte ani 0f8h ret ; ; delay delay the millisecond count contained in bc. ; ; destroys a and flags. delay: mvi a,delcnt/26 dlay1: dcx b inx b dcr a jnz dlay1 ;if not 1 millisecond dcx b mov a,c ora b jnz delay ;if not desired count ret drv$type: ds 1 ;current drive's type erflag: ds 1 ;error reporting ; ; main command & result phase buffer ; ciopl: equ 9 ciopb: ds ciopl ;disk command buffer tempbf: ds 8 ;result buffer end