title Kaypro 4-83 Resident Software Package subttl Disk Equate and Parameters ; ; ################################################################ ; ## ## ; ## Disk support routines ## ; ## ## ; ################################################################ ; ## Last Update:06/08/82 [001] ## ; ################################################################ ; # Revised to KPRO4, 3 Mar. 1984, C.B. Falconer ## ; ################################################################ ; ## Revised for integral data segment with Z80ASM 85/6/15 cbf ## ; ## General purpose disk routines, reduced Ram use. Avoid use ## ; ## of AF' register (reserved for other uses). Added use of ## ; ## register E for drive sensing, per CPM standards. Added ## ; ## provision for disabling write checking. Re-organized. ## ; ## The "home" routine now returns a pointer to a data area. ## ; ## Note the unmodified Kaypro returns a pointer in page zero, ## ; ## so software can tell them apart. ## ; ################################################################ ; ## 85/6/18 cbf. Added disk error messages, corrected summary ## ; ## reset on any cold boot via diskinit. Reduced error retrys ## ; ################################################################ ; extrn bitport, bankbit; bank selection extrn .thnsd; delay extrn .print, .vidout; for error system ; ; linkage to deblocking system extrn erflag, unacnt extrn hstact, hstbuf, hstdsk extrn hstsec, hsttrk, hstwrt ; entry home, seldsk, settrk, setsec entry sectran entry diskinit, datainit, diskon, diskoff entry readhst, writehst ; ; Public data areas (for deblocking area) entry sekdsk, seksec, sektrk entry denflag ; ; Entries to installed routines in data segment entry move, rd, wrt ; ; --------------------------------------------------------- ; drvmask equ 0FCH; drive select mask denmask equ 0DFH; density bit mask ddbit equ 00H; double density bit sdbit equ 20H; single density bit sidebit equ 4; mtroff equ 40h ; ; Disk controller ports control equ 10H; I/O port of disk controller status equ control+0; status register cmnd equ control+0; command register track equ control+1; track register sector equ control+2; sector register data equ control+3; data register ; ; Controller commands (selected) ficmd equ 11010000B; force interrupt (Abort current command) rdcmd equ 10001000B; read command wrtcmd equ 10101100B; write command seekcmd equ 00010000B; seek command rstcmd equ 00000000B; home (restore) command adrcmd equ 11000100B; read track address rdmask equ 10011100B; read status mask wrtmask equ 11111100B; write status mask ; ; retry control tries1 equ 3; re-home on bad sector # of tries+1 tries2 equ 6; re-read/write # of retries+1 ; ; Miscellaneous retcod equ 0C9H; return op code nmivec equ 0066H; non-maskable interupt vector esc equ 01bh; for error message positioning ; subttl Physical disk routines ; cseg ; ; enter here on system boot to initialize ; a,f,b,c,d,e,h,l diskinit: ld hl,ioimage; move rd/wrt/dphs routines into RAM ld de,move ld bc,imaglen ldir ; xor a; 0 to accumulator ld (hstact),a; host buffer inactive ld (unacnt),a; clear unalloc count ld a,ddbit; set double density flag ld (denflag),a ld a,255; set track numbers to 255 ld (dsk),a; clear disk number ld (tracka),a ld (trackb),a ret ; ; called on cold start to initialize error counts etc ; f,b,c,d,e,h,l datainit: ld hl,.ckwrt ld de,chkwrt ld bc,.ckend-.ckwrt ldir ret ; ; perform logical to physical sector translation. ; logical sector number in BC, table address in DE ; return physical sector number in HL ; a,f,d,e,h,l sectran: ld a,d; table address 0? or e ld h,b; if so no xlate ld l,c ret z ex de,hl; table address in hl add hl,bc; index by logical sector number ld l,(hl) ld h,0 ret ; ; set sector given by register c ; a,f setsec: ld a,c ld (seksec),a; sector to seek ld a,(denflag) or a ret z; single density ; " " ; select sector #, BC=Sector # (double density) ; a,f secset: in a,(bitport); single or double sided? and sidebit ld a,c; move sector number to A jp z,secst1; pure sector no in single sided add a,10 secst1: out (sector),a; to controller register ret ; ; Set track settrk: ld h,b ld l,c ld (sektrk),hl; set track given by registers BC ld a,(denflag) or a ret z; single density ; " " ; seek track #, BC=Track # (double density) trkset: call ready; make sure drive is on and ready ld a,(sidflg); {} or a jp z,tkset3; single sided, select side 0 ld a,c; else double sided drive rra; trk no divided /2 ld c,a; for actual selection in a,(bitport) jp c,tkset1; and select side 0 for even track and not sidebit; else select side 1 jp tkset2 tkset1: or sidebit tkset2: out (bitport),a; select side tkset3: ld a,c; get physical track number ld (ctrack),a out (data),a; track # to seek to ld a,seekcmd; seek command out (cmnd),a; issue command ; " " ; check status of controller, wait for command to finish executing ; a,f busy: ld a,(nmivec) push af ld a,retcod ld (nmivec),a halt; wait for command done pop af ld (nmivec),a busy1: in a,(status); now wait for not busy bit 0,a; preserve code to return jp nz,busy1 ret ; ; home disk ; a,f,h,l home: ld a,(denflag) or a jp nz,dohome; double density ld a,(hstwrt); patch by DRI or a jp nz,dohome ld (hstact),a ; " " ; home disk head dohome: call ready; make sure drive is on and ready in a,(bitport) and not sidebit out (bitport),a; select side 0 xor a ld (ctrack),a; reset current track number ld a,rstcmd; restore command out (cmnd),a; issue command call busy; test and wait for not busy ld hl,chkwrt ret ; ; select disk seldsk: ld a,c; selected disk number ld (sekdsk),a; seek disk number ; " " ; Physical disk drive select, C=drive number 0=A:, 1=B: ; return HL=dph for selected drive, or HL=0 for non-existent drive ; Set up density for ddrive selected dsksel: ld hl,0; hl = 0 for non-existent drive ld a,c cp 2 ret nc; drive number >B: or a; z flag set => A: drive else B: drive ld a,e; Save the CPM flag momentarily ld de,tracka; find propert track(x) and.. ld hl,dpha; select proper dph for drive jp z,sel0 ld de,trackb ld hl,dphb sel0: push hl; save dph of disk to be selected and 1 ld a,(dsk) jp nz,sel0a; Previously selected to CPM ld a,255 ld (de),a; Mark for sensing xor a ld (unacnt),a; home will reset hstact on sense ld a,(dsk) cp c jp z,selnot; reselecting the current disk sel0a: cp c; selecting disk already selected? jp z,selx; yes, no further action needed ld a,c; save new disk number ld (dsk),a or a; set zero flag if A: drive ld hl,ctrack ld de,trackb; now find proper track(x) jp z,sel1 ld de,tracka sel1: ld a,(de); have we been on disk we're "leaving" cp 255; if not do not update tracks jp z,selnot push bc ld bc,3 ldir; save old table pop bc selnot: ld de,ctrack; now to update main table {} ld a,c; set z flag if A: drive or a ld hl,tracka jp z,sel2 ld hl,trackb sel2: ld a,(hl) cp 255; first time for this drive? jp z,density; if so, go set it up ld bc,3 ldir; init table for density ld bc,15; dparm size ld de,dpbd; dparm addr ld hl,.dpbd; single sided table ld a,(sidflg) or a jp z,sel2a ld hl,.dsdd; double sided table sel2a: ldir; move in dparm for this drive ld a,(ctrack) out (track),a selx: pop hl; adr of dph for disk we are selecting ret ; density: ld a,ddbit ld (denflag),a call ready; physical disk select call home; seek track 0, side 0 pop hl call dcheck; see if we can read address jp z,dend; if so, density is double ld a,sdbit ld (denflag),a call ready; try single density call dcheck ret nz; can't read, so don't change ; " " ; set up for single density operations push hl; (* single density *) hl^ to dph push de ld de,tbl1; single density sector xlate table ld (hl),e; store table^ into dph inc hl ld (hl),d ld de,9; move foward in dph to dpb pointer add hl,de ld de,dpbs; single density dpb ld (hl),e inc hl ld (hl),d; {} jp den1 ; ; set up for double density operations dend: push hl; (* double density *) hl^ to dph push de ld de,0; no xlate table (done by FORMAT prog) ld (hl),e; store table^ into dph inc hl ld (hl),d ld de,9; move forward in dph to dpb pointer add hl,de ld de,dpbd; double density dpb ld (hl),e inc hl ld (hl),d in a,(bitport); now try side 1 (double sided?) or sidebit out (bitport),a; select side 1 call dcheck; try to read it ld bc,15 ld de,dpbd ld hl,.dpbd; source, single sided dparm ld a,0; single sided flag (dont chg flags) jp nz,selsid; single sided if not zero. ld a,(dsksns+2); is sector # > 9 cp 10; (it must be for valid dbl sided op) ld a,0; (if not, single sided drives) jp c,selsid; select single sided ld hl,.dsdd; source double sided dparm ld a,255; proper sidflg selsid: ld (sidflg),a; set up double density dparm {} ldir in a,(bitport); return to size zero and not sidebit out (bitport),a ; " " exit den1: ld hl,ctrack ld de,tracka ld a,(dsk) or a jp z,dskupd; update dsk trk, side, etc. table ld de,trackb dskupd: push bc ld bc,3 ldir pop bc pop de; restore pointer to dph pop hl; pointer to track(x) bios register ret ; ; check disk legible at current format. z flag if so ; a,f dcheck: push hl push bc ld hl,dsksns ld bc,6*256+data; 6 bytes from adr. header via "data" ld a,(nmivec) push af ld a,retcod ld (nmivec),a ld a,adrcmd; get address command out (cmnd),a; issue command to controller dchk1: halt; await interrupt from controller ini jp nz,dchk1 pop af ld (nmivec),a call busy; wait for command done and 10h; record not found flag pop bc pop hl ret; return status ; ; ready disk drive, perform physical disk select, set density bit ; a,f ready: push hl; save hl push de; and de push bc ld a,ficmd; abort any controller action out (cmnd),a call diskon; turn drive motor on ld a,(dsk); A=drive # ld e,a; save drive # in E in a,(bitport); A=bit port and drvmask and denmask; strip drive/density bits or e; or in requested drive inc a; bump, 01=A: 10=B: ld hl,denflag; hl^ to density bit for this drive or (hl) out (bitport),a; to bit port pop bc pop de pop hl ret ; ; turn disk motor on, delay for drive speed ; a,f,b (because .thnsd protected) diskon: in a,(bitport); get current drive motor status bit 6,a; is motor on? (save other bits) ret z; motor on, do nothing and not mtroff; motor on bit out (bitport),a; turn motor on ld b,50; delay 1/2 second & exit jp .thnsd ; ; turn disk motor off ; a,f diskoff: in a,(bitport) or mtroff; motor off bit out (bitport),a ret ; subttl Writehst and Readhst logical to Physical routines ;* WRITEHST performs the physical write to *; ;* the host disk, READHST reads the physical *; ;* disk. *; ; ; hstdsk = host disk #, hsttrk = host track #, ; hstsec = host sect #. write "hstsiz" bytes ; from hstbuf and return error flag in erflag. ; return erflag non-zero if error writehst: ld l,3; readback retries wthst0: ld a,(rtry1) ld d,a; reseek tries wthst1: ld a,(rtry2) ld e,a; retry error counts wthst2: push hl push de; save error counts call hstcom; set track and sector ld de,hstbuf ld b,4; record sector call wrt; write sector into hstbuf pop de; restore error flags pop hl jp z,wtchk; good op dec e; retry count jp nz,wthst2; try again dec d; home and reseek count jp z,wtchk3; can't recover push hl call dohome; re seek pop hl jp wthst1; reset retry count ; ; Read back the sector to check CRC for a good write wtchk: ld a,(chkwrt) or a ret z; write checking disabled ld b,0 in a,(nmivec) push af ld a,retcod ld (nmivec),a ld a,rdcmd; read it back to check CRC out (cmnd),a wtchk1: halt in a,(data) djnz wtchk1 wtchk2: halt in a,(data) djnz wtchk2 pop af ld (nmivec),a call busy and rdmask call nz,saverr; record any soft errors wtchk3: ld (erflag),a call errmsg; saves regs/flags, msg if a <> 0 ret z; either good or entry from wthst dec l jp nz,wthst0; try again ret; with hardware error flags ; ; hstdsk = host disk #, hsttrk = host track #, ; hstsec = host sect #. read "hstsiz" bytes ; into hstbuf and return error flag in erflag. readhst: ld a,(rtry1) ld d,a rdhst1: ld a,(rtry2) ld e,a; retry error counts rdhst2: push de; save error counts call hstcom; set track and sector ld de,hstbuf ld b,4; record sector call rd; read sector into hostbuf ld (erflag),a; error return flag pop de; restore error flags ret z; good op dec e; retry count jp nz,rdhst2; try again dec d; home and reseek count call z,errmsg; saves regs/flags, message if a<>0 ret z; can't recover call dohome; re seek jp rdhst1; reset retry count ; hstcom: ld a,(hstdsk); select disk ld c,a ld e,1; NOT a CPM call, dont force sensing call dsksel ld hl,(hsttrk); set track to hsttrk ld b,h; one byte more than "ld bc,", and ld c,l; easily tracked with ddt etc. call trkset; physical seek ld a,(hstsec); set physical sector ld c,a; c=sector jp secset; and exit ; ; show error details if a <> 0 errmsg: push af or a jp z,errmsgx; all well ; " " ; Show disk, track, sector, error kind. ; Must save and restore cursor position. ; This can foul the screen if it occurs in the midst of ; an escape sequence from the application program. push bc push de push hl ld c,esc call .vidout; separate, to get orig. cursor push hl; save cursor ld de,posn call .print; position to error msg location ; " " ; all positioned, now we can write the error details ld a,(dsk) add 'A' ld c,a call .vidout ld de,part2 call .print ld a,(hsttrk) call decout ld de,part3 call .print ld a,(hstsec) call decout ld a,(erflag) ld hl,errids-5 ld de,5 erm1: add hl,de; at least one bit must be set rlca jp nc,erm1; find appropriate message ex de,hl call .print ; " " ; restore the original cursor position ld c,esc call .vidout ld c,'=' call .vidout pop hl push hl ld c,h; the original "y" call .vidout pop bc; and the original "x" call .vidout pop hl pop de pop bc errmsgx: pop af ret ; posn: db '=', 23+' ', 64+' ',0; leaving 16 char msg space part2: db ': T',0 part3: db ', S',0 errids: db ' NRY',0; bit 7 - not ready db ' WPR',0; 6 - write protect db ' WFT',0; 5 - write fault db ' RNF',0; 4 - record not found db ' CRC',0; 3 - CRC error db ' LDA',0; 2 - data overrun db ' DRQ',0; 1 - data not supplied db ' BSY',0; bit 0 - not ready ; ; output (a) as 2 digit decimal no. ; a,f,c,h,l decout: ld c,'0'-1 dco1: inc c sub 10 jp nc,dco1 push af ld a,'0' cp c call nz,.vidout; suppress leading zero pop af add '0'+10 ld c,a jp .vidout ; ; save and record soft error counts. Save regs. saverr: push hl push af; the error ld a,(dsk) ld hl,aerr or a jp z,saver1; a drive inc hl inc hl; b drive, advance pointer inc hl saver1: pop af push af ld (hl),a inc hl inc (hl); count the error jp nz,saver2; no carry inc hl inc (hl) saver2: pop af pop hl ret ; subttl Physical disk I/O, RAM image ; ioimage: ; This area is moved to memory for execution. ; All jumps must be relative for position independance ; ; block memory move, turn rom on/off .move: in a,(bitport); turn rom off and not bankbit out (bitport),a ldir; move logical sector from hstbuf in a,(bitport); turn rom back on or bankbit out (bitport),a ret; back to rom ; ; read a sector into de^ ; return A=0 for no errors, ; A=1 for non-recoverable error ; if b=1 128, b=2 256, b=3 384, b=4 512 bytes/sector .rd: ld hl,rdmask*256+rdcmd; d=read status mask jr action; e=read command ; ; write a sector from de^ ; return as per read from de^. ; b is sector length in 128 byte records .wrt: ld hl,wrtmask*256+wrtcmd; d=status mask, ; " " e=write command ; " " action: ex de,hl call ready; make sure drive is on and ready di; no interrupts during disk I/O in a,(bitport); turn rom off and not bankbit out (bitport),a ld a,(nmivec) push af ld a,retcod ld (nmivec),a; set up nmi vector ld a,b; sector multiple ld bc,128*256+data; b=sector length, c=data port bit 0,a; if 0 then 256 or 512 bytes/sector jr nz,actn; b set for 128 or 384 bytes/sector ld b,0; b set for 256 or 512 bytes/sector actn: cp 1; compute entry point 1st or 2nd loop push psw; save as Z flag ld a,e; i/o command cp wrtcmd; a write? jr z,wstart; start write command ; " " out (cmnd),a; fall through to read loop pop psw jr z,rl2 rl1: halt; wait for controller ini jr nz,rl1 rl2: halt ini jr nz,rl2 jr done; read loop done, exit ; wstart: out (cmnd),a; write loop pop psw jr z,wl2 wl1: halt outi jr nz,wl1 wl2: halt outi jr nz,wl2 ; " " done: pop af; byte at nmi vector address ld (nmivec),a; restore it in a,(bitport); turn rom back on or bankbit out (bitport),a ei; turn interrupts on call busy; get status when contoller not busy and d; status mask jp nz,saverr; If soft error record it & exit ret; with any non-zero error code ; ; This section defines the disk parameters ; (dph's are images moved to RAM) .dpha: dw 0,0,0,0; dph for unit A: dw dirbuf,dpbd; directory buffer, Disk Param. Block dw csva, alva; check sum pointer, alloc. map ptr db ddbit; density flag for this drive ; .dphb: dw 0,0,0,0; dph for unit B: dw dirbuf,dpbd; directory buffer, Disk Param. Block dw csvb, alvb; check sum pointer, alloc. map ptr. db ddbit; density flag for this drive ; ; ( single density ); .dpbs: dw 18; (spt) sectors per track db 3; (bsh) block shift factor db 7; (blm) block mask db 0; (exm) extent mask dw 82; (dsm) max logical block # dw 31; (drm) max directory # db 80H; (al0) directory allocation map db 00H; (al1) dw 8; (cks) size of dir. check vector dw 3; (off) reserved tracks ; ; ( single sided/double density ) moved in as needed .dpbd: dw 40; (spt) sectors per track db 3; (bsh) block shift factor db 7; (blm) block mask db 0; (exm) extent mask dw 194; (dsm) max logical block # dw 63; (drm) max directory # db 0F0H; (al0) dir. alloc. map/BIOS space db 00H; (al1) dw 16; (cks) size of directory check vector dw 1; (off) reserved tracks ; ; sector interleave table ( single density ). Kept in RAM .tbl1: db 1,6,11,16 db 3,8,13,18 db 5,10,15,2 db 7,12,17,4 db 9,14 ; .imgend: imaglen equ .imgend-ioimage; length of this image ; ; Following only initialized on cold boot (power on) .ckwrt: db 0ffh; initializer, check for good writes .rtry1: db tries1; (0th table entry special) .rtry2: db tries2; initial error retry counts .adata: db 0,0,0; last error and soft error counts .bdata: db 0,0,0; and the same for the "b" drive .ckend: ; ; ( double sided/double density). Moved in as needed .dsdd: dw 40; (spt) sectors per track db 4; (bsh) block shift factor db 0fh; (blm) block mask db 1; (exm) extent mask dw 196; (dsm) max logical block dw 63; (drm) max directory # db 0c0h; (al0) dir. alloc. map & bios space db 0; (al1) dw 16; (cks) size of directory check vector dw 1; (off) reserved tracks ; ; ---------------------------------------------------------- ; NOTE: the data segment is placed after the code segment ; because storage lengths depend on the code to be moved in ; ---------------------------------------------------------- dseg ; ; ; Disk operations - requested values sekdsk: ds 1; disk requested sektrk: ds 2; track number seksec: ds 1; sector number ; dsk: ds 1; current disk drive ctrack: ds 1; track number (real) for current drive denflag: ds 1; density flag for current drive sidflg: ds 1 tracka: ds 3; Drive A track (255 means density unknown) trackb: ds 3; Drive B track (255 means density unknown) csva: ds 16; Drive A directory check alva: ds 26; Drive A allocation map csvb: ds 16; Drive B directory check alvb: ds 26; Drive B allocation map ; ; Routines inserted on power-on start ; ** THIS AREA MUST MATCH THE CODE ABOVE, moved in ** ; move: ds .rd-.move; move mem blocks, callable from rom rd: ds .wrt-.rd; read physical sector size b, to de^ wrt: ds .dpha-.wrt; write physical sect size b from de^ ; END of installed routines ; ; Disk drive stuff, initialized on power-on start ; ; Disk paramater blocks etc dpha: ds .dphb-.dpha; dph for A dphb: ds .dpbs-.dphb; dph for B dpbs: ds .dpbd-.dpbs; single density disk param blk dpbd: ds .tbl1-.dpbd; double density disk param blk tbl1: ds .imgend-.tbl1; single density skew table ; ; Any disk error parameters follow CHKWRT. HOME returns a pointer ; to chkwrt (in hl), to allow external systems to modify. ; An unmodified Kaypro returns a pointer to page 0, so we can ; tell them apart. ; MAKE SURE these agree with declaration from .chkwrt on in code chkwrt: ds 1; init true, check for good writes rtry1: ds 1 rtry2: ds 1 ; ; Following counts etc measured from last cold start time aerr: ds 1; last soft error on "a" aerrct: ds 2; count of soft errors on "a" berr: ds 1; last soft error on "b" berrct: ds 2; count of soft errors on "b" ; Can add further disk drives here. Use only for this structure ; ; ** ------- END of moved in area --------- ** ; ; Storage dsksns: ds 6; buffer for disk trk/adr data ; dirbuf: ds 128; CP/m directory buffer ; end