aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2014-05-09 21:52:52 -0700
committerH. Peter Anvin <hpa@zytor.com>2014-05-09 21:52:52 -0700
commitc224dce6bb9f07a65aafc4568b902d78394db8d8 (patch)
tree90c7dea74c9b12ef70614e84d1b5e6896abcb32a
parent6ff5c30b43a24615b2e669cefcedf24cb63bec27 (diff)
downloadabc80-c224dce6bb9f07a65aafc4568b902d78394db8d8.tar.gz
abc80-c224dce6bb9f07a65aafc4568b902d78394db8d8.tar.xz
abc80-c224dce6bb9f07a65aafc4568b902d78394db8d8.zip
CP/M: Add CBIOS plus change the loader
Make the loader simply POKE the (C)BIOS into memory and then copy it into place... a bit easier that way. Signed-off-by: H. Peter Anvin <hpa@zytor.com>
-rw-r--r--data/Makefile4
-rw-r--r--data/cbios.asm554
-rw-r--r--data/cpm.asm60
3 files changed, 569 insertions, 49 deletions
diff --git a/data/Makefile b/data/Makefile
index c11fe5a..673889c 100644
--- a/data/Makefile
+++ b/data/Makefile
@@ -56,8 +56,10 @@ chargen.mif : chargen.bin $(BIN2MIF)
videoram.bin: video.txt genvideo.pl
$(PERL) genvideo.pl -40 < $< > $@ || ( rm -f $@ ; exit 1 )
+cpm.bin: cpm.asm cbios.bin
+
cpm.bas: cpm.bin
- $(PERL) bin2poke.pl $< 32768 1 'Z%=CALL(32768%)' > $@ \
+ $(PERL) bin2poke.pl $< 32768 100 'Z%=CALL(32768%)' > $@ \
|| ( rm -f $@ ; exit 1 )
.bin.bas:
diff --git a/data/cbios.asm b/data/cbios.asm
new file mode 100644
index 0000000..f64ccdf
--- /dev/null
+++ b/data/cbios.asm
@@ -0,0 +1,554 @@
+; SOCZ80 CP/M 2.2 BIOS (or at least an attempt!)
+; 2013-11-03 Will Sowerbutts
+
+; useful resources:
+; http://www.cpm.z80.de/manuals/cpm22-m.pdf
+; http://www.retrotechnology.com/dri/howto_cpm.html#myown
+; http://www.retrotechnology.com/dri/cpm_features.html
+; cpmtools package
+
+msize: equ 64 ; KB of system RAM
+biosextra: equ 512
+bias: equ (msize-20)*1024-biosextra
+ccp: equ 0x3400+bias
+bdos: equ ccp+0x0806
+bios: equ ccp+0x1600
+cdisk: equ 0x0004 ; current disk number
+ ; 0=a, 1=b ... 15=p
+ ; bits 7:4 is the current USER
+iobyte: equ 0x0003 ; Intel "standard" I/O byte
+currow: equ 0xFFFE ; Cursor row
+curcol: equ 0xFFFF ; Cursor column
+
+ org bios
+
+; jump vector table used by CP/M
+ jp boot ; cold start
+wboote: 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 out
+ jp home ; move disk head to home position
+ jp seldsk ; select disk
+ jp seltrk ; set track number
+ jp setsec ; set setor number
+ jp setdma ; set DMA address
+ jp read ; read disk
+ jp write ; write disk
+ jp listst ; return list status
+ jp sectran ; sector translate
+
+; Interrupt vectors (must be aligned to an even byte boundary)
+ align 2
+kbdvec: defw irq_kbd
+v24vec: defw irq_v24
+
+; disk parameter header (16 bytes for each drive), see page 6-28 in CP/M 2.2 operating system manual
+dpbase:
+ ; disk 0 (A)
+ dw 0 ; sector translation table (0 = no translation)
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw dirbf ; DIRBUF (address of shared 128 byte scratch pad area)
+ dw dpblk ; DPB (disk parameter block)
+ dw chk00 ; CSV (unique scratch pad used to check for changed disks)
+ dw alv00 ; ALV (unique scratch pad for allocation information)
+ ; end of disk 0
+
+ ; disk 1 (B)
+ dw 0 ; sector translation table (0 = no translation)
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw dirbf ; DIRBUF (address of shared 128 byte scratch pad area)
+ dw dpblk ; DPB (disk parameter block)
+ dw chk01 ; CSV (unique scratch pad used to check for changed disks)
+ dw alv01 ; ALV (unique scratch pad for allocation information)
+ ; end of disk 1
+
+ ; disk 2 (C)
+ dw 0 ; sector translation table (0 = no translation)
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw dirbf ; DIRBUF (address of shared 128 byte scratch pad area)
+ dw dpblk ; DPB (disk parameter block)
+ dw chk02 ; CSV (unique scratch pad used to check for changed disks)
+ dw alv02 ; ALV (unique scratch pad for allocation information)
+ ; end of disk 2
+
+ ; disk 3 (D)
+ dw 0 ; sector translation table (0 = no translation)
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw 0 ; must be 0
+ dw dirbf ; DIRBUF (address of shared 128 byte scratch pad area)
+ dw dpblk ; DPB (disk parameter block)
+ dw chk03 ; CSV (unique scratch pad used to check for changed disks)
+ dw alv03 ; ALV (unique scratch pad for allocation information)
+ ; end of disk 3
+
+ndisks: equ 4 ; number of disks we defined
+
+; disk parameter block (can be shared by all disks with same configuration)
+; each volue is 15,597,568 bytes (238*256*256). We represent this as
+; tracks of 8192 bytes (64 sectors x 128 bytes) which matches an UFD-DOS
+; "cluster". This means there are 1904 tracks. The first track is reserved
+; for the system, leaving 1903*64 = 121792 user sectors.
+; We use 8K blocks, matching ABC clusters, so there are 1903 user blocks.
+dpblk:
+ dw 64 ; SPT: number of 128 byte sectors per track
+ db 5 ; BSH: block shift factor (see manual for table)
+ db 31 ; BLM: block mask (see manual for table)
+ db 1 ; EXM: extent mask (see manual for table, using entries marked N/A turns out to be a bad idea!)
+ dw 1903 ; DSM: (disk bytes / block bytes) - 1, change alv0x etc if you change this; this is the number of the last sector on the disk, excluding system tracks (ie more system tracks -> this gets smaller)
+ dw 511 ; DRM: directory max entries - 1
+ db 0xf0 ; AL0: directory sector allocation bitmask byte 0
+ db 0x00 ; AL1: directory sector allocation bitmask byte 1
+ dw 0 ; CKS: check size (change chk0x etc if you change this)
+ dw 1 ; OFF: track offset (number of system tracks)
+
+ align 2
+; Screen row address table
+rowtbl:
+ defw 7400h
+ defw 7500h
+ defw 7600h
+ defw 7700h
+ defw 7C00h
+ defw 7D00h
+ defw 7E00h
+ defw 7F00h
+ defw 7450h
+ defw 7550h
+ defw 7650h
+ defw 7750h
+ defw 7C50h
+ defw 7D50h
+ defw 7E50h
+ defw 7F50h
+ defw 74A0h
+ defw 75A0h
+ defw 76A0h
+ defw 77A0h
+ defw 7CA0h
+ defw 7DA0h
+ defw 7EA0h
+ defw 7FA0h
+
+pio_init:
+ ; Port A initialization sequence
+ defb 0CFh, 0FFh, kbdvec & 255, 0B7h, 080h
+ ; Port B initialization sequence
+ defb 0CFh, 087h, v24vec & 255, 0B7h, 000h
+
+; bios functions follow
+boot: ; Cold boot
+ di
+ ld a,7
+ out (7),a ; Map 3, NMI disabled
+ ld sp,0x100 ; Put our stack in the default DMA buffer
+
+ xor a
+ ld (iobyte), a
+ ; say hello
+ ld hl,bootmsg
+do_boot_msg:
+ xor a
+ ld c,(hl)
+ inc hl
+ cp c
+ jr z,msg_done
+ push hl
+ call conout
+ pop hl
+ jr do_boot_msg
+msg_done:
+ ; fall through to wboot
+
+wboot: ; warm boot -- reload CCP and then run it again,
+ ; we also reload BDOS for good measure
+ ; CCP+BDOS is located in track 0 at sector offset 0
+ ; CCP+BDOS is 0x1600 bytes long, ie 44 sectors.
+ ; so we read 44 sectors from sector 20 onwards
+ ; into memory starting at address ccp
+
+ ; put our stack in the default DMA buffer
+ di
+ ld a,7
+ out (7),a ; Map 3, NMI disabled
+ ld sp,0x100 ; Put our stack in the default DMA buffer
+
+ ; Configure interrupts
+ im 2
+ ld a,kbdvec/256
+ ld i,a
+
+ ; Configure PIO port A (keyboard)
+ ld bc,57 + 5*256
+ ld hl,pio_init
+ otir
+
+ ; Configure PIO port B (cassette/V.24)
+ ld bc,59 + 5*256
+ otir
+
+ ; Enable interrupts
+ ei
+
+ ; select disk 0
+ ld c, 0
+ call seldsk
+
+ ; select track 0
+ call home
+
+ ; point DMA target at ccp base
+ ld bc, ccp
+ call setdma
+
+ ; select first sector
+ ld c, 0
+ call setsec
+
+ ; count of sectors to load
+ ld de, 44
+ push de ; store remaining sector count
+
+wbootloop:
+ ; read sector
+ call read
+ pop de ; recover remaining sector count
+ dec de
+ ld a, d
+ or e
+ jr z, gocpm ; boot CP/M after loading the final sector
+ push de ; stash sector count
+
+ ; advance to next sector
+ ld a, (cursector)
+ inc a
+ ld (cursector), a
+
+ ; advance to next DMA address
+ ld hl, (curdmaaddr)
+ ld bc, 0x80
+ add hl, bc
+ ld (curdmaaddr), hl
+
+ ; loop and read in the next sector
+ jr wbootloop
+
+gocpm:
+ ld a, 0xc3 ; 0xc3 is a jmp instruction
+ ld (0), a ; write JMP instruction at 0x0000
+ ld (5), a ; write JMP instruction at 0x0005
+ ld hl, wboote
+ ld (1), hl ; write vector for warm boot at 0x0001, 0x0002.
+ ld hl, bdos ; BDOS entry point
+ ld (6), hl ; write vector for BDOS at 0x0006, 0x0007.
+ ld bc, 0x0080 ; default DMA address
+ call setdma ; configure DMA
+ ld a, (cdisk) ; get current disk
+ ld c, a ; send to ccp
+ jp ccp ; and we're off!
+
+irq_kbd:
+irq_v24:
+ reti
+
+const: ; read console status
+connotready:
+ xor a
+ ret
+
+conin: ; read character from console into A; wait if no character ready
+ xor a
+ ret
+
+conout: ; write chracter from C to console
+ ld a,c
+ sub 08h
+ jp z,con_bs
+ dec a
+ jp z,con_tab
+ dec a
+ jr z,con_lf
+ dec a
+ dec a
+ jp z,con_ff
+ dec a
+ jr z,con_cr
+ ; Fall through
+con_print:
+ ld a,(currow)
+ add a
+ add rowtbl & 0xff
+ ld l,a
+ ld h,rowtbl/256
+ ld a,(curcol)
+ ld b,a
+ add (hl)
+ ld e,a
+ inc l
+ ld d,(hl)
+ di
+ ld a,4 ; Map 0, NMI off
+ out (7),a
+ ld a,c
+ ld (de),a
+ ld a,7 ; Map 3, NMI off
+ out (7),a
+ ei
+ ld a,b
+ inc a
+ ld (curcol),a
+ sub 80
+ ret nz
+ ld (curcol),a
+ ; Fall through
+con_lf:
+ ld a,(currow)
+ inc a
+ ld (currow),a
+ cp 24
+ ret nz
+ ; Need to scroll...
+ dec a
+ ld (currow),a
+ push ix
+ di
+ ld a,4
+ out (7),a ; Map 0, NMI off
+ ld a,23
+ ld ix,rowtbl
+ ld b,0
+con_lfloop:
+ ld e,(ix+0)
+ ld d,(ix+1)
+ ld l,(ix+2)
+ ld h,(ix+3)
+ ld c,80
+ ldir
+ inc ix
+ inc ix
+ dec a
+ jr nz,con_lfloop
+ ; Clear the row which IX currently points to
+ ld l,(ix+0)
+ ld h,(ix+1)
+ ld e,l
+ ld d,h
+ inc e
+ ld c,32 ; space
+ ld (hl),c
+ ld bc,79
+ ldir
+con_pop_done:
+ ld a,7 ; Map 3, NMI off
+ out (7),a
+ ei
+ pop ix
+ ret
+con_cr:
+ xor a
+ ld (curcol),a
+ ret
+con_bs:
+ ld a,(curcol)
+ dec a
+ ret c
+ ld (curcol),a
+ ret
+con_tab:
+ ld c,32
+ call con_print
+ ld a,(curcol)
+ and 07h
+ jr nz,con_tab
+ ret
+con_ff:
+ push ix
+ di
+ ld a,4
+ out (7),a ; Map 0, NMI off
+ xor a
+ ld (curcol),a
+ ld (currow),a
+ ld a,24
+ ld ix,rowtbl
+con_ffloop:
+ ; Clear the row which IX currently points to
+ ld l,(ix+0)
+ ld h,(ix+1)
+ ld e,l
+ ld d,h
+ inc e
+ ld c,32 ; space
+ ld (hl),c
+ ld bc,79
+ ldir
+ inc ix
+ inc ix
+ dec a
+ jr nz,con_ffloop
+ jr con_pop_done
+
+list: ; write character to listing device (we don't have one!)
+ ret
+
+listst: ; return list device status
+ xor a ; 0 = not ready
+ ret
+
+punch: ; write character to punch device (we don't have one!)
+ ret
+
+reader: ; read character from reader device (which we don't have)
+ ld a, 0x1a ; end of file
+ ret
+
+seldsk: ; select disk indicated by register C
+ ld hl, 0 ; return code 0 indicates error
+ ld a, c
+ cp ndisks
+ ret nc ; return (with error code) if C >= ndisks ie illegal drive
+ ld (curdisk), a ; store current disk
+ ; compute proper disk parameter header address in HL
+ add a,a ; *2
+ add a,a ; *4
+ add a,a ; *8
+ add a,a ; *16
+ add a,l
+ ld l,a
+ if (dpbase & 0xff) > (256-ndisks*16)
+ ld a,h
+ adc a,0
+ ld h,a
+ endif
+ ; HL = dpbase + disk number * 16
+ ret
+
+home: ld bc, 0
+ ; fall through into seltrk
+seltrk: ; set track given by register BC
+ ld (curtrack), bc
+ ret
+
+setsec: ; set sector given by register (B)C
+ ld a,c
+ ld (cursector),a
+ ret
+
+sectran: ; logical to physical sector translation
+ ; HL=BC ie 1:1 mapping (no translation)
+ ld h, b
+ ld l, c
+ ret
+
+setdma: ; set DMA address given by BC
+ ld (curdmaaddr),bc
+ ret
+
+read: ; read a sector from disk
+ ; XXX: add error handing!
+ ld c,03h ; READ + SECTOR TO HOST
+ call sendcmd
+ ld hl,(curdmaaddr)
+ ld bc,128
+ inir
+ xor a ; Success!
+ ret
+
+write: ; write sector to disk
+ ; XXX: add error handing!
+ ld c,0Ch ; SECTOR FROM HOST + WRITE
+ call sendcmd
+ ld hl,(curdmaaddr)
+ ld bc,128
+ otir
+ xor a ; Success!
+ ret
+
+ ; Send a controller command, C = K0 command byte
+ ; Clobbers A DE HL, returns with disk selected
+sendcmd:
+ ld a,36
+ out (1),a ; Select disk
+ out (2),a ; Start command
+
+ ld a,(curdisk) ; curdisk
+ or 24h ; +4 for volume, CP/M mode
+ ld e,a
+ ld a,(cursector)
+ rrca
+ jr nc,bc1
+ set 4,e ; odd CP/M sector
+bc1:
+ ld d,0
+ ld hl,(curtrack)
+ srl h
+ rr l
+ rr d
+ srl h
+ rr l
+ rr d
+ srl h
+ rr l
+ rr d
+ or d
+ ld h,a
+
+ ; Now: K0 = C, K1 = E, K2 = L, K3 = H
+sc_wait1:
+ in a,(1) ; Status port
+ rlca
+ jr nc,sc_wait1
+
+ ld a,c
+ call sc_out_wait_ok
+ ld a,e
+ call sc_out_wait_ok
+ ld a,l
+ call sc_out_wait_ok
+ ld a,h
+ ; Fall through
+sc_out_wait_ok:
+ out (0),a
+sc_wait_ok:
+ in a,(1)
+ rrca
+ ret c
+ jr sc_wait_ok
+
+bootmsg: db "CP/M BIOS (H. Peter Anvin, 2014-05-09)\r\n"
+ db "CP/M 2.2 Copyright 1979 (c) by Digital Research\r\n"
+ db 0
+
+;---------------------------------------------------------------------------------------------------------------
+
+
+
+; scratch RAM used by BIOS
+curdisk: db 0
+cursector: db 0
+curtrack: dw 0
+curdmaaddr: dw 0
+
+; scratch RAM used by BDOS
+dirbf: ds 128 ; directory scratch area
+alv00: ds 238 ; allocation vector for disk 0, must be (DSM/8)+1 bytes
+alv01: ds 238 ; allocation vector for disk 1, must be (DSM/8)+1 bytes
+alv02: ds 238 ; allocation vector for disk 2, must be (DSM/8)+1 bytes
+alv03: ds 238 ; allocation vector for disk 3, must be (DSM/8)+1 bytes
+chk00: ds 0 ; check vector for disk 0 (must be CKS bytes long)
+chk01: ds 0 ; check vector for disk 1 (must be CKS bytes long)
+chk02: ds 0 ; check vector for disk 2 (must be CKS bytes long)
+chk03: ds 0 ; check vector for disk 3 (must be CKS bytes long)
+
+db "</bios>"
+; we point the stack at 0x0000 on restart, so that may overwrite the very end of RAM.
diff --git a/data/cpm.asm b/data/cpm.asm
index 1830775..cbbb876 100644
--- a/data/cpm.asm
+++ b/data/cpm.asm
@@ -15,10 +15,10 @@
;
HDSEL: equ 36
RAMSIZE: equ 64
-BIAS: equ (64-20)*1024
-CPMADDR: equ BIAS+3400h
+BIAS: equ (64-20)*1024-512
CPMBIOS: equ BIAS+4A00h
-CPMSECTS: equ 50/2 ; Total number of ABC sectors (@256b)
+ABCCURSOR: equ 65011
+CPMCURSOR: equ 0FFFEh
org 08000h
_start:
@@ -26,51 +26,15 @@ _start:
ld a,7 ; Map 3, NMI disabled
out (7),a
- ld a,HDSEL
- out (1),a
+ in a,(7)
+
+ ld hl,bios
+ ld de,CPMBIOS
+ ld bc,bios_size
+ ldir
- ld hl,CPMADDR
- ld sp,hl
- xor a
- ld e,a ; Current sector
-
-get_sect:
- out (2),a ; Start command
-
-wait_ready:
- in a,(1) ; Status port
- rlca
- jr nc,wait_ready
-
- ld a,3 ; READ SECTOR + SECTOR TO HOST
- call out_wait_ok
-
- ld a,4 ; Unit 4, buffer 0, 256 byte sector
- call out_wait_ok
-
- xor a ; Track 0
- call out_wait_ok
-
- ld a,e ; Sector number
- inc e
- call out_wait_ok
-
- xor a
- ld b,a ; 256 bytes
- ld c,a ; Port 0
- inir
-
- ld a,CPMSECTS
- cp e
- jr nz,get_sect
-
- in a,(7) ; Bus reset
jp CPMBIOS
-out_wait_ok:
- out (0),a
-wait_ok:
- in a,(1)
- rrca
- ret c
- jr wait_ok
+bios:
+ incbin "cbios.bin"
+bios_size: equ $-bios