title mbios 1.4 - magnum bios for cpm 2.2 .printx "magnum bios for cpm 2.2" ;*************************************************************************** ; ; Micro Design Associates ; ; 25 S. 8th Street ; Columbia, Missouri 65201 ; phone: (314) 874-2777 ; ; Inquiries will be gladly addressed by: ; David Wallen ; Don Delwood ; ; This is the Micro Design Associates official Basic Input/Output ; System (BIOS) for CP/M 2.2 (by Digital Research). It was designed ; to utilize Micro Design Associates' disk controllers - specifically ; the S-100 style FC-100 and Magnum style V-44-FC - both of which ; are based on the Western Digital 279X disk controller chip family. ; ; This BIOS and the family of related utilites provide a wide range ; of floppy disk interchangeability. The FLEXFORM program family allow ; alteration of the disk description parameters to both the disk ; controller and the BDOS. This enables the use of virtually any ; soft sectored 8 inch or 5 1/4 inch floppy disk format. ; ; Full error recovery support is provided. The SETERR program allows ; the user to specify any number of error recovery attempts - or no ; attempts and thus disabling this feature. Error reporting is likewise ; independently selectable. When enabled, each error is reported ; specifying the disk, cylinder, side, and sector, whether the error ; occured while reading or writing and the disk controller error ; status. When disabled, error recovery still occurs only without ; the printed notification. ; ; The Micro Design Associates BIOS and related utilities are ; distributed to owners of M.D.A. hardware. Since they are protected ; by copyright, they should not be distributed except with that hardware. ; ; (C) Copyright 1983 Don Delwood ;*************************************************************************** page .z80 msize equ 62 ; true top of ram ; when generating system using MOVCPM, use MSIZE-3 ; for nn, so command format is: MOVCPM nn * bias equ (msize-23)*1024 ccp equ bias+03400h ; base of ccp bdos equ ccp+806h ; entry of bdos biosb equ ccp+1600h ; base of bios biosjmp equ 00 ; BIOS entry vector iobyte equ 03 ; intel i/o byte cdisk equ 04 ; current logged in disk bdosjmp equ 05 ; BDOS entry vector dmadef equ 0080h ; default dma address stack equ 0100h ; stack base cstat equ 03h ; console status port cdata equ 02h ; console data port lstat equ 08h ; list device status port ldata equ 08h ; list device data port dskbase equ 0d0h ; base port for floppy disk controller trk equ 00 ; offset byte 0 set equ 01 ; offset byte 1 rate equ 02 ; offset byte 2 lcode equ 03 ; offset byte 3 maxtry equ 85h ; report errors, 5 re-tries subttl jump tables page ;*************************************************************************** ; ; standard CP/M jump table ; ;*************************************************************************** .phase biosb start: jp boot ; cold start bios: jp wboot ; warm start jp const ; console status jp conin ; console character in jp conout ; console character out jp list ; list character out jp punch ; punch character out jp reader ; reader character in jp home ; move head to home position jp seldsk ; select disk jp settrk ; set track number jp setsec ; set sector number jp setdma ; set dma address jp read ; read disk jp write ; write disk jp listst ; return list status jp sectran ; sector translate ;*************************************************************************** ; ; customized address/jump table ; ;*************************************************************************** bmap: dw dspb ; address of the disk setup parameter base dw dphm ; address of the disk parameter header map dw bootdsk ; address of the byte defining boot disk dw numdsks ; address of the byte defining the # of disks dw end ; address of length of bios (pure code) dw usermsg ; address of the user message area dw start ; address of start of bios dw drvnam ; address of table of drive names dw rwmax ; address of byte defining # of error re-tries .xlist db "(C) Copyright 1983 Don Delwood" .list subttl cold and warm boot page ;*************************************************************************** ; ; initialization code for cold starts ; ;*************************************************************************** boot: ld sp,stack ; initialize stack xor a ld (iobyte),a ; init the iobyte ld (dirty),a ; init the dirty byte ld a,(cdisk) ; boot rom has set current default disk ld (bootdsk),a ; also make it the boot disk ld bc,0 ; init diskno to any legal value 0~3 ld (diskno),bc call initdev ; initialize i/o devices from cold boot call signon ; print initial sign on message ;*************************************************************************** ; ; initialization code for warm starts ; ;*************************************************************************** wboot: ld sp,stack ; initialize stack ld a,(dirty) ; reset the buffer by... res update,a ; ...mark as clean buffer ld (dirty),a ld a,0ffh ; ...mark as impossible disk ld (bdisk),a xor a ld (rwerr),a ; ...erase error count page ; track sector ( 1024 bytes/sector ) ; 1 2 3 4 5 ; +-------+-------+-------+-------+-------+ ; 0 | * |<========== bios =========>| ccp1 | ccp -- 0800h 2.0k ; +-------+-------+-------+-------+-------+ bdos -- 0e00h 3.5k ; 1 | ccp2 |<========== bdos =========>| @ | bios -- 0f80h 3.875k ; +-------+-------+-------+-------+-------| ; ; * - id block 128 bytes: volume name, date, owner, reserved ; @ - reserved 512 bytes: ld bc,(bootdsk) ; select the boot disk ld e,0 call seldsk ld ix,(diskno) ; get the board setup byte call seldspb ld a,(ix+set) set 4,a ; set to side 0 res 5,a ; set to double density out (brdset),a ; send board setup byte to controller call dskrdy ; get it ready ; read in 1st half of ccp, track:0 sector:5 ld hl,5 ; starting sector ld bc,1024 ; 1k bytes to read ld de,ccp ; address of ccp call msr ; multiple sector read cp 0 ; was the read ok? jr z,wboot1 ; then continue ld hl,wbmsg1 ; report ccp1 read error call print jp wboot wbmsg1: db 0ah,0dh,"CCP1 read error","$" ; find track 1 and step in or flip sides accordingly wboot1: bit 2,(ix+rate) ; test sidedness jr z,stpin ; if single sided then step in ld a,(ix+set) ; else set controller for side 1 res 4,a out (brdset),a jr wboot2 stpin: ld a,stpncmd ; step in di out (cmdport),a call busywait ; read in the rest of the ccp and the bdos, track:1 sectors:1-5 wboot2: ld hl,1 ; starting sector ld bc,0400h+0e00h ; # of bytes to read ; ld de,ccp+0401h ; de remains set from previous msr call call msr ; do multiple sector disk read cp 0 ; was the read ok? jp z,gocpm ; then continue ld hl,wbmsg2 ; report ccp2/bdos read error call print jp wboot wbmsg2: db 0ah,0dh,"CCP2/BDOS read error","$" gocpm: ld a,0c3h ; 0000: jp bios ld (biosjmp),a ld hl,bios ld (biosjmp+1),hl ld (bdosjmp),a ; 0005: jp bdos ld hl,bdos ld (bdosjmp+1),hl ld bc,dmadef ; default dma address is 80h call setdma ld a,(cdisk) ; send current disk number to the ccp ld c,a jp ccp subttl device drivers page ;*************************************************************************** ; ; peripheral input/output device drivers ; ;*************************************************************************** ; ; 0003: iobyte ; +-------+-------+-------+-------+ ; | 7 : 6 | 5 : 4 | 3 : 2 | 1 : 0 | ; +-------+-------+-------+-------+ ; list punch reader console ; 00 01 10 11 ; +-------------------------------+ ; con | tty | crt | bat | uc1 | ; +-------------------------------+ ; rdr | tty | ptr | ur1 | ur2 | ; +-------------------------------+ ; pun | tty | ptp | up1 | up2 | ; +-------------------------------+ ; lst | tty | crt | lpt | ul1 | ; +-------------------------------+ ; const <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > console device status ; ; enter: none ; exit: a - 00h: no character ready ; a - ffh: character ready ; changed: a const: ld b,3 ; mask for bits 0,1 ld a,(iobyte) and b call case dw ttyst ; iobyte: 00 - tty dw ttyst ; iobyte: 01 - crt dw ttyst ; iobyte: 10 - bat dw ttyst ; iobyte: 11 - uc1 ttyst: in a,(cstat) ; get a byte from the status port bit 1,a ; test to see if a character is ready jr nz,ttyst1 ; if so - jump to ready ld a,0 ; if not then load zero as signal ret ; return ttyst1: ld a,0ffh ; if so load 0ffh as signal ret ; conin <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > read character from console device (bdos01, bdos06) ; ; enter: none ; exit: a - low 7 bits of input character ; changed: a conin: ld b,3 ; mask for bits 0,1 ld a,(iobyte) and b call case dw ttyin ; iobyte: 00 - tty dw ttyin ; iobyte: 01 - crt dw ttyin ; iobyte: 10 - bat dw ttyin ; iobyte: 11 - uc1 ttyin: in a,(cstat) ; get a byte from the status port bit 1,a ; test for character ready jr z,ttyin ; if console not ready, try again in a,(cdata) ; if ready, read the character and 07fh ; strip off parity bit ret ; conout <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > send character to console device (bdos02, bdos06) ; ; enter: c - 7 bit character to send to console ; exit: none changed: a conout: ld b,3 ; mask for bits 0,1 ld a,(iobyte) and b call case dw ttyout ; iobyte: 00 - tty dw ttyout ; iobyte: 01 - crt dw ttyout ; iobyte: 10 - bat dw ttyout ; iobyte: 11 - uc1 ttyout: in a,(cstat) ; get a byte from terminal status bit 0,a ; test lowest bit jr z,ttyout ; if console not ready, try again ld a,c ; transfer byte to write out (cdata),a ; if ready, write the character ret page ; listst <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > logical list device status ; ; enter: none ; exit: a - 00h: not ready for a character ; a - 01h; ready for character ; changed: a listst: ld b,3 ; mask for bits 0,1 ld a,(iobyte) rlca ; shift bits 7,6 to 1,0 rlca and b call case dw lptst ; iobyte: 00 - tty dw ttyst ; iobyte: 01 - crt dw lptst ; iobyte: 10 - lpt dw lptst ; iobyte: 11 - ul1 lptst: in a,(ldata) ; input port status bit 0,a ; test for status ld a,0 ; if ready then a should be 0 ret z ; so leave ld a,1 ; else a should be 1 ret ; list <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > send character to logical list device (bdos05) ; ; enter: c - 7 bit character to send to list device ; exit: none ; changed: a list: ld b,3 ; mask for bits 0,1 ld a,(iobyte) rlca ; shift bits 7,6 to 1,0 rlca and b call case dw lptout ; iobyte: 00 - tty dw ttyout ; iobyte: 01 - crt dw lptout ; iobyte: 10 - lpt dw lptout ; iobyte: 11 - ul1 lptout: in a,(lstat) ; input port status bit 0,a ; test for status jr nz,lptout ; jump back if not ready ld a,c ; character to register a or 80h ; strobe stuff out (ldata),a ; write it xor 80h ; more strobe stuff out (ldata),a ; write it xor 80h ; still more strobe stuff out (ldata),a ; write it ret page ; punch <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > send character to logical punch device (bdos04) ; ; enter: c - 7 bit character to send to punch device ; exit: none ; changed: a punch: ret ; null subroutine ; reader <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > get next character from logical reader device (bdos03) ; ; enter: none ; exit: a - 7 bit character from reader device ; changed: a reader: ret subttl select disk page ; seldsk <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > select disk drive specified for further disk operations ; ; enter: c - logical disk to select (0~15 :: A~P) ; e - virgin flag 0: first select ; exit: hl - selected disk parameter header address ; hl - 00 is error ; changed: register ix ; misc: diskno seldsk: push af ; save regular registers push bc push de ld hl,0 ; set hl to zero for default error ld a,(numdsks) ; test for valid disk dec a ; convert number of disks to 0 based cp c ; do the compare ret c ; *** error return *** c > numdsks ld ix,(diskno) ; get old disk's dspb call seldspb in a,(trkreg) ; save cylinder position of old disk ld (ix+trk),a ld b,0 ; select new disk ld (diskno),bc ld ix,(diskno) ; new disk number call seldspb ; get the table address in ix ld a,(ix+trk) ; get the new track address out (trkreg),a ; set the controller up for current track ld a,(ix+set) ; get the new setup byte out (brdset),a ; set the controller setup byte ; set the rate bits of the type I commands: seek and restore ld a,(ix+rate) ; get the rate/sidedness/etc byte and 03 ; isolate the rate bits 0-1 ld b,a ; save into reg: b ld a,(seekcmd) ; get the seek command and 0fch ; clear its rate bits or b ; set the rate bits ld (seekcmd),a ; save it back ld a,(restcmd) ; get the restore command and 0fch ; clear its rate bits too or b ; set the rate bits ld (restcmd),a ; save it back bit 0,e ; test for virgin needing login call z,reshom ; set home and home disk ; compute the disk parameter header address ld hl,(diskno) add hl,hl ; *2 add hl,hl ; *4 add hl,hl ; *8 add hl,hl ; *16 (size of each header) ld de,dphm ; de=table base address add hl,de ; hl=dphm(disk) pop de ; restore registers pop bc pop af ret subttl home and set track page ; home <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > set selected logical disk head to track 0 ; ; enter: none ; exit/changed: see settrk home: ld bc,0 ; special case of settrk ; settrk <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > establish logical track number for next r/w operation ; (actually converts track to side/cylinder pair) ; ; enter: bc - logical track number ( ex. 0-153 for double sided 8 in. ) ; exit: none ; misc: side, cylind ; cylinder 0 1 2 3 4 ... ; track conversion: side 0 - 000 002 004 006 008 ... max-1 ; side 1 - 001 003 005 007 009 ... max ; sector header: cylinder : side : sector : length code ; settrk: ld (track),bc ; save logical track ld ix,(diskno) ; test drive sidedness call seldspb bit 2,(ix+rate) jr z,mgtk0 ; if 0 then the drive is single sided ; for ds: track/2 = cylinder, if even -> side 0, if odd -> side 1 srl b ; double register shift rr c ; log trk -> cylinder (bc), side(carry) jr nc,mgtk0 ; if even, select side 0 xor a ld (side),a ; if odd, set side mask for side 1 jr mgtk mgtk0: ld a,10h ; set side mask for side 0 ld (side),a mgtk: ld (cylind),bc ; set cylinder ret subttl set sector page ; setsec <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > establish record for next i/o operation ; (actually converts logical record to physical sector/offset pair) ; ; enter: bc - standard 128 byte record number ; note: depending on dspb, offset 3, bit 3 ... ; if first sector is 1 then bc ranges from 1~spt ; if first sector is 0 then bc ranges from 0~spt-1 ; exit: none ; misc: dsect, offset setsec: ld (sector),bc ; save original value ld ix,(diskno) ; initialize... call seldspb ld a,(ix+lcode) and 7 ; ... a <-- length code ld hl,0 ; ... hl <-- 0 (buffer offset) ld de,128 ; ... de <-- 128 (sector length) or a ; for lcode=0, all done jr z,ss7 bit 3,(ix+lcode) ; if starting sector number is 1... jr nz,sstop dec bc ; adjust to 0 base temporarily for math below ; perform an "n shift" of reg pair BC where "n" is lcode value (1~7) in a ; low n bits - value v such that 128*2^v is buffer offset ; high 16-n bits - when 1 based, is the physical sector sstop: srl b ; shift lsb into carry rr c ; rotate into msb of c, lsb -> carry jr nc,ss10 ; was it a 1 add hl,de ; add "adder" value into running total ss10: sla e ; effective *2 rl d ; continued dec a ; count down jr nz,sstop ; if not done jump back bit 3,(ix+lcode) ; if starting sector number is 1... jr nz,ss7 inc bc ; revert back to 1 base ss7: ld (dsect),bc ; physical destination sector ld (offset),hl ; offset into sector buffer ret subttl sector translation and set dma page ; sectran <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > translate record according to disk skew factor ; ; enter: bc - 128 byte record number to translate (0~n) ; de - address of skew table ; exit: hl - translated 128 byte record number ; changed: none ; note: seldsk returns dph address in hl. The first word of this table ; contains address of skew table. If this entry is zero, then ; sectran should not have been called. Sectran anticipates this ; simple error and recovers nicely. sectran:push af ; save stuff ld a,d ; fold de... or e ; ... onto itself jr nz,sect1 ; if non-zero, then real table push bc ; otherwise, just copy... pop hl ; into exit parameter jr sect2 sect1: ex de,hl ; hl= translate table addr add hl,bc ; hl= trans(sector) ld l,(hl) ; l = trans(sector) ld h,0 ; hl= trans(sector) sect2: pop af ret ; with value in hl ; setdma <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > establish file buffer address for r/w operations ; ; enter: bc - address for system r/w operations ; exit: none ; changed: none setdma: ld (dmaad),bc ret subttl disk read operations page .printx "disk read operations" ; read <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > record placed at last SETDMA as per SELDSK, SETTRK and SETSEC ; ; enter: none ; exit: a - 0: no error nonzero: error read: call match ; ensure desired sector is in memory buffer cp 0 ; test for error ret nz ; *** error return to bdos *** rls: ld hl,(offset) ; "read logical sector" i.e. 128 byte record... ld de,buffer ; ...from proper offset in buffer add hl,de ld de,(dmaad) ; ...to dma address ld bc,128 ldir ret ; always a pretty return subttl disk write operations page ; write <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> ; ; > record specified by last SETDMA written as per SELDSK, SETTRK, SETSEC ; whether actually written to disk depends on buffering scheme and C ; ; enter: c - 0: normal write, preread, defer writing if possible ; 1: directory, preread, NO defer of writing ; 2: first write, no preread, defer writing if possible ; exit: a - 0: no error nonzero: error write: ld a,c call case dw normal ; 0 normal write dw dir ; 1 directory write dw newblk ; 2 first write normal: call match ; match the desired sector to buffered sector cp 0 ; test for error ret nz ; *** error return to bdos *** call wls ; write from dma to buffer ret ; always a good return dir: call normal ; directory write follows same path cp 0 ; test for error ret nz ; *** error return to bdos *** call hstwrt ; write buffered physical sector immediately ret newblk: call match ; does a preread to position disk head properly cp 0 ; test for error ret nz ; *** error return to bdos *** ; this does not initialize entire new allocation block, just new 1k buffer ld a,fill ; initialize buffer with fill character E5h ld (buffer),a ld hl,buffer ld de,buffer+1 ld bc,1024-1 ldir call wls ; write a logical sector into the buffer ret ; > move a logical sector from dma to buffered physical sector ; enter: none exit: a - 0 changed: all wls: ld hl,(offset) ; "write logical sector" i.e. 128 byte record.. ld de,buffer ; ...from proper offset in buffer add hl,de ld de,(dmaad) ; ...to dma address ex de,hl ld bc,128 ldir ld a,(dirty) ; mark buffered physical sector as updated set update,a ; set dirty bit ld (dirty),a ; store flag xor a ; always a clean return ret subttl primative block read routine page ; hstrd {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} ; ; > read 1024 byte physical buffer as per diskno, cylind, side, dsect ; label the physical buffer as per bdisk, bcylind, bside, bsect ; ; note: error-on-read leaves buffer invalid, MATCH will force re-read ; entry: none ; exit: none ; changes registers - all ; hstrd: ld a,(dirty) ; make buffer invalid until successful read set valid,a ld (dirty),a ld bc,(diskno) ; establish the "b" variables ld (bdisk),bc ; disk ld bc,(cylind) ld (bcylind),bc ; cylinder ld bc,(side) ld (bside),bc ; side ld bc,(dsect) ld (bsect),bc ; sector ld a,(bcylind) ; seek to proper cylinder out (datreg),a ld a,(seekcmd) di out (cmdport),a call busywait ; track reg = data reg = cylinder ld a,(bsect) ; set sector out (secreg),a ld a,(bside) ; set side ld b,a ld ix,(bdisk) ; get board setup byte call seldspb ld a,(ix+set) res 4,a ; clear sidedness bit or b ; set to proper side out (brdset),a bit 4,(ix+lcode) ; set track register (if not same as cylinder) jr z,hr2 ld a,(track) out (trkreg),a hr2: call dskrdy ; make drive ready call lenlen ; bc <-- sector length ld de,buffer ; de <-- physical buffer pointer ld a,rdcmd ; read sector call rsec ; controller read primative call busywait ; wait till the read is done in a,(status) ; get the status and 9ch ; preserve bits 7,4,3,2 ld (tempst),a ; save status temporarily bit 4,(ix+lcode) ; reset track register to represent cylinder jr z,hr4 ld a,(bcylind) out (trkreg),a hr4: ld a,(tempst) ; if error, leave buffer as invalid cp 0 jr nz,hr1 ld a,(dirty) ; no error, mark buffer as valid res valid,a ld (dirty),a jr hrend ; read error recovery hr1: ld a,(rwerr) ; increment error counter inc a ld (rwerr),a ld b,a ; store in b ld a,(rwmax) ; get maximum of error tries and 7fh ; clear "message" bit cp b jr c,hrend ; exit regardless after max error attempts ld bc,(bdisk) ; re-select disk ld e,1 call seldsk call restr ; restore disk (don't home! will mess track) ld a,(rwmax) bit 7,a ; test "message" bit jp z,hstrd ; don't print error message ld a,(tempst) ; status ld bc,hr3 ; message call prterr jp hstrd hr3: db ' read error ','$' hrend: xor a ; init error counter ld (rwerr),a ld a,(tempst) ; restore error status ret page subttl primative block write routine ; hstwrt {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} ; ; > perform actual writing of buffered physical sector to disk ; ; note: always clears dirty bit - keeps error-on-write from hanging ; enter: none ; exit: a - 0: no error nonzero: error ; changed: all hstwrt: ld bc,(diskno) ; save current logical disk push bc ld bc,(bdisk) ; select physical buffer's disk ld e,1 call seldsk hw1: ; assume head is already set to cylinder ld a,(bside) ; set side ld b,a ld ix,(bdisk) ; get board setup byte call seldspb ld a,(ix+set) res 4,a ; clear sidedness bit or b ; set to proper side out (brdset),a ld a,(bsect) ; set sector out (secreg),a bit 4,(ix+lcode) ; set track register (if not same as cylinder) jr z,hw4 ld a,(track) out (trkreg),a hw4: call dskrdy ; make sure the disk is ready call lenlen ; bc <-- sector length ld de,buffer ; de <-- physical buffer pointer ld a,wrcmd ; write sector command call wsec ; controller write primative call busywait ; wait till the controller is done in a,(status) ; get the status for error checking and 0dch ; preserve bits 7,6,4,3,2 ld (tempst),a ; save status temporarily bit 4,(ix+lcode) ; reset track register to represent cylinder jr z,hw5 ld a,(bcylind) out (trkreg),a hw5: ld a,(tempst) ; exit if no error cp 0 jr z,hwend ; write error recovery hw2: ld a,(rwerr) ; increment error counter inc a ld (rwerr),a ld b,a ; store in b ld a,(rwmax) ; get maximum of error tries and 7fh ; clear "message" bit cp b jr c,hwend ; exit regardless after max error attempts ld bc,(bdisk) ; re-select, re-home disk ld e,0 call seldsk ld bc,(bcylind) ; re-seek head out (datreg),a ld a,(seekcmd) di out (cmdport),a call busywait ld a,(rwmax) ; test "message" bit bit 7,a jp z,hw1 ; don't print error message ld a,(tempst) ; status ld bc,hw3 ; message call prterr jp hw1 hw3: db ' write error ','$' hwend: ld a,(dirty) ; clear the update bit of the dirty word res update,a ld (dirty),a pop bc ; restore originally selected disk ld e,1 call seldsk xor a ; init error counter ld (rwerr),a ld a,(tempst) ; restore status for error checking ret subttl common routines and error recovery page .printx "common routines" ;--------------------------------------------------------------------------- ; common routines ;--------------------------------------------------------------------------- status equ 00h+dskbase cmdport equ 00h+dskbase trkreg equ 01h+dskbase secreg equ 02h+dskbase datreg equ 03h+dskbase brdset equ 04h+dskbase wait equ 06h+dskbase timerst equ 07h+dskbase valid equ 1 ; validity bit update equ 0 ; update bit of dirty word fill equ 0e5h ; cp/m sector fill character nullcmd equ 0d0h ; board interrupt mrdcmd equ 094h ; multiple sector read command rdcmd equ 084h ; the read command wrcmd equ 0a0h ; the write command stpncmd equ 5bh ; slow step-in command w/update cr equ 0dh lf equ 0ah ;--------------------------------------------------------------------------- ; case ; ; > computed case routine ; ; enter: a - number of routine to branch to. 0 based value ; exit: none ; changed: de case: ld (temp),hl ; save hl ld l,a ; a -> hl ld h,0 add hl,hl ; *2 to get offset ex de,hl ; put into de ex (sp),hl ; get address of first routine add hl,de ; add offset, now have address of nth routine ld e,(hl) ; de gets nth routine inc hl ld d,(hl) ex de,hl ; and transfer to hl ex (sp),hl ; put back into stack ld hl,(temp) ; restore hl ret ; and go to it page ;--------------------------------------------------------------------------- ; match ; ; > writes buffer to disk if previously updated ; > then ensures that desired sector is in buffer ; ; enter: none ; exit: a - i/o error flag ; changed: all match: ld ix,dsect ; are the sectors the same? ld iy,bsect call comp16 jr nz,flush ; no match - get a new sector ld ix,cylind ; are the cylinders the same? ld iy,bcylind call comp16 jr nz,flush ; no match - get a new sector ld ix,side ; are the sides the same? ld iy,bside call comp16 jr nz,flush ; no match - get a new sector ld ix,diskno ; are the disks the same? ld iy,bdisk call comp16 jr nz,flush ; no match - get a new sector ld a,(dirty) ; is buffer valid? bit valid,a jr nz,flush ; no - get a new sector ; sector already in memory - use it xor a ; signal no "i/o" error ret ; return if everything matches ; flush > writes updated buffer to disk ; enter: none ; exit: a - error flag flush: ld a,(dirty) ; has the buffer been updated? bit update,a jr z,fsh call hstwrt ; yes, save buffer to disk cp 0 ret nz ; report write error fsh: call hstrd ; get needed sector ret ; comp16 > a 16 bit compare ; enter: ix - address of 2 byte value1 ; iy - address of 2 byte value2 ; exit: z flag true if value1=value2 comp16: ld a,(ix) ; get low half of value1 cp (iy) ; compare to low half of value2 ret nz ld a,(ix+1) ; get high half of value1 cp (iy+1) ; compare to high half of value2 ret ;--------------------------------------------------------------------------- ; seldspb ; ; > return dspb address of specified logical disk ; ; enter: ix - logical disk number ; exit: ix - dspb of specified disk ; changed: none seldspb:push de ; save values in registers add ix,ix ; ( * 2 ) shift for offset add ix,ix ; ( * 4 ) ld de,dspb ; get table base add ix,de ; get table offset pop de ; restore register values ret ;--------------------------------------------------------------------------- ; lenlen ; ; > returns sector length for any given disk ; ; entry: ix - dspb of desired drive ; exit: bc - sector length in bytes ; changed: ix lenlen: push af ; save registers push hl ld a,(ix+lcode) ; get the code and 7 ; mask off bits 0-2 add a,a ; double the value... ld c,a ; ...to convert to an offset ld b,0 ; put into bc ld hl,lencode ; get length xlate table base add hl,bc ; add offset ld c,(hl) ; fetch contents inc hl ld b,(hl) pop hl ; restore registers pop af ret lencode:dw 128,256,512,1024 ; length code table page ;--------------------------------------------------------------------------- ; print ; ; > prints a string delimited by a '$' sign ; ; enter: hl - string address ; exit: hl - points to delimiter ; changed: none print: push af push bc pchar: ld a,(hl) ; get a byte to print cp '$' ; is it end byte? jp z,pdone ; ld c,a ; if not then setup byte call conout ; print byte out inc hl ; goto next byte jp pchar ; then back to start pdone: pop bc pop af ret ;--------------------------------------------------------------------------- ; prterr ; ; > prints message, buffer disk, side, sector, and status ; ; enter: a - status ; bc - message ; exit: none ; changed: none prterr: push af ; save registers push bc ld hl,crlf ; new line call print pop hl ; message line call print ld a,(bdisk) ; disk add a,'A' ld c,a call conout ld c,58 ; ':' call conout ld a,(bcylind) ; cylinder call ahex ld hl,hslash ; 'h/' call print ld a,(bside) ; side cp 0 jr z,errs1 ld hl,ems0 ; 's0' jr errcom errs1: ld hl,ems1 ; 's1' errcom: call print ld c,46 ; '.' call conout ld a,(bsect) ; sector call ahex ld hl,stmsg ; 'status: ' call print pop af ; status call ahex ret ems0: db 's0$' ems1: db 's1$' hslash: db 'h/$' crlf: db cr,lf,'$' stmsg: db ' status: $' ;--------------------------------------------------------------------------- ; signon ; ; > prints cold boot sign on message ; signon: ld hl,smsg ; print sign-on message call print ret ;--------------------------------------------------------------------------- ; ahex ; ; > prints reg a in 2 hex digits ; ; enter: a - value whose two nibbles get printed ; changed: ahex: push af ; save copy of a rrca ; get high nibble first rrca rrca rrca call nibhex ; convert to ascii call conout ; print it pop af ; get low nibble call nibhex ; ascii call conout ; print ret ;--------------------------------------------------------------------------- ; nibhex ; ; > converts a nibble to hex ; ; enter: a - value whose low 4 bits get converted ; exit: c - hex code for low order nibble ; changed: none nibhex: and 0fh ; mask off lower nibble add a,'0' ; convert to ascii cp '9'+1 ; test for a-f jr c,nib1 ; regular digit add a,7 ; convert to letter a-f nib1: ld c,a ret page ;--------------------------------------------------------------------------- ; clear ; ; > waits until disk status indicates ready ; ; enter: none ; exit: none ; changed: none clear: ; on entry to this command interrupts are expected to be off push af ; save registers ld a,nullcmd ; issue a 0 type interrupt out (cmdport),a call busywait ; wait till done pop af ; restore registers ret ;--------------------------------------------------------------------------- ; busywait ; ; > after controller operation, waits till no longer busy ; ; enter: none ; exit: none ; changed: none busywait:push af ; save registers push bc ld b,10 ; tight loop needed as per Western Digital bwself: djnz bwself bwtest: in a,(status) ; test status for busy bit 0,a jr nz,bwtest ; do it until controller is not busy pop bc ; restore registers pop af ret ;--------------------------------------------------------------------------- ; dskrdy ; ; > waits until either 8 inch or 5.25 inch drive is ready ; ; enter: ix - dspb of desired drive ; exit: none ; changed: af dskrdy: bit 3,(ix+rate) ; test whether drive implements the READY line jr z,dr5 ; disk ready routine for controller/drive combination using READY line dr8: in a,(status) ; test READY line... bit 7,a jr nz,dr8 ; ...until its ready ret ; disk ready routine for unavailable READY line, five revolutions for spin-up dr5: in a,(timerst) ; get the status of the timer bit 3,a ret z ; return if timer still on push bc ; save them registers ld b,05h ; count 5 index pulses dr5a: in a,(status) ; start disk and look for index hole bit 1,a jr z,dr5a dr5b: in a,(status) ; now look for no hole bit 1,a jr nz,dr5b djnz dr5a ; count 5 index pulses pop bc ; restore the registers ret ;--------------------------------------------------------------------------- ; restore ; ; > physically and logically home selected drive ; ; reshom: call home ; tell the system what i will do restr: di ; no interrupts ld a,(restcmd) ; restore command out (cmdport),a call busywait ; wait till the restore is done ret ;--------------------------------------------------------------------------- ; wsec ; ; > write data stream to controller ; ; enter: a - controller write command ; bc - number of bytes to write ; de - source address ; exit: none ; changed: all wsec: di out (cmdport),a wsec1: out (wait),a ; wait to sync z80 and wd279x ld a,(de) ; store into buffer out (datreg),a ; write byte to controller inc de ; increment pointer dec bc ; decrement byte count ld a,c ; done? or b jp nz,wsec1 ; do until count run out ret ;--------------------------------------------------------------------------- ; rsec ; ; > read data stream from controller ; ; enter: a - controller read command ; bc - number of bytes to read ; de - destination address ; exit: none ; changed: all rsec: di out (cmdport),a rsec1: out (wait),a ; wait to sync z80 and wd279x in a,(datreg) ; get byte from controller ld (de),a ; store into buffer inc de ; increment pointer dec bc ; decrement byte count ld a,c ; done? or b jp nz,rsec1 ; do until count run out ret ;--------------------------------------------------------------------------- ; msr ; ; > performs a multiple sector read ; ; enter: bc - number of bytes to read ; de - destination address ; l - starting sector ; exit: error code ; changed: af,ix msr: ld a,l ; set sector to read out (secreg),a ld a,mrdcmd ; load multiple sector read call rsec ; read sector primative in a,(status) ; get the status and 9ch ; mask off 7,4,3,2 call clear ; wait till termination is complete ret page ;--------------------------------------------------------------------------- ; initdev ; ; > initializes input/output ports for use (called from cold boot) ; ; enter: none ; exit: none ; changed: all initdev: ret ; dummy routine subttl initialized data area page ;........................................................................... ; ; initialized data area ; ;........................................................................... smsg: db cr,lf ; system id message db 'micro design associates',cr,lf db ' magnum cp/m 2.2 ' ssize: db '62k',cr,lf db ' bios version 1.4 ',cr,lf,lf db 'drive configuration',cr,lf db ' drive a: ' aname: db 'MAGNUM8 ',cr,lf db ' drive b: ' bname: db 'MAGNUM8 ',cr,lf db ' drive c: ' cname: db 'MAGNUM8 ',cr,lf db ' drive d: ' dname: db 'IBM3740 ',cr,lf usermsg:db '$' ; reserved for 20 character user msg ds 20 numdsks:db 04 ; the number of disks in the system seekcmd:db 18h ; generic seek command restcmd:db 08h ; generic restore command drvnam: dw aname,bname,cname,dname ; table of 8 character drive names rwmax: db maxtry ; number of error recovery attempts ; bit 7: set - print error messages page ;........................................................................... ; disk set-up parameter block ; ; trk - byte which contains cylinder on which physical drive is located ; set - disk controller board setup byte during last read ; rate - rate: (bits 0,1) step rate code for disk drive, 0 is fastest ; sidedness: (bit 2) set for double sided disk drives ; readyness: (bit 3) set if controller/drive support READY line ; lcode - sector size code: (bits 0,1,2) 0:128 1:256 2:512 3:1024 ; sec1: (bit 3) 0:starting sector is 1 1:starting sector is 0 ; trkcyl: (bit 4) 0:sector labeled w/ cylinder 1:... w/ track ; offset 1 (set): board setup byte precomp equ 10000000b ; [no] precompensation inch8 equ 01000000b ; 8 inch floppy disk drive inch5 equ 00000000b ; 5 1/4 inch floppy disk drive sden equ 00100000b ; single density dden equ 00000000b ; double density drv0 equ 00001110b ; physical drive 0 drv1 equ 00001101b ; physical drive 1 drv2 equ 00001011b ; physical drive 2 drv3 equ 00000111b ; physical drive 3 ; offset 2 (rate): physical drive attributes rate0 equ 00000000b ; fastest 8: 3 msec (5: 2x) rate1 equ 00000001b ; fast 8: 6 msec rate2 equ 00000010b ; medium 8: 15 msec rate3 equ 00000011b ; slow 8: 30 msec dside equ 00000100b ; double sided sside equ 00000000b ; single sided ready equ 00001000b ; supports ready line ; offset 3 (lcode): physical diskette attributes l128 equ 00 ; 3 bits for expansion to large sectors l256 equ 01 l512 equ 02 l1024 equ 03 sec0 equ 00001000b ; sector number of first sector is zero sec1 equ 00000000b ; sector number of first sector is one trkcyl equ 00010000b ; sectors labeled with track (Jade) page ; drive a ; magnum 8 inch dspb: db 00 ; old cylinder db precomp+inch8+dden+drv0 ; board setup byte db dside+rate2 ; physical drive characteristics db sec1+l1024 ; diskette characteristics ; drive b ; magnum 8 inch db 00 ; old cylinder db precomp+inch8+dden+drv1 ; board setup byte db dside+rate2 ; sidedness and step speed db sec1+l1024 ; sector length code ; drive c ; magnum 8 inch db 00 ; old cylinder db precomp+inch8+dden+drv2 ; board setup byte db dside+rate2 ; sidedness and step speed db sec1+l1024 ; sector length code ; drive d ; ibm standard 8 inch db 00 ; old cylinder db inch8+sden+drv3 ; board setup byte db sside+rate2 ; sidedness and step speed db sec1+l128 ; sector length code page ;........................................................................... ; disk parameter header map ; ; disk a dphm: dw tdska ; xlt ; addr of skew-translation vector dw 0000h,0000h,0000h ; bdos work area dw dirbf ; dirbuf ; addr of directory i/o buffer dw ddska ; dpb ; addr of disk paramter block dw chk.a ; csv ; addr of directory ckeck area dw all.a ; alv ; addr of allocation vector area ; disk b dw tdskb ; xlt ; addr of skew-translation vector dw 0000h,0000h,0000h ; bdos work area dw dirbf ; dirbuf ; addr of directory i/o buffer dw ddskb ; dpb ; addr of disk paramter block dw chk.b ; csv ; addr of directory ckeck area dw all.b ; alv ; addr of allocation vector area ; disk c dw tdskc ; xlt ; addr of skew-translation vector dw 0000h,0000h,0000h ; bdos work area dw dirbf ; dirbuf ; addr of directory i/o buffer dw ddskc ; dpb ; addr of disk paramter block dw chk.c ; csv ; addr of directory ckeck area dw all.c ; alv ; addr of allocation vector area ; disk d dw tdskd ; xlt ; addr of skew-translation vector dw 0000h,0000h,0000h ; bdos work area dw dirbf ; dirbuf ; addr of directory i/o buffer dw ddskd ; dpb ; addr of disk paramter block dw chk.d ; csv ; addr of directory ckeck area dw all.d ; alv ; addr of allocation vector area page ;........................................................................... ; sector translate vector ; translate table for MAGNUM standard 8 inch diskettes tdska: db 01,02,03,04,05,06,07,08 ; 1 ; 1, 2, 3, 4, 5, 6, 7, 8 db 17,18,19,20,21,22,23,24 ; 3 ; 9,10,11,12,13,14,15,16 db 33,34,35,36,37,38,39,40 ; 5 ; 17,18,19,20,21,22,23,24 db 49,50,51,52,53,54,55,56 ; 7 ; 25,26,27,28,29,30,31,32 db 65,66,67,68,69,70,71,72 ; 9 ; 33,34,35,36,37,38,39,40 db 09,10,11,12,13,14,15,16 ; 2 ; 41,42,43,44,45,46,47,48 db 25,26,27,28,29,30,31,32 ; 4 ; 49,50,51,52,53,54,55,56 db 41,42,43,44,45,46,47,48 ; 6 ; 57,58,59,60,61,62,63,64 db 57,58,59,60,61,62,63,64 ; 8 ; 65,66,67,68,69,70,71,72 ; translate table for MAGNUM standard 8 inch diskettes tdskb: db 01,02,03,04,05,06,07,08 ; 1 ; 1, 2, 3, 4, 5, 6, 7, 8 db 17,18,19,20,21,22,23,24 ; 3 ; 9,10,11,12,13,14,15,16 db 33,34,35,36,37,38,39,40 ; 5 ; 17,18,19,20,21,22,23,24 db 49,50,51,52,53,54,55,56 ; 7 ; 25,26,27,28,29,30,31,32 db 65,66,67,68,69,70,71,72 ; 9 ; 33,34,35,36,37,38,39,40 db 09,10,11,12,13,14,15,16 ; 2 ; 41,42,43,44,45,46,47,48 db 25,26,27,28,29,30,31,32 ; 4 ; 49,50,51,52,53,54,55,56 db 41,42,43,44,45,46,47,48 ; 6 ; 57,58,59,60,61,62,63,64 db 57,58,59,60,61,62,63,64 ; 8 ; 65,66,67,68,69,70,71,72 ; translate table for MAGNUM standard 8 inch diskettes tdskc: db 01,02,03,04,05,06,07,08 ; 1 ; 1, 2, 3, 4, 5, 6, 7, 8 db 17,18,19,20,21,22,23,24 ; 3 ; 9,10,11,12,13,14,15,16 db 33,34,35,36,37,38,39,40 ; 5 ; 17,18,19,20,21,22,23,24 db 49,50,51,52,53,54,55,56 ; 7 ; 25,26,27,28,29,30,31,32 db 65,66,67,68,69,70,71,72 ; 9 ; 33,34,35,36,37,38,39,40 db 09,10,11,12,13,14,15,16 ; 2 ; 41,42,43,44,45,46,47,48 db 25,26,27,28,29,30,31,32 ; 4 ; 49,50,51,52,53,54,55,56 db 41,42,43,44,45,46,47,48 ; 6 ; 57,58,59,60,61,62,63,64 db 57,58,59,60,61,62,63,64 ; 8 ; 65,66,67,68,69,70,71,72 ; ibm single density 3740 standard tdskd: db 1,7,13,19 ; sectors 1,2,3,4 db 25,5,11,17 ; sectors 5,6,7,8 db 23,3,9,15 ; sectors 9,10,11,12 db 21,2,8,14 ; sectors 13,14,15,16 db 20,26,6,12 ; sectors 17,18,19,20 db 18,24,4,10 ; sectors 21,22,23,24 db 16,22 ; sectors 25,26 ds 46 ; room for total of 72 sectors page ;........................................................................... ; disk paramter block tables ; disk description for MAGNUM double sided 8 inch diskettes ; note: 2k blocks, total disk data capacity = 152tr*9k/tr = 1368K = 684 blocks ; 64 directory entry/block ( 16k/dir ) == 86 min. entry for 1 large file ; 64 entry/bl * 5bl = 320 entry = 640K max ( assuming 1 block files ) ddska: dw 72 ; spt - sectors per track db 4 ; bsh - block shift factor db 15 ; blm - block mask db 0 ; exm - extent mask dw 683 ; dsm - drive capacity dw 319 ; drm - directory size db 0f8h,0 ; al0/al1 - initial allocation vector dw 80 ; cks - check area size (319+1)/4 dw 2 ; off - reserved tracks ; disk description for MAGNUM double sided 8 inch diskettes ; note: 2k blocks, total disk data capacity = 152tr*9k/tr = 1368K = 684 blocks ; 64 directory entry/block ( 16k/dir ) == 86 min. entry for 1 large file ; 64 entry/bl * 5bl = 320 entry = 640K max ( assuming 1 block files ) ddskb: dw 72 ; spt - sectors per track db 4 ; bsh - block shift factor db 15 ; blm - block mask db 0 ; exm - extent mask dw 683 ; dsm - drive capacity dw 319 ; drm - directory size db 0f8h,0 ; al0/al1 - initial allocation vector dw 80 ; cks - check area size (319+1)/4 dw 2 ; off - reserved tracks ; disk description for MAGNUM double sided 8 inch diskettes ; note: 2k blocks, total disk data capacity = 152tr*9k/tr = 1368K = 684 blocks ; 64 directory entry/block ( 16k/dir ) == 86 min. entry for 1 large file ; 64 entry/bl * 5bl = 320 entry = 640K max ( assuming 1 block files ) ddskc: dw 72 ; spt - sectors per track db 4 ; bsh - block shift factor db 15 ; blm - block mask db 0 ; exm - extent mask dw 683 ; dsm - drive capacity dw 319 ; drm - directory size db 0f8h,0 ; al0/al1 - initial allocation vector dw 80 ; cks - check area size (319+1)/4 dw 2 ; off - reserved tracks ; disk description for ibm single density 3740 standard ; note: 1k blocks, total disk data capacity 243K = 243 blocks ; 32 directory entry/block ( 16k/dir ) == 15 min. entry for 1 large file ; 32 entry/bl * 2bl = 64 entry = 64K max ( assuming 1 block files ) ddskd: dw 26 ; spt - sectors per track db 3 ; bsh - block shift factor db 7 ; blm - block mask db 0 ; exm - extent mask dw 242 ; dsm - drive capacity dw 63 ; drm - directory size db 0C0h,0 ; al0/al1 - initial allocation vector dw 16 ; cks - check area size (63+1)/4 dw 2 ; off - reserved tracks ; end of fixed tables end equ $-start ; length of pure data page ;=========================================================================== ; impure area ; the remainder of this bios is reserved uninitialized ; data area, and does not need to be a part of the ; system memory image stored on track 1 sectors 1-4. ; When planning CP/M size, allow room enough for 'totsiz'. begdat equ $ ; beginning of impure data area ; various and sundry variables bootdsk:db 0 ; the assigned boot disk temp: dw 0 ; extra tempst: db 0 ; room for temporary storage of status rwerr: db 0 ; error counter dmaad: dw 0 ; dma address as per SETDMA ; i/o descriptor variables for: drive, head placement, sector and deblocking diskno: dw 0 ; current disk as per SETDSK track: dw 0 ; useful for sectors labeled with track not cylinder cylind: dw 0 ; actual physical cylinder as per SETTRK side: dw 0 ; side as per SETTRK dsect: dw 0 ; destination physical sector number sector: dw 0 ; logical record (128 byte sector) number as per SETSEC offset: dw 0 ; offset into physical record buffer ; buffered sector identification variables bdisk: dw 0 ; disk of current buffered sector bcylind:dw 0 ; cylinder of current buffered sector bside: dw 0 ; side of current buffered sector bsect: dw 0 ; current physical sector in buffer ; one kilobyte sector buffer area dirty: dw 0 ; dirty byte (extra byte for safety) buffer: ds 1024 ; buffer for largest possible physical sector dirbf: ds 128 ; scratch directory area ; allocation vector: size is dsm/8+1 all.a: ds 100 ; allocation vector - big enough for 793 blocks all.b: ds 100 ; allocation vector all.c: ds 100 ; allocation vector all.d: ds 100 ; allocation vector ; directory check information: size is cks (of dpbm) or (drm+1)/4 chk.a: ds 100 ; check vector - big enough for 400 directory entries chk.b: ds 100 ; check vector chk.c: ds 100 ; check vector chk.d: ds 100 ; check vector ;=========================================================================== datsiz equ $-begdat ; size of data area totsiz equ $-start ; total size of bios - pure and impure .dephase end