# File z80_cbios3.asm 0000 ; CP/M CBIOS for CPUville 8-bit computers 0000 ; version 3, June 2019 by Donn Stewart 0000 ; Modified from skeletal cbios for first level of CP/M 2.0 alteration 0000 ; 64-sector tracks, numbering starting at zero (sectors 0 to 63) 0000 ; Block size 2048 0000 ; Disk size 2 mb, DSM 1023, yields 256 tracks (0 to 255) 0000 ; 4 disks 0000 ; Made more efficient use of disk by OR-ing CP/M disk, track, sector values 0000 ; to create logical block address (LBA) for IDE disk 0000 ; Added greeting message to cold boot 0000 ; 0000 ccp: equ 0E400h ;base of ccp 0000 bdos: equ 0EC06h ;bdos entry 0000 bios: equ 0FA00h ;base of bios 0000 cdisk: equ 0004h ;address of current disk number 0=a,... l5=p 0000 iobyte: equ 0003h ;intel i/o byte 0000 disks: equ 04h ;number of disks in the system 0000 ; 0000 org bios ;origin of this program fa00 nsects: equ ($-ccp)/128 ;warm start sector count fa00 ; fa00 ; jump vector for individual subroutines fa00 ; fa00 c3 a8 fa JP boot ;cold start fa03 c3 b8 fa wboote: JP wboot ;warm start fa06 c3 2a fb JP const ;console status fa09 c3 37 fb JP conin ;console character in fa0c c3 43 fb JP conout ;console character out fa0f c3 4e fb JP list ;list character out fa12 c3 52 fb JP punch ;punch character out fa15 c3 54 fb JP reader ;reader character out fa18 c3 59 fb JP home ;move head to home position fa1b c3 5f fb JP seldsk ;select disk fa1e c3 78 fb JP settrk ;set track number fa21 c3 7d fb JP setsec ;set sector number fa24 c3 89 fb JP setdma ;set dma address fa27 c3 9b fb JP read ;read disk fa2a c3 fd fb JP write ;write disk fa2d c3 50 fb JP listst ;return list status fa30 c3 82 fb JP sectran ;sector translate fa33 ; fa33 ; Data tables for disks fa33 ; Four disks, 26 sectors/track, disk size = number of 1024 byte blocks fa33 ; Number of directory entries (32-bytes each) set to 127 per 500 blocks fa33 ; Allocation map bits = number of blocks needed to contain directory entries fa33 ; No translations -- translation maps commented out fa33 ; fa33 ; disk Parameter header for disk 00 fa33 00 00 00 00 dpbase: defw 0000h, 0000h fa37 00 00 00 00 defw 0000h, 0000h fa3b 64 fc 73 fa defw dirbf, dpblk fa3f e4 fe e4 fc defw chk00, all00 fa43 ; disk parameter header for disk 01 fa43 00 00 00 00 defw 0000h, 0000h fa47 00 00 00 00 defw 0000h, 0000h fa4b 64 fc 73 fa defw dirbf, dpblk fa4f e5 fe 64 fd defw chk01, all01 fa53 ; disk parameter header for disk 02 fa53 00 00 00 00 defw 0000h, 0000h fa57 00 00 00 00 defw 0000h, 0000h fa5b 64 fc 73 fa defw dirbf, dpblk fa5f e6 fe e4 fd defw chk02, all02 fa63 ; disk parameter header for disk 03 fa63 00 00 00 00 defw 0000h, 0000h fa67 00 00 00 00 defw 0000h, 0000h fa6b 64 fc 73 fa defw dirbf, dpblk fa6f e7 fe 64 fe defw chk03, all03 fa73 ; fa73 ; sector translate vector fa73 ;Since no translation will comment out fa73 ;trans: defm 1, 7, 13, 19 ;sectors 1, 2, 3, 4 fa73 ; defm 25, 5, 11, 17 ;sectors 5, 6, 7, 6 fa73 ; defm 23, 3, 9, 15 ;sectors 9, 10, 11, 12 fa73 ; defm 21, 2, 8, 14 ;sectors 13, 14, 15, 16 fa73 ; defm 20, 26, 6, 12 ;sectors 17, 18, 19, 20 fa73 ; defm 18, 24, 4, 10 ;sectors 21, 22, 23, 24 fa73 ; defm 16, 22 ;sectors 25, 26 fa73 ; fa73 dpblk: ;disk parameter block for all disks. fa73 40 00 defw 64 ;sectors per track fa75 04 defm 4 ;block shift factor fa76 0f defm 15 ;block mask - with block shift, sets block size to 1024 fa77 00 defm 0 ;null mask fa78 ff 03 defw 1023 ;disk size-1 = number of blocks in a disk - 1 fa7a 00 01 defw 256 ;directory max = no. directory entries/disk, arbitrary fa7c f0 defm 240 ;alloc 0 -- need 4 bits (blocks) for 256 directory entries -- fa7d 00 defm 0 ;alloc 1 -- no. bits = (directory max x 32)/block size fa7e 00 00 defw 0 ;check size -- no checking, so zero fa80 01 00 defw 1 ;track offset -- first track for system fa82 ; fa82 ; end of fixed tables fa82 ; fa82 ; Greeting message for cold boot fa82 0d 0a greet: defm 0dh, 0ah fa84 .. defm "CP/M 2.2 for CPUville 64K systems" faa5 0d 0a 00 defm 0dh, 0ah, 0 faa8 faa8 ; individual subroutines to perform each function faa8 boot: ;entry point from CP/M loader faa8 ;CP/M loader has loaded BDOS, CCP and CBIOS faa8 ;print greeting and perform parameter initialization faa8 21 82 fa ld hl,greet ;address of greeting message faab cd 8f fb call prmsg ;print message subroutine faae af XOR a ;zero in the accum faaf 32 03 00 LD (iobyte),A ;clear the iobyte fab2 32 04 00 LD (cdisk),A ;select disk zero fab5 c3 01 fb JP gocpm ;initialize and go to cp/m fab8 ; fab8 wboot: ;load BDOS and CCP only -- CBIOS should still be there... fab8 31 80 00 LD sp, 80h ;use space below buffer for stack fabb 0e 00 LD c, 0 ;select disk 0 fabd cd 5f fb call seldsk fac0 cd 59 fb call home ;go to track 00 fac3 ; fac3 06 2c LD b, nsects ;b counts * of sectors to load fac5 0e 00 LD c, 0 ;c has the current track number fac7 16 01 LD d, 1 ;d has the next sector to read fac9 ; note that we begin by reading track 0, sector 1 since sector 0 fac9 ; contains the cold start loader, which is skipped in a warm start fac9 21 00 e4 LD HL, ccp ;base of cp/m (initial load point) facc load1: ;load one more sector facc c5 PUSH BC ;save sector count, current track facd d5 PUSH DE ;save next sector to read face e5 PUSH HL ;save dma address facf 4a LD c, d ;get sector address to register C fad0 cd 7d fb call setsec ;set sector address from register C fad3 c1 pop BC ;recall dma address to b, C fad4 c5 PUSH BC ;replace on stack for later recall fad5 cd 89 fb call setdma ;set dma address from b, C fad8 ; fad8 ; drive set to 0, track set, sector set, dma address set fad8 cd 9b fb call read fadb fe 00 CP 00h ;any errors? fadd c2 b8 fa JP NZ,wboot ;retry the entire boot if an error occurs fae0 ; fae0 ; no error, move to next sector fae0 e1 pop HL ;recall dma address fae1 11 80 00 LD DE, 128 ;dma=dma+128 fae4 19 ADD HL,DE ;new dma address is in h, l fae5 d1 pop DE ;recall sector address fae6 c1 pop BC ;recall number of sectors remaining, and current trk fae7 05 DEC b ;sectors=sectors-1 fae8 ca 01 fb JP Z,gocpm ;transfer to cp/m if all have been loaded faeb ; faeb ; more sectors remain to load, check for track change faeb 14 INC d faec 7a LD a,d ;sector=64?, if so, change tracks faed fe 40 CP 64 faef da cc fa JP C,load1 ;carry generated if sector<64 faf2 ; faf2 ; end of current track, go to next track faf2 16 00 LD d,0 ;begin with first sector of next track faf4 0c INC c ;track=track+1 faf5 ; faf5 ; save register state, and change tracks faf5 c5 PUSH BC faf6 d5 PUSH DE faf7 e5 PUSH HL faf8 cd 78 fb call settrk ;track address set from register c fafb e1 pop HL fafc d1 pop DE fafd c1 pop BC fafe c3 cc fa JP load1 ;for another sector fb01 ; fb01 ; end of load operation, set parameters and go to cp/m fb01 gocpm: fb01 3e c3 LD a, 0c3h ;c3 is a jmp instruction fb03 32 00 00 LD (0),A ;for jmp to wboot fb06 21 03 fa LD HL, wboote ;wboot entry point fb09 22 01 00 LD (1),HL ;set address field for jmp at 0 fb0c ; fb0c 32 05 00 LD (5),A ;for jmp to bdos fb0f 21 06 ec LD HL, bdos ;bdos entry point fb12 22 06 00 LD (6),HL ;address field of Jump at 5 to bdos fb15 ; fb15 01 80 00 LD BC, 80h ;default dma address is 80h fb18 cd 89 fb call setdma fb1b ; fb1b fb ei ;enable the interrupt system fb1c 3a 04 00 LD A,(cdisk) ;get current disk number fb1f fe 04 cp disks ;see if valid disk number fb21 da 26 fb jp c,diskok ;disk valid, go to ccp fb24 3e 00 ld a,0 ;invalid disk, change to disk 0 fb26 4f diskok: LD c, a ;send to the ccp fb27 c3 00 e4 JP ccp ;go to cp/m for further processing fb2a ; fb2a ; fb2a ; simple i/o handlers (must be filled in by user) fb2a ; in each case, the entry point is provided, with space reserved fb2a ; to insert your own code fb2a ; fb2a const: ;console status, return 0ffh if character ready, 00h if not fb2a db 03 in a,(3) ;get status fb2c e6 02 and 002h ;check RxRDY bit fb2e ca 34 fb jp z,no_char fb31 3e ff ld a,0ffh ;char ready fb33 c9 ret fb34 3e 00 no_char:ld a,00h ;no char fb36 c9 ret fb37 ; fb37 conin: ;console character into register a fb37 db 03 in a,(3) ;get status fb39 e6 02 and 002h ;check RxRDY bit fb3b ca 37 fb jp z,conin ;loop until char ready fb3e db 02 in a,(2) ;get char fb40 e6 7f AND 7fh ;strip parity bit fb42 c9 ret fb43 ; fb43 conout: ;console character output from register c fb43 db 03 in a,(3) fb45 e6 01 and 001h ;check TxRDY bit fb47 ca 43 fb jp z,conout ;loop until port ready fb4a 79 ld a,c ;get the char fb4b d3 02 out (2),a ;out to port fb4d c9 ret fb4e ; fb4e list: ;list character from register c fb4e 79 LD a, c ;character to register a fb4f c9 ret ;null subroutine fb50 ; fb50 listst: ;return list status (0 if not ready, 1 if ready) fb50 af XOR a ;0 is always ok to return fb51 c9 ret fb52 ; fb52 punch: ;punch character from register C fb52 79 LD a, c ;character to register a fb53 c9 ret ;null subroutine fb54 ; fb54 ; fb54 reader: ;reader character into register a from reader device fb54 3e 1a LD a, 1ah ;enter end of file for now (replace later) fb56 e6 7f AND 7fh ;remember to strip parity bit fb58 c9 ret fb59 ; fb59 ; fb59 ; i/o drivers for the disk follow fb59 ; for now, we will simply store the parameters away for use fb59 ; in the read and write subroutines fb59 ; fb59 home: ;move to the track 00 position of current drive fb59 ; translate this call into a settrk call with Parameter 00 fb59 0e 00 LD c, 0 ;select track 0 fb5b cd 78 fb call settrk fb5e c9 ret ;we will move to 00 on first read/write fb5f ; fb5f seldsk: ;select disk given by register c fb5f 21 00 00 LD HL, 0000h ;error return code fb62 79 LD a, c fb63 32 63 fc LD (diskno),A fb66 fe 04 CP disks ;must be between 0 and 3 fb68 d0 RET NC ;no carry if 4, 5,... fb69 ; disk number is in the proper range fb69 ; defs 10 ;space for disk select -- not needed for modern hard disk fb69 ; compute proper disk Parameter header address fb69 3a 63 fc LD A,(diskno) fb6c 6f LD l, a ;l=disk number 0, 1, 2, 3 fb6d 26 00 LD h, 0 ;high order zero fb6f 29 ADD HL,HL ;*2 fb70 29 ADD HL,HL ;*4 fb71 29 ADD HL,HL ;*8 fb72 29 ADD HL,HL ;*16 (size of each header) fb73 11 33 fa LD DE, dpbase fb76 19 ADD HL,DE ;hl=,dpbase (diskno*16) Note typo here in original source. fb77 c9 ret fb78 ; fb78 settrk: ;set track given by register c fb78 79 LD a, c fb79 32 5d fc LD (track),A fb7c c9 ret fb7d ; fb7d setsec: ;set sector given by register c fb7d 79 LD a, c fb7e 32 5f fc LD (sector),A fb81 c9 ret fb82 ; fb82 ; fb82 sectran: fb82 ;translate the sector given by bc using the fb82 ;translate table given by de fb82 eb EX DE,HL ;hl=.trans fb83 09 ADD HL,BC ;hl=.trans (sector) fb84 c9 ret ;debug no translation fb85 6e LD l, (hl) ;l=trans (sector) fb86 26 00 LD h, 0 ;hl=trans (sector) fb88 c9 ret ;with value in hl fb89 ; fb89 setdma: ;set dma address given by registers b and c fb89 69 LD l, c ;low order address fb8a 60 LD h, b ;high order address fb8b 22 61 fc LD (dmaad),HL ;save the address fb8e c9 ret fb8f ; fb8f ; Subroutine to print greeting message fb8f ; Address of zero-terminated string passed in HL fb8f 3e 00 prmsg: ld a,0 ;check if at end of string fb91 4e ld c,(hl) fb92 b1 or c fb93 c8 ret z ;yes, return fb94 cd 43 fb call conout ;no, output character fb97 23 inc hl fb98 c3 8f fb jp prmsg fb9b ; fb9b ; fb9b read: fb9b ;Read one CP/M sector from disk. fb9b ;Return a 00h in register a if the operation completes properly, and 01h if an error occurs during the read. fb9b ;Disk number in 'diskno' fb9b ;Track number in 'track' fb9b ;Sector number in 'sector' fb9b ;Dma address in 'dmaad' (0-65535) fb9b ; fb9b 21 e8 fe ld hl,hstbuf ;buffer to place disk sector (256 bytes) fb9e db 0f rd_status_loop_1: in a,(0fh) ;check status fba0 e6 80 and 80h ;check BSY bit fba2 c2 9e fb jp nz,rd_status_loop_1 ;loop until not busy fba5 db 0f rd_status_loop_2: in a,(0fh) ;check status fba7 e6 40 and 40h ;check DRDY bit fba9 ca a5 fb jp z,rd_status_loop_2 ;loop until ready fbac 3e 01 ld a,01h ;number of sectors = 1 fbae d3 0a out (0ah),a ;sector count register fbb0 3a 5f fc ld a,(sector) ;CP/M sector (0 to 63, 6 bits) fbb3 cb 27 sla a ;make room for diskno (0 to 3, 2 bits) fbb5 cb 27 sla a fbb7 47 ld b,a fbb8 3a 63 fc ld a,(diskno) ;CP/M disk (0 to 3) fbbb 80 add b ;diskno and sector now in one byte fbbc d3 0b out (0bh),a ;lba bits 0 - 7 fbbe 3a 5d fc ld a,(track) ;CP/M track (0 to 255, 8 bits) fbc1 d3 0c out (0ch),a ;lba bits 8 - 15 fbc3 3e 00 ld a,0 ;upper bits zero fbc5 d3 0d out (0dh),a ;lba bits 16 - 23 fbc7 3e e0 ld a,11100000b ;LBA mode, select host drive 0 fbc9 d3 0e out (0eh),a ;drive/head register fbcb 3e 20 ld a,20h ;Read sector command fbcd d3 0f out (0fh),a fbcf db 0f rd_wait_for_DRQ_set: in a,(0fh) ;read status fbd1 e6 08 and 08h ;DRQ bit fbd3 ca cf fb jp z,rd_wait_for_DRQ_set ;loop until bit set fbd6 db 0f rd_wait_for_BSY_clear: in a,(0fh) fbd8 e6 80 and 80h fbda c2 d6 fb jp nz,rd_wait_for_BSY_clear fbdd db 0f in a,(0fh) ;clear INTRQ fbdf db 08 read_loop: in a,(08h) ;get data fbe1 77 ld (hl),a fbe2 23 inc hl fbe3 db 0f in a,(0fh) ;check status fbe5 e6 08 and 08h ;DRQ bit fbe7 c2 df fb jp nz,read_loop ;loop until clear fbea 2a 61 fc ld hl,(dmaad) ;memory location to place data read from disk fbed 11 e8 fe ld de,hstbuf ;host buffer fbf0 06 80 ld b,128 ;size of CP/M sector fbf2 1a rd_sector_loop: ld a,(de) ;get byte from host buffer fbf3 77 ld (hl),a ;put in memory fbf4 23 inc hl fbf5 13 inc de fbf6 10 fa djnz rd_sector_loop ;put 128 bytes into memory fbf8 db 0f in a,(0fh) ;get status fbfa e6 01 and 01h ;error bit fbfc c9 ret fbfd fbfd write: fbfd ;Write one CP/M sector to disk. fbfd ;Return a 00h in register a if the operation completes properly, and 0lh if an error occurs during the read or write fbfd ;Disk number in 'diskno' fbfd ;Track number in 'track' fbfd ;Sector number in 'sector' fbfd ;Dma address in 'dmaad' (0-65535) fbfd 2a 61 fc ld hl,(dmaad) ;memory location of data to write fc00 11 e8 fe ld de,hstbuf ;host buffer fc03 06 80 ld b,128 ;size of CP/M sector fc05 7e wr_sector_loop: ld a,(hl) ;get byte from memory fc06 12 ld (de),a ;put in host buffer fc07 23 inc hl fc08 13 inc de fc09 10 fa djnz wr_sector_loop ;put 128 bytes in host buffer fc0b 21 e8 fe ld hl,hstbuf ;location of data to write to disk fc0e db 0f wr_status_loop_1: in a,(0fh) ;check status fc10 e6 80 and 80h ;check BSY bit fc12 c2 0e fc jp nz,wr_status_loop_1 ;loop until not busy fc15 db 0f wr_status_loop_2: in a,(0fh) ;check status fc17 e6 40 and 40h ;check DRDY bit fc19 ca 15 fc jp z,wr_status_loop_2 ;loop until ready fc1c 3e 01 ld a,01h ;number of sectors = 1 fc1e d3 0a out (0ah),a ;sector count register fc20 3a 5f fc ld a,(sector) ;CP/M sector (0 to 63, 6 bits) fc23 cb 27 sla a ;make room for diskno fc25 cb 27 sla a fc27 47 ld b,a fc28 3a 63 fc ld a,(diskno) ;CP/M disk (0 to 3, two bits) fc2b 80 add b ;diskno and sector in one byte fc2c d3 0b out (0bh),a ;lba bits 0 - 7 fc2e 3a 5d fc ld a,(track) ;CP/M track (0 to 255, 8 bits) fc31 d3 0c out (0ch),a ;lba bits 8 - 15 fc33 3e 00 ld a,0 ;upper bits zero fc35 d3 0d out (0dh),a ;lba bits 16 - 23 fc37 3e e0 ld a,11100000b ;LBA mode, select drive 0 fc39 d3 0e out (0eh),a ;drive/head register fc3b 3e 30 ld a,30h ;Write sector command fc3d d3 0f out (0fh),a fc3f db 0f wr_wait_for_DRQ_set: in a,(0fh) ;read status fc41 e6 08 and 08h ;DRQ bit fc43 ca 3f fc jp z,wr_wait_for_DRQ_set ;loop until bit set fc46 7e write_loop: ld a,(hl) fc47 d3 08 out (08h),a ;write data fc49 23 inc hl fc4a db 0f in a,(0fh) ;read status fc4c e6 08 and 08h ;check DRQ bit fc4e c2 46 fc jp nz,write_loop ;write until bit cleared fc51 db 0f wr_wait_for_BSY_clear: in a,(0fh) fc53 e6 80 and 80h fc55 c2 51 fc jp nz,wr_wait_for_BSY_clear fc58 db 0f in a,(0fh) ;clear INTRQ fc5a e6 01 and 01h ;check for error fc5c c9 ret fc5d fc5d fc5d ; fc5d ; the remainder of the cbios is reserved uninitialized fc5d ; data area, and does not need to be a Part of the fc5d ; system memory image (the space must be available, fc5d ; however, between"begdat" and"enddat"). fc5d ; fc5d 00... track: defs 2 ;two bytes for expansion fc5f 00... sector: defs 2 ;two bytes for expansion fc61 00... dmaad: defs 2 ;direct memory address fc63 00... diskno: defs 1 ;disk number 0-15 fc64 ; fc64 ; scratch ram area for bdos use fc64 begdat: equ $ ;beginning of data area fc64 00... dirbf: defs 128 ;scratch directory area fce4 ;Allocation scratch areas, size of each must be (DSM/8)+1 fce4 00... all00: defs 128 ;allocation vector 0 fd64 00... all01: defs 128 ;allocation vector 1 fde4 00... all02: defs 128 ;allocation vector 2 fe64 00... all03: defs 128 ;allocation vector 3 fee4 ;Could probably remove these chk areas, but just made size small fee4 00... chk00: defs 1 ;check vector 0 fee5 00... chk01: defs 1 ;check vector 1 fee6 00... chk02: defs 1 ;check vector 2 fee7 00... chk03: defs 1 ;check vector 3 fee8 ; fee8 enddat: equ $ ;end of data area fee8 datsiz: equ $-begdat; ;size of data area fee8 00... hstbuf: ds 256 ;buffer for host disk sector ffe8 end # End of file z80_cbios3.asm ffe8