; ============================================================================
; *                 CP/M 2.2 BIOS EMULATOR FOR CP/M 3.x                      *
; ============================================================================
;  This program, by Mike Griswold, is a Resident System Extension (RSX) which
;  runs under CP/M-Plus.  This RSX intercepts certain BIOS function calls,
;  and resolves differences between CP/M 2.2 and CP/M 3.x.  It allows many
;  programs that do not normally work with CP/M-Plus to work correctly.
;  So far, I have installed this RSX on DU2, which normally does not work
;  at all, and with the RSX, it works perfectly.
;  Installation instructions:
;      1. RMAC CPM22 $pz $sz
;      2. LINK CPM22[OP]
;      3. REN CPM22.RSX=CPM22.PRL
;      4. GENCOM <program name> CPM22
;        NOTE:  <program name> is a .COM file which is the CP/M 2.2 command
;             file.  
;
;  Program typed from Doctor Dobbs Journal #93, July 1984.
;     Typed & Instructions written by Charles Foreman.
;     Uploaded to CURA RCP/M - (212) 625-5931 - by Charles Foreman.
;
;
	title	'CP/M 2.2 BIOS RSX'
;
;	18Jan84			By Mike Griswold
;
;	This RSX will provide CP/M 2.2 compatible BIOS support
;	for CP/M 3.x.  Primarily it performs logical sector
;	blocking and deblocking needed for some programs.
;	All actual I/O is done by the CP/M 3.0 BIOS.
;
	maclib	z80		; Z80 opcode equates
	cseg
;
;	This equate is the only hardware dependant value.
;	It should be set to the largest sector size that 
;   	will be used.
;
max$sector$size:	equ	512
;
;
;	RSX prefix structure
;
	db	0,0,0,0,0,0
entry:	jmp	boot
next:	db	jmp		; jump
	dw	0		; next module in line
prev:	db	0		; previous module
remove:	db	00fh		; remove flag
nonbnk:	db	0
	db	'BIOS2.21'
	db	0,0,0
;
;	Align jump table on next page boundary.  This is needed
;	for programs that cheat when getting the addresses of
;	BIOS jump table entries.  Optimization freaks could move
;	some code up here.  With a 60k TPA though its hard to
;	get excited.
;
	ds	229
;
;	BIOS Jump Table
;
cbt:	jmp	wboot		; cold boot entry
wbt:	jmp	wboot		; warm boot entry
	jmp	xconst		; console status
	jmp	xconin		; console input
	jmp	xconout		; console output
	jmp	xlist		; list output
	jmp	xauxout		; aux device output
	jmp	xauxin		; aux device input
	jmp	home		; home disk head
	jmp	seldsk		; select drive
	jmp	settrk		; select track
	jmp	setsec		; select sector
	jmp	setdma		; set dma address
	jmp	read		; read a sector
	jmp	write		; write a sector
	jmp	xlistst		; list status
	jmp	sectran		; sector translation
;
;	The CP/M 3.0 BIOS jump table is copied here
;	to allow easy access to its routines.  The disk
;	I/O routines are potentially in banked memory
;	so they cannot be called directly.
;
xwboot:	jmp	0		; warm boot
xconst:	jmp	0
xconin:	jmp	0
xconout:jmp	0
xlist:	jmp	0
xauxout:jmp	0
xauxin:	jmp	0
	jmp	0
	jmp	0
	jmp	0
	jmp	0
	jmp	0
	jmp	0
	jmp	0
xlistst:jmp	0
;
;	Signon message
;
signon:	db	0dh,0ah,'BIOS ver 2.21 ACTIVE',0dh,0ah,0
;
;	Cold boot
;
boot:	push	psw		; a BDOS call is in progress
	push	h		; so save CPU state
	push	d
	push	b
	lxi	h,next		; now bypass this RSX on
	shld	entry+1		; all subsequent BDOS calls
	call	init		; initialize BIOS variables
	lhld	1		; save the CP/M 3.0 BIOS jump
	shld	old$addr	; at location 0
	lxi	d,xwboot	; set up to move jump table
	lxi	b,15*3		; byte count
	ldir
	lxi	h,wbt		; substitute new jump address
	shld	1
	lxi	h,signon	; sound off
	call	prmsg
	pop	b		; restore BDOS call state
	pop	d
	pop	h
	pop	psw
	jmp	next		; carry on
;
;	Warm boot
;
wboot:	lhld	old$addr
	shld	1		; restore normal BIOS address
	jmp	0		; jump to CP/M 3.0 warm boot
;
;	Initialize BIOS internal variables for cold boot
;
init:	xra	a
	sta	hstwrt		; host buffer written
	sta	hstact		; host buffer inactive
	lxi	h,80h
	shld	dmaadr
	ret
;
;	Routine to call banked BIOS routines via BDOS
;	function 50.  All disk I/O calls are made through
;	here.
;
xbios:	sta	biospb		; set BIOS function
	mvi	c,50		; direct BIOS call function
	lxi	d,biospb	; BIOS parameter block
	jmp	next		; jump to BDOS
;
biospb:	db	0		; BIOS function
areg:	db	0		; A reguster
bcreg:	dw	0		; BC register
dereg:	dw	0		; DE register
hlreg:	dw	0		; HL register
;
;	Home disk.
;
home:	lda	hstwrt		; check if pending write
	ora	a
	cnz	writehst	; dump buffer to disk
	xra	a
	sta	hstwrt		; buffer written
	sta	hstact		; buffer inactive
	sta	unacnt		; zero alloc count
	sta	sektrk		; zero track count
	sta	sektrk+1
	ret
;
;	Select disk.  Create a fake DPH for programs
;	that might use it.
;
seldsk:	mov	a,c		; requested drive number
	sta	sekdsk
	sta	bcreg		; set C reg in BIOSPB
	mvi	a,9		; BIOS function number
	call	xbios		; CP/M 3.0 select
	mov	a,h
	ora	l		; check for HL=0
	rz			; select error
	mov	e,m		; get address of xlat table
	inx	h
	mov	d,m
	xchg
	shld	xlat		; save xlat address
	lxi	h,11		; offset to dpb address
	dad	d
	mov	e,m		; fetch address to dpb
	inx	h
	mov	d,m
	xchg
	shld	dpb		; address of dpb
	mov	a,m		; cpm sectors per track
	sta	spt
	inx	h
	inx	h		; point to block shift mask
	inx	h
	mov	a,m
	sta	bsm		; save block shift mask
	lxi	d,12		; offset to psh
	dad	d
	mov	a,m
	sta	psh		; save physical shift factor
	lxi	h,dph		; return DPH address
	ret
;
;	This fake DPH holds the addresses of the actual
;	DPB.  The CP/M 3.0 DPH is *not* understood
;	by CP/M 2.2 programs.
;
dph:	equ	$
	dw	0		; no translation
	ds	6		; scratch words
	ds	2		; directory buffer
dpb:	ds	2		; DPB
	ds	2		; CSV
	ds	2		; ALV
;
;	Set track.
;
settrk:	sbcd	sektrk
	ret
;
;	Set dma.
;
setdma:	sbcd	dmaadr
	ret
;
;	Translate sectors.  Sectors are not translated yet.
;	Wait until we know the physical sector number.
;	This works fine as long as the program trusts
;	the BIOS to do the translation.  Some programs
;	access the XLAT table directly to do their own
;	translation.  These programs will get the wrong
;	idea about disk skew but it should cause no
;	harm.
;
sectran:mov	l,c		; return sector in HL
	mov	h,b
	ret
;
;	Set sector number.
;
setsec:	mov	a,c
	sta	seksec
	ret
;
;	Read the selected CP/M sector.
;
read:	mvi	a,1
	sta	readop		; read operation
	inr	a		; a=2 (wrual)
	sta	wrtype		; treat as unalloc
	jmp	alloc		; perform read
;
;	Write the selected CP/M sector.
;
write:	xra	a
	sta	readop		; not a read operation
	mov	a,c
	sta	wrtype		; save write type
	cpi	2		; unalloc block?
	jrnz	chkuna
;
;	Write to first sector of unallocated block.
;
	lda	bsm		; get block shift mask
	inr	a		; adjust value
	sta	unacnt		; unalloc record count
	lda	sekdsk		; set up values for
	sta	unadsk		; writing to an unallocated
	lda	sektrk		; block
	sta	unatrk
	lda	seksec
	sta	unasec
;
chkuna:	lda	unacnt		; any unalloc sectors
	ora	a		; in this block
	jrz	alloc		; skip if not
	dcr	a		; --unacnt
	sta	unacnt
	lda	sekdsk
	lxi	h,unadsk
	cmp	m		; sekdsk = unadsk ?
	jrnz	alloc		; skip if not
	lda	sektrk
	cmp	m		; sektrk = unatrk ?
	jrnz 	alloc		; skip if not
	lda	seksec
	lxi	h,unasec
	cmp	m		; sektrk = unasec ?
	jrnz	alloc		; skip if not
	inr	m		; move to next sector
	mov	a,m
	lxi	h,spt		; addr of spt
	cmp	m		; sector > spt ?
	jrc	noovf		; skip if no overflow
	lhld	unatrk
	inx	h
	shld	unatrk		; bump track
	xra	a
	sta	unasec		; reset sector count
noovf:	xra 	a
	sta	rsflag		; don't pre-read
	jr	rwoper		; perform write
;
alloc:	xra	a		; requires pre-read
	sta	unacnt
	inr	a
	sta	rsflag		; force pre-read
;
rwoper:	xra	a
	sta	erflag		; no errors yet
	lda	psh		; get physical shift factor
	ora 	a		; set flags
	mov	b,a
	lda	seksec		; logical sector
	lxi	h,hstbuf	; addr of buffer
	lxi	d,128
	jrz	noblk		; no blocking
	xchg			; shuffle registers
shift:	xchg
	rrc
	jrnc	sh1
	dad	d		; bump buffer address
sh1:	xchg
	dad	h
	ani	07fh		; zero high bit
	djnz	shift
	xchg			; HL=buffer addr
noblk:	sta	sekhst
	shld	sekbuf
	lxi	h,hstact	; buffer active flag
	mov	a,m
	mvi	m,1		; set buffer active
	ora	a		; was it already?
	jrz	filhst		; fill buffer if not
	lda	sekdsk
	lxi	h,hstdsk	; same disk ?
	cmp	m
	jrnz	nomatch
	lda	sektrk
	lxi	h,hsttrk	; same track ?
	cmp	m
	jrnz	nomatch
	lda	sekhst		; same buffer ?
	lxi	h,hstsec
	cmp	m
	jrz	match
;
nomatch:
	lda	hstwrt		; buffer changed?
	ora	a
	cnz	writehst	; clear buffer
;
filhst:	lda	sekdsk
	sta	hstdsk
	lhld	sektrk
	shld	hsttrk
	lda	sekhst
	sta	hstsec
	lda	rsflag		; need to read ?
	ora	a
	cnz	readhst		; yes
	xra	a
	sta	hstwrt		; no pending write
;
match:	lhld	dmaadr
	xchg
	lhld	sekbuf
	lda	readop		; which way to move ?
	ora	a
	jrnz	rwmove		; skip if read
	mvi	a,1
	sta	hstwrt		; mark buffer changed
	xchg			; hl=dma  de=buffer
;
rwmove:	lxi	b,128		; byte count
	ldir			; block move
	lda	wrtype		; write type
	cpi	1		; to directory ?
	jrnz	exit		; done
	lda	erflag		; check for errors
	ora	a
	jrnz	exit		; don't write dir if so
	xra	a
	sta	hstwrt		; show buffer written
	call	writehst	; write buffer
exit:	lda	erflag
	ret
;
;	Disk read.  Call CP/M 3.0 BIOS to fill the buffer
; 	with one physical sector.
;
readhst:
	call	rw$init		; init CP/M 3.0 BIOS
	mvi	a,13		; read function number
	call	xbios		; read sector
	sta	erflag
	ret
;
;	Disk write.  Call CP/M 3.0 BIOS to write one
;	physical sector from buffer.
;
writehst:
	call	rw$init		; init CP/M 3.0 BIOS
	mvi	a,14		; write function number
	call	xbios		; write sector
	sta	erflag
	ret
;
;	Translate sector.  Set CP/M 3.0 track, sector,
;	DMA buffer and DMA bank.
;
rw$init:
	lda	hstsec		; physical sector number
	mov	l,a
	mvi	h,0
	shld	bcreg		; sector number in BC
	lhld	xlat		; address of xlat table
	shld	dereg		; xlat address in DE
	mvi	a,16		; sectrn function number
	call	xbios		; get skewed sector number
	mov	a,l
	sta	actsec		; actual sector
	shld	bcreg		; sector number in BC
	mvi	a,11		; setsec function number
	call	xbios		; set CP/M 3.0 sector
	lhld	hsttrk		; physical track number
	shld	bcreg		; track number in BC
	mvi	a,10		; settrk function number
	call	xbios
	lxi	h,hstbuf	; sector buffer
	shld	bcreg		; buffer address in BC
	mvi	a,12		; setdma function number
	call	xbios
	mvi	a,1		; DMA bank number
	sta	areg		; bank number in A
	mvi	a,28		; setbnk function number
	call	xbios		; set DMA bank
	ret
;
;	Print message at HL until null.
;
prmsg:	mov	a,m
	ora 	a
	rz
	mov	c,m
	push	h
	call	xconout
	pop	h
	inx	h
	jmp	prmsg
;
;	disk i/o buffer
;
hstbuf:	ds	max$sector$size
;
;	variable storage area
;
sekdsk:	ds	1		; logical disk number
sektrk:	ds	2		; logical track number
seksec:	ds	1		; logical sector number
;
hstdsk:	ds	1		; physical disk number
hsttrk:	ds	2		; physical track number
hstsec:	ds	1		; physical sector number
;
actsec:	ds	1		; skewed physical sector
sekhst:	ds	1		; temp physical sector
hstact:	ds	1		; buffer active flag
hstwrt:	ds	1		; buffer changed flag
;
unacnt:	ds	1		; unallocated sector count
unadsk:	ds	1		; unalloc disk number
unatrk:	ds	2		; unalloc track number
unasec:	ds	1		; unalloc sector number
sekbuf:	ds	2		; logical sector address in buffer
;
spt:	ds	1		; cpm sectors per track
xlat:	ds	2		; xlat address
bsm:	ds	1		; block shift mask
psh:	ds	1		; physical shift factor
;
erflag:	ds	1		; error reporting
rsflag:	ds	1		; force sector read
readop:	ds	1		; 1 if read operation
rwflag:	ds	1		; physical read flag
wrtype:	ds	1		; write operation type
dmaadr:	ds	2		; last dma address
oldaddr:ds	2		; address of old BIOS
;
	end
