title 'wd1797 w/ Z80 DMA Single density diskette handler' ; CP/M-80 Version 3 -- Modular BIOS ; Disk I/O Module for wd1797 based diskette systems ; Initial version 0.01, ; Single density floppy only. - jrp, 4 Aug 82 dseg ; Disk drive dispatching tables for linked BIOS public fdsd0,fdsd1 ; 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, 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 ; Port Address Equates maclib ports ; 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$init0 db 0,0 ; relative drive zero fdsd0 dph trans,dpbsd,16,31 dw fd$write dw fd$read dw fd$login dw fd$init1 db 1,0 ; relative drive one fdsd1 dph trans,dpbsd,16,31 cseg ; DPB must be resident dpbsd dpb 128,26,77,1024,64,2 dseg ; rest is banked trans skew 26,6,1 ; Disk I/O routines for standardized BIOS interface ; Initialization entry point. ; called for first time initialization. fd$init0: lxi h,init$table fd$init$next: mov a,m ! ora a ! rz mov b,a ! inx h ! mov c,m ! inx h outir jmp fd$init$next fd$init1: ; all initialization done by drive 0 ret init$table db 4,p$zpio$1A db 11001111b, 11000010b, 00010111b,11111111b db 4,p$zpio$1B db 11001111b, 11011101b, 00010111b,11111111b db 0 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 ret ; we have nothing to do in ; simple single density only environment. ; 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: lxi h,read$msg ; point at " Read " mvi a,88h ! mvi b,01h ; 1797 read + Z80DMA direction jmp rw$common fd$write: lxi h,write$msg ; point at " Write " mvi a,0A8h ! mvi b,05h ; 1797 write + Z80DMA direction ; jmp wr$common rw$common: ; seek to correct track (if necessary), ; initialize DMA controller, ; and issue 1797 command. shld operation$name ; save message for errors sta disk$command ; save 1797 command mov a,b ! sta zdma$direction ; save Z80DMA direction code lhld @dma ! shld zdma$dma ; get and save DMA address lda @rdrv ! mov l,a ! mvi h,0 ; get controller-relative disk drive lxi d,select$table ! dad d ; point to select mask for drive mov a,m ! sta select$mask ; get select mask and save it out p$select ; select drive more$retries: mvi c,10 ; allow 10 retries retry$operation: push b ; save retry counter lda select$mask ! lxi h,old$select ! cmp m mov m,a jnz new$track ; if not same drive as last, seek lda @trk ! lxi h,old$track ! cmp m mov m,a jnz new$track ; if not same track, then seek in p$fdmisc ! ani 2 ! jnz same$track ; head still loaded, we are OK new$track: ; or drive or unloaded head means we should . . . call check$seek ; . . read address and seek if wrong track lxi b,16667 ; 100 ms / (24 t states*250 ns) spin$loop: ; wait for head/seek settling dcx b mov a,b ! ora c jnz spin$loop same$track: lda @trk ! out p$fdtrack ; give 1797 track lda @sect ! out p$fdsector ; and sector lxi h,dma$block ; point to dma command block lxi b,dmab$length*256 + p$zdma ; command block length and port address outir ; send commands to Z80 DMA in p$bankselect ; get old value of bank select port ani 3Fh ! mov b,a ; mask off DMA bank and save lda @dbnk ! rrc ! rrc ; get DMA bank to 2 hi-order bits ani 0C0h ! ora b ; merge with other bank stuff out p$bankselect ; and select the correct DMA bank lda disk$command ; get 1797 command call exec$command ; start it then wait for IREQ and read status sta disk$status ; save status for error messages pop b ; recover retry counter ora a ! rz ; check status and return to BDOS if no error ani 0001$0000b ; see if record not found error cnz check$seek ; if a record not found, we might need to seek dcr c ! jnz retry$operation ; suppress error message if BDOS is returning errors to application... lda @ermde ! cpi 0FFh ! jz hard$error ; Had permanent error, print message like: ; BIOS Err on d: T-nn, S-mm, , Retry ? call ?pderr ; print message header lhld operation$name ! call ?pmsg ; last function ; then, messages for all indicated error bits lda disk$status ; get status byte from last error lxi h,error$table ; point at table of message addresses errm1: mov e,m ! inx h ! mov d,m ! inx h ; get next message address add a ! push psw ; shift left and push residual bits with status xchg ! cc ?pmsg ! xchg ; print message, saving table pointer pop psw ! jnz errm1 ; if any more bits left, continue lxi h,error$msg ! call ?pmsg ; print ", Retry (Y/N) ? " call u$conin$echo ; get operator response cpi 'Y' ! jz more$retries ; Yes, then retry 10 more times hard$error: ; otherwise, mvi a,1 ! ret ; return hard error to BDOS cancel: ; here to abort job jmp ?wboot ; leap directly to warmstart vector ; subroutine to seek if on wrong track ; called both to set up new track or drive check$seek: push b ; save error counter call read$id ; try to read ID, put track in jz id$ok ; if OK, we're OK call step$out ; else step towards Trk 0 call read$id ; and try again jz id$ok ; if OK, we're OK call restore ; else, restore the drive mvi b,0 ; and make like we are at track 0 id$ok: mov a,b ! out p$fdtrack ; send current track to track port lda @trk ! cmp b ! pop b ! rz ; if its desired track, we are done out p$fddata ; else, desired track to data port mvi a,00011010b ; seek w/ 10 ms. steps jmp exec$command step$out: mvi a,01101010b ; step out once at 10 ms. jmp exec$command restore: mvi a,00001011b ; restore at 15 ms ; jmp exec$command exec$command: ; issue 1797 command, and wait for IREQ ; return status out p$fdcmnd ; send 1797 command wait$IREQ: ; spin til IREQ in p$fdint ! ani 40h ! jz wait$IREQ in p$fdstat ; get 1797 status and clear IREQ ret read$id: lxi h,read$id$block ; set up DMA controller lxi b,length$id$dmab*256 + p$zdma ; for READ ADDRESS operation outir mvi a,11000100b ; issue 1797 read address command call exec$command ; wait for IREQ and read status ani 10011101b ; mask status lxi h,id$buffer ! mov b,m ; get actual track number in ret ; and return with Z flag true for OK u$conin$echo: ; get console input, echo it, and shift to upper case call ?const ! ora a ! jz u$c1 ; see if any char already struck call ?conin ! jmp u$conin$echo ; yes, eat it and try again u$c1: call ?conin ! push psw mov c,a ! call ?cono pop psw ! cpi 'a' ! rc sui 'a'-'A' ; make upper case ret disk$command ds 1 ; current wd1797 command select$mask ds 1 ; current drive select code old$select ds 1 ; last drive selected old$track ds 1 ; last track seeked to disk$status ds 1 ; last error status code for messages select$table db 0001$0000b,0010$0000b ; for now use drives C and D ; error message components read$msg db ', Read',0 write$msg db ', Write',0 operation$name dw read$msg ; table of pointers to error message strings ; first entry is for bit 7 of 1797 status byte error$table dw b7$msg dw b6$msg dw b5$msg dw b4$msg dw b3$msg dw b2$msg dw b1$msg dw b0$msg b7$msg db ' Not ready,',0 b6$msg db ' Protect,',0 b5$msg db ' Fault,',0 b4$msg db ' Record not found,',0 b3$msg db ' CRC,',0 b2$msg db ' Lost data,',0 b1$msg db ' DREQ,',0 b0$msg db ' Busy,',0 error$msg db ' Retry (Y/N) ? ',0 ; command string for Z80DMA device for normal operation dma$block db 0C3h ; reset DMA channel db 14h ; channel A is incrementing memory db 28h ; channel B is fixed port address db 8Ah ; RDY is high, CE/ only, stop on EOB db 79h ; program all of ch. A, xfer B->A (temp) zdma$dma ds 2 ; starting DMA address dw 128-1 ; 128 byte sectors in SD db 85h ; xfer byte at a time, ch B is 8 bit address db p$fddata ; ch B port address (1797 data port) db 0CFh ; load B as source register db 05h ; xfer A->B db 0CFh ; load A as source register zdma$direction ds 1 ; either A->B or B->A db 0CFh ; load final source register db 87h ; enable DMA channel dmab$length equ $-dma$block read$id$block db 0C3h ; reset DMA channel db 14h ; channel A is incrementing memory db 28h ; channel B is fixed port address db 8Ah ; RDY is high, CE/ only, stop on EOB db 7Dh ; program all of ch. A, xfer A->B (temp) dw id$buffer ; starting DMA address dw 6-1 ; Read ID always xfers 6 bytes db 85h ; byte xfer, ch B is 8 bit address db p$fddata ; ch B port address (1797 data port) db 0CFh ; load dest (currently source) register db 01h ; xfer B->A db 0CFh ; load source register db 87h ; enable DMA channel length$id$dmab equ $-read$id$block cseg ; easier to put ID buffer in common id$buffer ds 6 ; buffer to hold ID field ; track ; side ; sector ; length ; CRC 1 ; CRC 2 end