You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

577 lines
17 KiB

;
;=======================================================================
; p-System BIOS for RomWBW HBIOS
;=======================================================================
;
; 3:46 PM 1/13/2023 - WBW - Initial release
; 5:29 PM 1/15/2023 - WBW - Implemeted extended BIOS functions
; 10:34 AM 1/16/2023 - WBW - Moved slices into partition
;
; TODO:
;
; NOTES:
; - The partition type ID used is the same as the CP/M partition
; type ID. Might make sense to create a new partition ID which
; could allow p-System to co-exist with CP/M on a disk image. This
; would require changes to the RomWBW boot loader as well.
;
; - MBR is borrowed from RomWBW CP/M layout, so the partition size
; is 64 8MB slices. p-System only uses 6 slices. Might be better
; to create a custom MBR with an appropriate size for p-System
; partition.
;
; - The sysinit routine does a lot of work that just sets up a few
; variables for later use. This work could be moved into the
; p-System loader to reduce the size of this BIOS. Since the BIOS
; is only 768 bytes at this point, I have not bothered with it.
;
#include "../ver.inc"
#include "psys.inc"
#include "psys_ior.inc"
#include "../HBIOS/hbios.inc"
;-----------------------------------------------------------------------
; Local constants
;-----------------------------------------------------------------------
; We need to read and buffer a single sector (MBR) at initialization.
; It looks like the area just above the loader is the safest place.
dskbuf .equ loader_loc + loader_size
;-----------------------------------------------------------------------
; BIOS Jump Table
;-----------------------------------------------------------------------
.org bios_loc
; Simple BIOS vectors
jp sysinit ; 0: Initialize machine
jp syshalt ; 1: Exit UCSD Pascal
jp coninit ; 2: Console initialize
jp constat ; 3: Console status
jp conread ; 4: Console input
jp conwrit ; 5: Console output
jp setdisk ; 6: Set disk number
jp settrak ; 7: Set track number
jp setsect ; 8: Set sector number
jp setbufr ; 9: Set buffer address
jp dskread ; 10: Read sector from disk
jp dskwrit ; 11: Write sector to disk
jp dskinit ; 12: Reset disk
jp dskstrt ; 13: Activate disk
jp dskstop ; 14: De-activate disk
; Extended BIOS vectors
jp prninit ; 15: Printer initialize
jp prnstat ; 16: Printer status
jp prnread ; 17: Printer read
jp prnwrit ; 18: Printer write
jp reminit ; 19: Remote initialize
jp remstat ; 20: Remote status
jp remread ; 21: Remote read
jp remwrit ; 22: Remote write
jp usrinit ; 23: User devices initialize
jp usrstat ; 24: User devices status
jp usrread ; 25: User devices read
jp usrwrit ; 26: User devices write
jp clkread ; 27: System clock read
;-----------------------------------------------------------------------
; Simple BIOS routines
;-----------------------------------------------------------------------
sysinit: ; 0: Initialize machine
; Get critical HBIOS bank ids for use later
ld b,BF_SYSGET ; HBIOS SysGet function
ld c,BF_SYSGET_BNKINFO ; BankInfo sub-function
rst 08 ; do it, D=BIOS, E=USER
ld (hb_bnks),de ; save bank info
; Get boot disk to use for all subsequent disk I/O
ld b,BF_SYSGET ; HBIOS SysGet function
ld c,BF_SYSGET_BOOTINFO ; BootInfo sub-function
rst 08 ; do it, boot disk device unit in
ld a,d ; boot unit id returned in D
ld (hb_dev),a ; save for disk I/O
; Get the count of serial (CIO) HBIOS devices in system
ld b,BF_SYSGET ; HBIOS SysGet function
ld c,BF_SYSGET_CIOCNT ; CIO Count sub-function
rst 08 ; do it, count in E
push de ; save it
; Get current HBIOS console unit and assign to pSys console
ld b,BF_SYSPEEK ; HBIOS Peek Function
ld a,(hb_bios) ; HBIOS bank id
ld d,a ; ... goes in D
ld hl,$112 ; offset $112 is current console device
rst 08 ; call HBIOS, value returned in E
ld a,e ; move to A
ld hl,hb_con ; use HL to point to hb_con
ld (hl),a ; save as console device
; Assign additional HBIOS serial devices as pSys remote and printer
pop bc ; recover CIO count, now in C
ld a,0 ; assume remote on HB unit 0
cp (hl) ; conflict?
jr nz,sysinit1 ; if no conflict, continue
inc a ; else increment to next unit
sysinit1:
cp c ; check for over max serial count
jr nc,sysinit3 ; if exceeded, we are done
ld (hb_rem),a ; assign remote device
inc a ; bump to next dev for printer
cp (hl) ; conflict?
jr nz,sysinit2 ; if no conflict, continue
inc a ; else increment to next unit
sysinit2:
cp c ; check for over max serial count
jr nc,sysinit3 ; if exceeded, we are done
ld (hb_prn),a ; assign printer device
sysinit3:
; Announce BIOS
ld hl,str_banner ; load version banner
call prtstr ; and display it
;call conread ; wait for user
; The p-System slices live within a disk partition. So, now we
; read the MBR, look for our partition ID, extract the
; corresponding LBA offset and save it for subsequent disk I/O.
; Read MBR. The MBR lives in the first sector of the hard
; disk. At this point paroff, curdisk, curtrak, and cursect
; are all zero. So, we just set the disk buffer and make a
; disk I/O call which results in reading the first (MBR)
; sector.
ld bc,dskbuf ; load disk buf adr
ld (curbufr),bc ; save it
call dskread ; read first sector of phy disk
jp nz,parterr ; abort on error
; Check signature
ld hl,(dskbuf+$1FE) ; get signature
ld a,l ; first byte
cp $55 ; should be $55
jp nz,parterr ; if not, no part table
ld a,h ; second byte
cp $AA ; should be $AA
jp nz,parterr ; if not, no part table
; Search part table for entry (type 0x2E)
ld b,4 ; four entries in part table
ld hl,dskbuf+$1BE+4 ; offset of first part type
sysinit4:
ld a,(hl) ; get part type
cp $2E ; CP/M partition?
jr z,sysinit5 ; cool, grab the LBA offset
ld de,16 ; part table entry size
add hl,de ; bump to next part type
djnz sysinit4 ; loop thru table
jp parterr ; too bad, no CP/M partition
sysinit5:
; Capture the starting LBA of the partition we found
ld de,4 ; LBA is 4 bytes after part type
add hl,de ; point to it
ld de,paroff ; loc to store lba offset
ld bc,4 ; 4 bytes (32 bits)
ldir ; copy it
sysinit6:
; Vector sysinit is being called twice during startup. Once
; from the bootstrap and then from the interpreter. So, we
; remap the vector here to avoid doing the above stuff
; multiple times.
ld hl,sysinitz ; re-vector to sysinitz
ld (bios_loc+1),hl ; update the jump table
sysinitz:
ret ; done
xor a ; signal success
syshalt: ; 1: Exit UCSD Pascal
; The syshalt vector does not seem be to invoked when
; selecting the Halt option from the p-System menu.
; I have no idea why.
ld b,BF_SYSRESET ; HBIOS reset function
ld c,BF_SYSRES_WARM ; warm reset is fine
rst 08 ; do it
; We should never get here.
di ; interrupts off
halt ; ... and die
coninit: ; 2: Console initialize
ld a,(hb_con) ; initialize console unit
jp serinit ; do it
constat: ; 3: Console status
ld a,(hb_con) ; status of console unit
jp serstat ; do it
conread: ; 4: Console input
ld a,(hb_con) ; read from console unit
jp serread ; do it
conwrit: ; 5: Console output
ld a,c
cp 27 ; escape?
ld a,(hb_con) ; write to console unit
jp nz,serwrit ; if not, handle normally
call serwrit ; else, send escape
ld c,'[' ; ... followed by '[' for ANSI
ld a,(hb_con) ; write to console unit
jp serwrit ; do it
setdisk: ; 6: Set disk number
ld a,c ; disk number to A
ld (curdisk),a ; save for later
; Each p-System disk lives in a slice. Additionally,
; the start of the slices is determined by the hard
; disk partition table. To avoid computing the p-System
; disk offset on every I/O call, below we pre-compute
; the physical HBIOS disk LBA offset for the slice of the
; p-System disk being selected here.
ld hl,(paroff) ; initialize DE:HL
ld de,(paroff+2) ; ... to start of partition
or a ; use A as loop ctr, check for zero
jr z,setdisk2 ; if 0, no slice offset needed
setdisk1:
ld bc,(sps) ; get low word of sps
add hl,bc ; add low words
ex de,hl ; swap high word into HL
ld bc,(sps+2) ; get high word of sps
adc hl,bc ; add high words (w/ carry)
ex de,hl ; swap back to get DE:HL
dec a ; dec loop ctr
jr nz,setdisk1 ; rinse and repeat
setdisk2:
ld (curoff),hl ; save low word
ld (curoff+2),de ; save high word
xor a ; signal success
ret ; done
settrak: ; 7: Set track number
ld a,c ; track number to A
ld (curtrak),a ; save for later
xor a ; signal success
ret ; done
setsect: ; 8: Set sector number
ld a,c ; sector number to A
dec a ; from 1 indexed to 0 indexed
ld (cursect),a ; save for later
xor a ; signal success
ret
setbufr: ; 9: Set buffer address
ld (curbufr),bc ; save buf adr for later
xor a ; signal success
ret ; done
dskread: ; 10: Read sector from disk
call chkdisk
ret nz
call seek
ret nz
ld b,BF_DIOREAD ; HBIOS disk read function
ld a,(hb_dev) ; HBIOS disk unit
ld c,a ; ... goes in C
ld a,(HB_CURBNK) ; get current memory bank
ld d,a ; use as target bank for transfer
ld e,1 ; read 1 sector
ld hl,(curbufr) ; disk read buffer adr
rst 08 ; do it
ret z ; return if good read
ld a,ior_badblk ; else i/o error
ret ; done
dskwrit: ; 11: Write sector to disk
call chkdisk
ret nz
call seek
ret nz
ld b,BF_DIOWRITE ; HBIOS disk read function
ld a,(hb_dev) ; HBIOS disk unit
ld c,a ; ... goes in C
ld a,(HB_CURBNK) ; get current memory bank
ld d,a ; use as target bank for transfer
ld e,1 ; read 1 sector
ld hl,(curbufr) ; disk read buffer adr
rst 08 ; do it
ret z ; return if good read
ld a,ior_badblk ; else i/o error
ret ; done
dskinit: ; 12: Reset disk
call chkdisk
ret nz
xor a ; signal success
ret ; done
dskstrt: ; 13: Activate disk
xor a ; signal success
ret ; done
dskstop: ; 14: De-activate disk
xor a ; signal success
ret ; done
;-----------------------------------------------------------------------
; Extended BIOS routines
;-----------------------------------------------------------------------
prninit: ; 15: Printer initialize
ld a,(hb_prn) ; initialize printer unit
jp serinit ; do it
prnstat: ; 16: Printer status
ld a,(hb_prn) ; status of printer unit
jp serstat ; do it
prnread: ; 17: Printer read
ld a,(hb_prn) ; read from printer unit
jp serread ; do it
prnwrit: ; 18: Printer write
ld a,(hb_prn) ; write to printer unit
jp serwrit ; do it
reminit: ; 19: Remote initialize
ld a,(hb_rem) ; initialize remote unit
jp serinit ; do it
remstat: ; 20: Remote status
ld a,(hb_rem) ; status of remote unit
jp serstat ; do it
remread: ; 21: Remote read
ld a,(hb_rem) ; read from remote unit
jp serread ; do it
remwrit: ; 22: Remote write
ld a,(hb_rem) ; write to remote unit
jp serwrit ; do it
usrinit: ; 23: User devices initialize
ld a,9 ; offline status
ret
usrstat: ; 24: User devices status
pop hl ; return address
pop de ; discard input/output toggle
pop de ; discard ptr to status rec
pop de ; discard device number
ld a,9 ; offline status
jp (hl) ; return
usrread: ; 25: User devices read
usrwrit: ; 26: User devices write
pop hl ; return address
pop de ; extra parameter 2
pop de ; extra parameter 1
pop de ; pointer to buffer
pop de ; device number
pop de ; extra parameter 5
ld a,9 ; offline status
jp (hl) ; return
clkread: ; 27: System clock read
ld b,BF_SYSGET ; HBIOS SysGet function
ld c,BF_SYSGET_TIMER ; Timer sub-function
rst 08 ; do it, ticks ret in DE:HL
ex de,hl ; swap for pSys
xor a ; signal success
ret ; done
;-----------------------------------------------------------------------
; Support routines
;-----------------------------------------------------------------------
serinit:
; Initialize HBIOS serial port identified in reg A
cp $FF ; do we have desired port?
jr z,nodev ; handle it if so
xor a ; signal success
ret ; done
serstat:
; Check status of HBIOS serial port identified in reg A
cp $FF ; do we have desired port?
jr z,nodev ; handle it if so
ld b,BF_CIOIST ; serial port status function
ld c,a ; HBIOS serial port
rst 08 ; call HBIOS
ld c,0 ; assume no chars pendin
jr z,serstat1 ; if zero, no chars waiting
ld c,$FF ; signal char(s) pending
serstat1:
xor a ; signal success
ret ; done
serread:
; Read one byte from HBIOS serial port identified in reg A
cp $FF ; do we have desired port?
jr z,nodev ; handle it if so
ld b,BF_CIOIN ; serial port read function
ld c,a ; HBIOS serial port
rst 08 ; call HBIOS
ld c,e ; char to C
xor a ; signal success
ret ; done
serwrit:
; Write one byte to HBIOS serial port identified in reg A
cp $FF ; do we have desired port?
jr z,nodev ; handle it if so
ld e,c ; char to write to E
ld b,BF_CIOOUT ; serial port write function
ld c,a ; HBIOS serial port
rst 08 ; call HBIOS
xor a ; signal success
ret ; done
nodev:
ld a,9 ; signal volume offline
ret ; and done
chkdisk:
; Validate that curdisk is <= max supported
ld a,(curdisk) ; get current disk
cp disks ; compare to disk count
jr nc,chkdisk1 ; if too high, go to err
xor a ; signal success
ret ; done
chkdisk1:
ld a,ior_novol ; signal not online
or a ; set flags
ret ; done
seek:
; We use LBA addressing for disk access. So, we need to
; translate the track/sector value from p-System into an
; lba offset. Since we are using 16 sectors per track, we
; can cheat (avoid multiplication) by using the low 4 bits
; for sector and the high bits for track which allows us to
; just "or" the values together. We are only using word values
; here since that will handle up to a 32MB p-System file system
; (slice) which is more than enough.
ld a,(curtrak) ; cur track in accum
ld l,a ; move to low byte of HL
ld h,0 ; zero out high byte of HL
add hl,hl ; * 2
add hl,hl ; * 4
add hl,hl ; * 8
add hl,hl ; * 16 (sectors per track)
ld a,(cursect) ; cur sec to accum
or l ; combine with low byte of HL
ld l,a ; back to low byte of HL
; HL now has LBA offset of desired sector. Next
; we need to add in the offset of the current disk.
; At this point, we need to start using dword values
; using DE:HL to accommodate large disk drives.
ld de,0 ; extend LBA to DE:HL
ld bc,(curoff) ; get low word of offset
add hl,bc ; add low words together
ex de,hl ; swap high word of LBA into HL
ld bc,(curoff+2) ; get high word of offset
adc hl,bc ; add high words together (w/ carry)
ex de,hl ; swap back to get DE:HL
; Now we have final LBA in DE:HL. We just set the
; LBA flag bit and do the disk seek.
set 7,d ; high order bit designates LBA I/O
ld b,BF_DIOSEEK ; HBIOS seek function
ld a,(hb_dev) ; HBIOS disk unit
ld c,a ; ... goes in C
rst 08 ; do it
ret z ; if no error, done
ld a,ior_badblk ; signal I/O error
ret ; done
prtstr:
; Print a null terminated string on the p-System console
ld a,(hl) ; get next char
or a ; set flags
ret z ; done if null
push hl ; save buffer pointer
ld c,a ; char to C
call conwrit ; write it out to pSys console
pop hl ; recover buffer pointer
inc hl ; increment to next char
jr prtstr ; loop as needed
parterr:
ld hl,str_parterr ; partition error string
call prtstr ; display it
jp syshalt ; back to boot loader
panic:
; Hard stop
di ; no interrupts
halt ; ... and halt
;-----------------------------------------------------------------------
; Local storage
;-----------------------------------------------------------------------
hb_bnks:
hb_usr .db 0 ; HBIOS User bank id
hb_bios .db 0 ; HBIOS BIOS bank id
hb_dev .db 0 ; HBIOS device for pSys disk
hb_con .db $FF ; HBIOS device for pSys console unit
hb_rem .db $FF ; HBIOS device for pSys remote unit
hb_prn .db $FF ; HBIOS device for pSys printer unit
curdisk .db 0 ; Current pSys disk number
curtrak .db 0 ; Current pSys track number
cursect .db 0 ; Current pSys sector number
curbufr .dw 0 ; Current pSys disk buffer address
curoff .dw 0,0 ; Current sector offset (dword LBA)
paroff .dw 0,0 ; Partition offset (dword LBA)
sps .dw 16384,0 ; Sectors per slice (8MB / 512) = 16384
str_banner .db 13,10,13,10,"RomWBW p-System Extended BIOS v"
.db BIOSVER,0
str_parterr .db 13,10,"*** Disk partition table error!",0
#if ($ >= bios_end)
.echo "*** ERROR: Out of space in pSystem BIOS!!!\n"
!!! ; force an assembly error
#endif
slack .equ bios_end - $
.echo "pSystem BIOS space remaining: "
.echo slack
.echo " bytes.\n"
.fill bios_end - $
.end