;
;=======================================================================
;   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
