; BIOS for Z80 Single Board System ; ; Implements 512 to 128 byte blocking/deblocking ; ; ; msize equ 62 ;cp/m version memory size in kilobytes ; ; "bias" is address offset from 3400H for memory systems ; than 16K (referred to as "b" throughout the text). ; bias equ (msize-20)*1024 ccp equ 3400H+bias ;base of ccp bdos equ ccp+806h ;base of bdos bios equ ccp+1600h ;base of bios cdisk equ 0004H ;current disk number 0=A,...,15=P iobyte equ 0003h ;intel i/o byte ; Total number of drives tdrives equ 3 ; Total number of floppy drives fdrives equ 2 ; Blocking equates blksiz equ 2048 ; CP/M allocation size hstsiz equ 512 ; Host disk sector size hstspt equ 9 ; Host disk sectors/trk hstblk equ hstsiz/128 ; CP/M sect/host buff cpmspt equ hstblk*hstspt ; CP/M sectors/track secmsk equ hstblk-1 ; Sector mask secshf equ 2 ; number of bits needed to get (512/128)=4 ; BDOS Constants on entry to write wrall equ 0 ; write to allocated wrdir equ 1 ; write to directory wrual equ 2 ; write to unallocated ; I/O Equates tty0d equ 0 ; Serial Port tty0c equ 1 fdcsta equ 040h ; WD1797 FDC fdccmd equ 040h fdctrk equ 041h fdcsec equ 042h fdcdat equ 043h fdcsel equ 0F0h ; Select drive idedath equ 0C0h ; Data High (latch) idedatl equ 060h ; Data Low ideerr equ 061h ; Error idesc equ 062h ; Sector Count idesect equ 063h ; Sector start idecl equ 064h ; Cylinder low idech equ 065h ; Cylinder High idehead equ 066h ; Head idecmd equ 067h ; Command idestat equ 067h ; Status ; WD1797 Commands fhome equ 00Ch ; Restore drive, verify fhomen equ 008h ; Don't verify fseek equ 01Ch ; Seek to track, verify fseekn equ 018h ; Seek, no verify fread equ 088h ; Read sector fwrit equ 0A8h ; Write sector fint equ 0D0h ; Force Interrupt ; IDE Commands iwrite equ 030h iread equ 020h iheadm equ 0A0h ; Head regsiter mask ; WD1797 Status bits fbusy equ 1 fdrq equ 2 ; IDE Status Bits ierror equ 001h idrq equ 008h irdy equ 040h ibsy equ 080h ; org bios ;origin of this program nsects equ ($-ccp)/128 ;warm start sector count ; ; jump vector for individual subroutines jmp boot ;cold start wboote: jmp wboot ;warm start jmp const ;console status jmp conin ;console character in jmp conout ;console character out jmp list ;list character out jmp punch ;punch character out jmp reader ;reader character out jmp home ;move head to home position jmp seldsk ;select disk jmp settrk ;set track number jmp setsec ;set sector number jmp setdma ;set dma address jmp read ;read disk jmp write ;write disk jmp listst ;return list status jmp sectran ;sector translate ; ; fixed data tables for four-drive standard ; IBM-compatible 8" disks ; disk parameter header for disk 00 dpbase: dw 0000H,0000H dw 0000H,0000H dw dirbf,dpblk dw csv00,alv00 dw 0000H,0000H dw 0000H,0000H dw dirbf,dpblk dw csv01,alv01 dw 0000H,0000H dw 0000H,0000H dw dirbf,hdblk dw 0000H,alvhd dsm equ 355 ; size of disk drm equ 512 ; total directory entries allsiz equ (dsm/8)+1 chksiz equ drm/4 dpblk: ;disk parameter block, common to all disks dw cpmspt ;sectors per track db 4 ;block shift factor db 15 ;block mask db 0 ;null mask dw dsm ;disk size-1 dw drm-1 ;directory max db 0FFh ;alloc 0 db 000h ;alloc 1 dw chksiz ;check size dw 2 ;track offset ; DPB for Fixed Disk hdblk: dw 10*17 ;sectors per track (680) db 6 ;block shift factor db 63 ;block mask db 3 ;extent mask ; dw 5205 ;disk size-1 dw 1000 dw 2047 ;directory max db 0FFh ;alloc 0 db 000h ;alloc 1 dw 0 ;check size dw 1 ;track offset hdcyl: dw 980 ; number of cylinders hdhead: dw 10 ; number of heads hdsect: dw 17 ; number of sectors per cylinder ; ; end of fixed tables ; ; individual subroutines to perform each function boot: ;simplest case is to just perform parameter initialization xra a ;zero in the accum sta iobyte ;clear the iobyte sta cdisk ;select disk zero ; print boot message mvi c, 13 call conout mvi c, 10 call conout lxi h,signon print: mov a, m ora a jz gocpm ; Initialize and got to cp/m inx h mov c, a call conout jmp print signon: db "62k CP/M vers 2.2",13,10,0 ; wboot: ;simplest case is to read the disk until all sectors loaded lxi sp,80h ;use space below buffer for stack mvi c,0 ;select disk 0 call seldsk call home ;go to track 00 ; mvi b,nsects ;b counts # of sectors to load mvi c,0 ;c has the current track number mvi d,0 ;d has the next sector to read lxi h,ccp ;base of cp/m (initial load point) load1: ;load one more sector push b ;save sector count, current track push d ;save next sector to read push h ;save dma address mov c,d ;get sector address to register c call setsec ;set sector address from register c pop b ;recall dma address to b,c push b ;replace on stack for later recall call setdma ;set dma address from b,c ; ; drive set to 0, track set, sector set, dma address set call read cpi 00h ;any errors? jnz wboot ;retry the entire boot if an error occurs ; ; no error, move to next sector pop h ;recall dma address lxi d,128 ;dma=dma+128 dad d ;new dma address is in h,l pop d ;recall sector address pop b ;recall number of sectors remaining, and current trk dcr b ;sectors=sectors-1 jz gocpm ;transfer to cp/m if all have been loaded ; ; more sectors remain to load, check for track change inr d mov a,d ;sector=36?, if so, change tracks cpi cpmspt jc load1 ;carry generated if sector<36 ; ; end of current track, go to next track mvi d,0 ;begin with first sector of next track inr c ;track=track+1 ; ; save register state, and change tracks push b push d push h call settrk ;track address set from register c pop h pop d pop b jmp load1 ;for another sector ; ; end of load operation, set parameters and go to cp/m gocpm: mvi c, 13 call conout mvi c, 10 call conout xra a sta hstact ;host buffer inactive sta unacnt ;clear unalloc count mvi a,0c3h ;c3 is a jmp instruction sta 0 ;for jmp to wboot lxi h,wboote ;wboot entry point shld 1 ;set address field for jmp at 0 ; sta 5 ;for jmp to bdos lxi h,bdos ;bdos entry point shld 6 ;address field of jump at 5 to bdos ; lxi b,80h ;default dma address is 80h call setdma ; ;ei ;enable the interrupt system xra a ;select drive 0 mov c,a ;send to the ccp jmp ccp ;go to cp/m for further processing ; ; ; simple i/o handlers (must be filled in by user) ; in each case, the entry point is provided, with space reserved ; to insert your own code ; const: ;console status, return 0ffh if character ready, 00h if not in tty0c ani 2 ; Receive ready flag rz mvi a, 0FFh ret ; conin: ;console character into register a in tty0c ani 2 jz conin in tty0d ani 7fh ;strip parity bit ret ; conout: ;console character output from register c in tty0c ; wait for character to finish ani 1 jz conout mov a,c ;get to accumulator out tty0d cout1: in tty0c ;wait for console to acknowledge ani 1 jz cout1 ret ; list: ;list character from register c mov a,c ;character to register a ret ;null subroutine ; listst: ;return list status (0 if not ready, 1 if ready) xra a ;0 is always ok to return ret ; punch: ;punch character from register c mov a,c ;character to register a ret ;null subroutine ; ; reader: ;read character into register a from reader device mvi a,1ah ;enter end of file for now (replace later) ani 7fh ;remember to strip parity bit ret ; ; ; i/o drivers for the disk follow ; for now, we will simply store the parameters away for use ; in the read and write subroutines ; home: ;move to the track 00 position of current drive ; translate this call into a settrk call with parameter 00 lda hstwrt ; ckec for pending write ora a jnz homed sta hstact homed: xra a sta sektrk ret ; seldsk: ;select disk given by register C lxi h,0000h ;error return code mov a, c cpi tdrives ; total number of drives rnc ; return with error if greater than sta sekdsk ; store disk number lxi d,dpbase mov l,a ; DE = 0A mvi h,0 dad h ; drive * 16 = offset dad h dad h dad h dad d ; add dpbase ret ; settrk: ;set track given by register bc mov h,b mov l,c shld sektrk ret ; setsec: ;set sector given by register c mov a,c sta seksec ret ; sectran: ;translate the sector given by BC using the ;translate table given by DE mov h,b mov l,c ret ;with value in HL ; setdma: ;set dma address given by registers b and c mov l,c ;low order address mov h,b ;high order address shld dmaadr ;save the address ret ; ;***************************************************** ;* * ;* The READ entry point takes the place of * ;* the previous BIOS defintion for READ. * ;* * ;***************************************************** read: ;read the selected CP/M sector xra a sta unacnt mvi a,1 sta readop ;read operation sta rsflag ;must read data mvi a,wrual sta wrtype ;treat as unalloc jmp rwoper ;to perform the read ; ;***************************************************** ;* * ;* The WRITE entry point takes the place of * ;* the previous BIOS defintion for WRITE. * ;* * ;***************************************************** write: ;write the selected CP/M sector xra a ;0 to accumulator sta readop ;not a read operation mov a,c ;write type in c sta wrtype cpi wrual ;write unallocated? jnz chkuna ;check for unalloc ; ; write to unallocated, set parameters mvi a,blksiz/128 ;next unalloc recs sta unacnt lda sekdsk ;disk to seek sta unadsk ;unadsk = sekdsk lhld sektrk shld unatrk ;unatrk = sectrk lda seksec sta unasec ;unasec = seksec ; chkuna: ;check for write to unallocated sector lda unacnt ;any unalloc remain? ora a jz alloc ;skip if not ; ; more unallocated records remain dcr a ;unacnt = unacnt-1 sta unacnt lda sekdsk ;same disk? lxi h,unadsk cmp m ;sekdsk = unadsk? jnz alloc ;skip if not ; ; disks are the same lxi h,unatrk call sektrkcmp ;sektrk = unatrk? jnz alloc ;skip if not ; ; tracks are the same lda seksec ;same sector? lxi h,unasec cmp m ;seksec = unasec? jnz alloc ;skip if not ; ; match, move to next sector for future ref inr m ;unasec = unasec+1 mov a,m ;end of track? cpi cpmspt ;count CP/M sectors jc noovf ;skip if no overflow ; ; overflow to next track mvi m,0 ;unasec = 0 lhld unatrk inx h shld unatrk ;unatrk = unatrk+1 ; noovf: ;match found, mark as unnecessary read xra a ;0 to accumulator sta rsflag ;rsflag = 0 jmp rwoper ;to perform the write ; alloc: ;not an unallocated record, requires pre-read xra a ;0 to accum sta unacnt ;unacnt = 0 inr a ;1 to accum sta rsflag ;rsflag = 1 ; ;***************************************************** ;* * ;* Common code for READ and WRITE follows * ;* * ;***************************************************** rwoper: ;enter here to perform the read/write xra a ;zero to accum sta erflag ;no errors (yet) mvi a,3 ;three retries sta rcount ;retry count lda seksec ;compute host sector ;rept secshf ora a ;carry = 0 rar ;shift right ora a ;carry = 0 rar ;shift right ;endm sta sekhst ;host sector to seek ; ; active host sector? lxi h,hstact ;host active flag mov a,m mvi m,1 ;always becomes 1 ora a ;was it already? jz filhst ;fill host if not ; ; host buffer active, same as seek buffer? lda sekdsk lxi h,hstdsk ;same disk? cmp m ;sekdsk = hstdsk? jnz nomatch ; ; same disk, same track? lxi h,hsttrk call sektrkcmp ;sektrk = hsttrk? jnz nomatch ; ; same disk, same track, same buffer? lda sekhst lxi h,hstsec ;sekhst = hstsec? cmp m jz match ;skip if match ; nomatch: ;proper disk, but not correct sector lda hstwrt ;host written? ora a cnz writehst ;clear host buff ; filhst: ;may have to fill the host buffer lda sekdsk sta hstdsk lhld sektrk shld hsttrk mov a, l lda sekhst sta hstsec lda rsflag ;need to read? ora a cnz readhst ;yes, if 1 xra a ;0 to accum sta hstwrt ;no pending write ; match: ;copy data to or from buffer lda seksec ;mask buffer number ani secmsk ;least signif bits mov l,a ;ready to shift mvi h,0 ;double count ;rept 7 ;shift left 7 dad h dad h dad h dad h dad h dad h dad h ;endm ; hl has relative host buffer address lxi d,hstbuf dad d ;hl = host address mov a, h mov a, l xchg ;now in DE lhld dmaadr ;get/put CP/M data mvi c,128 ;length of move lda readop ;which way? ora a jnz rwmove ;skip if read ; ; write operation, mark and switch direction mvi a,1 sta hstwrt ;hstwrt = 1 xchg ;source/dest swap ; rwmove: ;C initially 128, DE is source, HL is dest ldax d ;source character inx d mov m,a ;to dest inx h dcr c ;loop 128 times jnz rwmove ; ; data has been moved to/from host buffer lda wrtype ;write type cpi wrdir ;to directory? lda erflag ;in case of errors rnz ;no further processing ; ; clear host buffer for directory write ora a ;errors? rnz ;skip if so xra a ;0 to accum sta hstwrt ;buffer written call writehst lda erflag ret ; ;***************************************************** ;* * ;* Utility subroutine for 16-bit compare * ;* * ;***************************************************** sektrkcmp: ;HL = .unatrk or .hsttrk, compare with sektrk xchg lxi h,sektrk ldax d ;low byte compare cmp m ;same? rnz ;return if not ; low bytes equal, test high 1s inx d inx h ldax d cmp m ;sets flags ret ; Perform actual write to 512 byte sector ; ; hstdsk = disk, hsttrk = track ; hstsec = sector ; return erflag non-zero if error writehst: push b push d push h lda hstdsk cpi fdrives jc writefd ; if selected disk <= floppy drive ; Perform IDE write opreation call hdinit mvi c,030h call iwcmd lxi h,hstbuf mvi c,0 ; count 256 words idewr1: in idestat ; wait for DRQ ani idrq jz idewr1 idewr2: mov b,m inx h mov a,m inx h out idedath mov a,b out idedatl dcr c jnz idewr2 pop h pop d pop b xra a ret readhst: push b push d push h lda hstdsk cpi fdrives jc readfd ; if selected disk a floppy ; Perform IDE read operation call hdinit mvi c, 020h call iwcmd iderd1: in idestat ; wait for DRQ ani idrq jz iderd1 lxi h,hstbuf mvi c,0 ; count 256 words iderd2: in idedatl mov m,a inx h in idedath mov m,a inx h dcr c jnz iderd2 pop h pop d pop b xra a ret writefd: lda rcount ; get rcount ori 010h ; set write flag sta rcount ; so we know where to return to on an error call fdseek ; seek to correct track ; and initialise controller registers lda hstsec ; get sector inr a ; from 0-8 to 1-9 out fdcsec ; store it lxi h, hstbuf ; write from hstbuf mvi b, fbusy|fdrq ; flags used in loop mvi d, fdrq lda hstsid ; get side add a ; 0 = 0, 1 = 2 ori fwrit ; write sector and side out fdccmd writack in fdcsta ; wait for acknowledge ani 1 ; when BUSY goes true jz writack wloop in fdcsta ; get status mov c,a ; keep error condition ana b ; mask busy and drq jz iodone ; jump if neither busy or data waiting ana d ; mask drq jz wloop ; jump if nothing waiting mov a, m out fdcdat ; put data inx h jmp wloop readfd: call fdseek ; set controller parameters lda hstsec ; get sector inr a ; from 0-8 to 1-9 out fdcsec ; store it lda hstsid add a ori fread ; read sector lxi h, hstbuf mvi b, fbusy|fdrq ; flags used in loop mvi d, fdrq out fdccmd readack in fdcsta ; wait for acknowledge ani 1 ; when BUSY goes true jz readack rloop in fdcsta ; get status mov c, a ; keep error condition ana b ; mask busy and drq jz iodone ; jump if neither busy or data waiting ana d ; mask drq jz rloop ; jump if nothing waiting in fdcdat ; get data mov m, a inx h jmp rloop iodone: mov a,c pop h pop d pop b ani 01Ch ; error flags sta erflag ; report errors rz ; return if no errors occured ; An error has occured with the read/write operation. ; Restore the drive, seek to the needed sector and retry, ; keeping track of retries in (rcount), which is set to 3 ; at rwoper and 013h on a write operation. lda rcount dcr a sta rcount ani 00Fh ; mask of R/W flag rz ; if zero, we have retried 3 times, return ; Perform disk restore mvi c, fint ; reset drive out fdccmd ; immediately mvi c,fhomen ; Drive to Track 0 blindly call wcmd mvi a,79 ; seek to innermost track out fdcdat mvi c,fseekn ; seek blindly call wcmd mvi c,fhomen ; Drive to Track 0 blindly call wcmd ; Set the locations used by fdseek to reflect new drive condition lxi h,trktab lda hstdsk mov e,a ; current drive to DE mvi d,0 dad d ; add current drive to fdc track table mvi m,0 ; update condition ; Now we figure out where to jump to, based on rcount. If it's ; >= 0x10 then we jump to writehst to retry. lda rcount ani 0F0h jnz writehst jp readhst ; otherwise it's a read operation ; Initialises controller for either a read or write operation fdseek: lda hstdsk ; get disk out fdcsel ; select correct drive mov e,a ; drive to DE to compare later mvi d,0 lxi h,trktab dad d ; pointer becomes track table + drive lda hsttrk ; get track (max 160, ignore upper byte) ora a ; clear c rar ; divide by two, side to C mov b, a ; will be compared to mvi a, 0 ; clear a adc a ; add carry flag, A becomes side sta hstsid ; store side ; next compare the desired track to the track ; the selected drive is currently on mov a,m ; get selected drive's track out fdctrk ; send to floppy controller cmp b ; same track? rz ; return if same mov a,b ; else seek to new track mov m,a ; store new track for future out fdcdat ; send desired track to FDC mvi c, fseek ; seek command ; Wait for any command on the FDC to finish, then send command in ; register C, wait for it to acknowledge and end. wcmd in fdcsta ; wait for busy to be false ani fbusy jnz wcmd mov a, c ; send command out fdccmd wcmd0 in fdcsta ; wait for command acknowledge ani fbusy jz wcmd0 wcmd1 in fdcsta ; wait for command to end ani fbusy jnz wcmd1 ret ; Write command to IDE drive iwcmd: in idestat ani ibsy jnz iwcmd mov a,c out idecmd ;mvi c,13 ;call conout ;mvi c,10 ;call conout ret hdinit: in idestat ; don't do anything until drive is ready ani ibsy|irdy xri irdy jnz hdinit lhld hdsect ; total hard disk sectors per cylinder xchg ; spt->DE mvi c, 0 ; C becomes head number lda hstsec mov l,a ; = hstsec mov h,c ; = 0 calcl: inr c db 0EDh, 052h ; Z80 SBC HL, DE jnc calcl dad d ; went too far, add one inx h ; sectors start at 1 dcr c ; went too far mov a,l ; get sector out idesect ;push b ;call phex ;pop b mov a,c ; head ori iheadm ; head mask out idehead ;call phex lhld hsttrk ; host cylinder mov a,l out idecl ; cylinder low ;call phex mov a,h out idech ; cylinder high ;call phex mvi a,1 out idesc ; sector count ;lda hstsec ;call phex ret ; Print A as an ASCII hex number ;phex: push psw ; ani 0F0h ; rrc ; rrc ; rrc ; rrc ; call phex1 ; pop psw ;phex1: ani 00Fh ; cpi 00Ah ; jc phex2 ; sui 009h ; ori 040h ; jmp phex3 ;phex2: ori 030h ;phex3: mov c, a ; call conout ; ret ; the remainder of the CBIOS is reserved uninitialized ; data area, and does not need to be a part of the ; system memory image (the space must be available, ; however, between "begdat" and "enddat"). ; sekdsk: ds 1 ; seek disk number sektrk: ds 2 ; seek track seksec: ds 1 ; seek sector hstdsk: ds 1 ; host disk hsttrk: ds 2 ; host track hstsid: ds 1 ; host side hstsec: ds 1 ; host sector rcount: ds 1 ; read/write retry count trktab: ds tdrives ; FDC track table sekhst: ds 1 ; seek shr secshf hstact: ds 1 ; host active flag hstwrt: ds 1 ; host written flag unacnt: ds 1 ; unalloc rec cnt unadsk: ds 1 ; last unalloc disk unatrk: ds 2 ; last unalloc track unasec: ds 1 ; last unalloc sector erflag: ds 1 ;error reporting rsflag: ds 1 ;read sector flag readop: ds 1 ;1 if read operation wrtype: ds 1 ;write operation type dmaadr: ds 2 ;last dma address dmaad: ds 2 ;direct memory address diskno: ds 1 ;disk number 0-15 ; ; scratch ram area for BDOS use begdat equ $ ;beginning of data area alv00: ds allsiz ;allocation vector csv00: ds chksiz ;check vector alv01: ds allsiz ;allocation vector csv01: ds chksiz ;check vector dirbf: ds 128 ; enddat equ $ ;end of data area datsiz equ $-begdat;size of data area hstbuf: ds hstsiz alvhd: ds 501 ; HDD Check Vector