	title	'Basic & Extended I/O Systems'
	maclib	diskdef
;
; bios for micro-2 computer
;
;
false	equ	0
true	equ	not false
;
debug	equ	false
;
MHz4	equ	true

	if	MHz4
dlycnst	equ	086h
	else
dlycnst	equ	054h
	endif
;
	org	0000h

pdisp	equ	$-3
xdos	equ	pdisp-3
;
;	jump vector for individual subroutines
	jmp	coldstart	;cold start
wboot:
	jmp	warmstart	;warm start
	jmp	const		;console status
	jmp	conin		;console character in
	jmp	conout		;console character out
	jmp	list		;list character out
	jmp	rtnempty	;punch not implemented
	jmp	rtnempty	;reader not implemented
	jmp	home		;move head to home
	jmp	seldsk		;select disk
	jmp	settrk		;set track number
	jmp	setsec		;set sector number
	jmp	setdma		;set dma address
	jmp	read		;read disk
	jmp	write		;write disk
	jmp	pollpt		;list status
	jmp	sectran		;sector translate

	jmp	selmemory	; select memory
	jmp	polldevice	; poll device
	jmp	startclock	; start clock
	jmp	stopclock	; stop clock
	jmp	exitregion	; exit region
	jmp	maxconsole	; maximum console number
	jmp	systeminit	; system initialization
	db	0		; force use of internal dispatch @ idle
;	jmp	idle		; idle procedure
;
coldstart:
warmstart:
	mvi	c,0
	jmp	xdos		; system reset, terminate process
;
;
;I/O handlers
;
;
;  MP/M 1.0   Console Bios
;
;
nmbcns	equ	3	; number of consoles

poll	equ	131	; XDOS poll function
makeque	equ	134	; XDOS make queue function
readque	equ	137	; XDOS read queue function
writeque equ	139	; XDOS write queue function
xdelay	equ	141	; XDOS delay function
create	equ	144	; XDOS create process function

pllpt	equ	0	; poll printer
plco0	equ	1	; poll console out #0
plco2	equ	2	; poll console out #1
plco3	equ	3	; poll console out #2 (Port 3)
plci3	equ	4	; poll console in #2 (Port 3)
	if	debug
plci0	equ	5	; poll console in #0
	endif

;
const:			; Console Status
	call	ptbljmp	; compute and jump to hndlr
	dw	pt0st	; console #0 status routine
	dw	pt2st	; console #1 (Port 2) status rt
	dw	pt3st	; console #2 (Port 3) status rt

conin:			; Console Input
	call	ptbljmp	; compute and jump to hndlr
	dw	pt0in	; console #0 input
	dw	pt2in	; console #1 (Port 2) input
	dw	pt3in	; console #2 (Port 3) input

conout:			; Console Output
	call	ptbljmp	; compute and jump to hndlr
	dw	pt0out	; console #0 output
	dw	pt2out	; console #1 (Port 2) output
	dw	pt3out	; console #2 (Port 3) output

;
ptbljmp:		; compute and jump to handler
			; d = console #
			; do not destroy d !
	mov	a,d
	cpi	nmbcns
	jc	tbljmp
	pop	psw	; throw away table address
rtnempty:
	xra	a
	ret
tbljmp:			; compute and jump to handler
			; a = table index
	add	a	; double table index for adr offst
	pop	h	; return adr points to jump tbl
	mov	e,a
	mvi	d,0
	dad	d	; add table index * 2 to tbl base
	mov	e,m	; get handler address
	inx	h
	mov	d,m
	xchg
	pchl		; jump to computed cns handler

;
; ASCII Character Equates
;
uline	equ	5fh
rubout	equ	7fh
space	equ	20h
backsp	equ	8h
altrub	equ	uline
;
; Input / Output Port Address Equates
;
data0	equ	40h
sts0	equ	data0+1
cd0	equ	sts0
data1	equ	48h
sts1	equ	data1+1
cd1	equ	sts1
data2	equ	50h
sts2	equ	data2+1
cd2	equ	sts2
data3	equ	58h
sts3	equ	data3+1
cd3	equ	sts3
;
; Poll Console #0 Input
;
	if	debug
polci0:
pt0st:
	in	sts0
	ani	2
	rz
	mvi	a,0ffh
	ret
;
pt0in:
	mvi	c,poll
	mvi	e,plci0
	call	xdos
	in	data0
	ani	7fh
	ret
;
	else
pt0st:
			; return 0ffh if ready,
			;        000h if not
	lda	c0inmsgcnt
	ora	a
	rz
	mvi	a,0ffh
	ret
;
; Console #0 Input
;
c0inpd:
	dw	c2inpd	; pl
	db	0	; status
	db	32	; priority
	dw	c0instk+38 ; stkptr
	db	'c0in    '  ; name
	db	0	; console
	db	0ffh	; memseg
	ds	25

c0instk:
	dw	0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h
	db	'FACBEDLHxiyifacbedlh'
	dw	c0inp	; starting address

c0inq:
	dw	0	; ql
	db	'c0inque ' ; name
	dw	1	; msglen
	dw	4	; nmbmsgs
	ds	8
c0inmsgcnt:
	ds	2	; msgcnt
	ds	4	; buffer

c0inqcb:
	dw	c0inq	; pointer
	dw	ch0in ; msgadr
ch0in:
	db	0

c0inuqcb:
	dw	c0inq	; pointer
	dw	char0in ; msgadr
char0in:
	db	0

c0inp:
	mvi	c,makeque
	lxi	d,c0inq
	call	xdos	; make the c0inq

c0inloop:
	mvi	c,flagwait
	mvi	e,6
	call	xdos	; wait for c0 in intr flag
	mvi	c,writeque
	lxi	d,c0inqcb
	call	xdos	; write c0in queue
	jmp	c0inloop


pt0in:
			; return character in reg A
	mvi	c,readque
	lxi	d,c0inuqcb
	call	xdos		; read from c0 in queue
	lda	char0in		; get character
	ani	7fh		; strip parity bit
	ret
;
	endif
;
; Console #0 Output
;
pt0out:
			; Reg C = character to output
	in	sts0
	ani	01h
	jnz	tx0rdy
	push	b
	mvi	c,poll
	mvi	e,plco0
	call	xdos	; poll console #0 output
	pop	b
tx0rdy:
	mov	a,c
	out	data0
	ret
;
; poll console #0 output
;
polco0:
	in	sts0
	ani	01h
	rz
	mvi	a,0ffh
	ret
;
;
; Line Printer Driver:  TI 810 Serial Printer
;			TTY Model 40
;
initflag:
	db	0	; printer initialization flag

list:			; List Output
pt1out:
			; Reg c = Character to print
	lda	initflag
	ora	a
	jnz	pt1xx
	mvi	a,27h
	out	49h		; TTY Model 40 init
	sta	initflag
pt1xx:
	in	sts1
	ani	01h
	jnz	tx1rdy
	push	b
	mvi	c,poll
	mvi	e,pllpt
	call	xdos		; poll printer output
	pop	b
tx1rdy:
	mov	a,c		; char to register a
	out	data1
	ret
;
; Poll Printer Output
;
pollpt:
			; return 0ffh if ready,
			;        000h if not
	in	sts1
	ani	01h
	rz
	mvi	a,0ffh
	ret
;
; Poll Console #1 (Port 2) Input
;
pt2st:
			; return 0ffh if ready,
			;        000h if not
	lda	c2inmsgcnt
	ora	a
	rz
	mvi	a,0ffh
	ret
;
; Console #1 (Port 2) Input
;
c2inpd:
	dw	0	; pl
	db	0	; status
	db	34	; priority
	dw	c2instk+38 ; stkptr
	db	'c2in    '  ; name
	db	2	; console
	db	0ffh	; memseg
	ds	25

c2instk:
	dw	0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h
	db	'FACBEDLHxiyifacbedlh'
	dw	c2inp	; starting address

c2inq:
	dw	0	; ql
	db	'c2inque ' ; name
	dw	1	; msglen
	dw	4	; nmbmsgs
	ds	8
c2inmsgcnt:
	ds	2	; msgcnt
	ds	4	; buffer

c2inqcb:
	dw	c2inq	; pointer
	dw	ch2in ; msgadr
ch2in:
	db	0

c2inuqcb:
	dw	c2inq	; pointer
	dw	char2in ; msgadr
char2in:
	db	0

c2inp:
	mvi	c,makeque
	lxi	d,c2inq
	call	xdos	; make the c2inq

c2inloop:
	mvi	c,flagwait
	mvi	e,8
	call	xdos	; wait for c2 in intr flag
	mvi	c,writeque
	lxi	d,c2inqcb
	call	xdos	; write c2in queue
	jmp	c2inloop


pt2in:
			; return character in reg A
	mvi	c,readque
	lxi	d,c2inuqcb
	call	xdos		; read from c2 in queue
	lda	char2in		; get character
	ani	7fh		; strip parity bit
	ret
;
; Console #1 (Port 2) Output
;
pt2out:
			; Reg C = character to output
	in	sts2
	ani	01h
	jnz	tx2rdy
	push	b
	mvi	c,poll
	mvi	e,plco2
	call	xdos	; poll console #1 output
	pop	b
tx2rdy:
	mov	a,c
	out	data2
	ret
;
; poll console #1 output
;
polco2:
	in	sts2
	ani	01h
	rz
	mvi	a,0ffh
	ret
;
; Poll Console #2 (Port 3) Input
;
polci3:
pt3st:			; return 0ffh if ready,
			;        000h if not
	in	sts3
	ani	2
	rz
	mvi	a,0ffh
	ret
;
; Console #2 (Port 3) Input
;
pt3in:			; return character in reg A
	mvi	c,poll
	mvi	e,plci3
	call	xdos		; poll console #0 input
	in	data3		; read character
	ani	7fh		; strip parity bit
	ret
;
; Console #2 (Port 3) Output
;
pt3out:			; Reg C = character to output
	in	sts3
	ani	01h
	jnz	tx3rdy
	push	b
	mvi	c,poll
	mvi	e,plco3
	call	xdos		; poll console #2 (Port 3) output
	pop	b
tx3rdy:
	mov	a,c
	out	data3		; transmit character
	ret
;
; Poll Console #2 (Port 3) Output
;
polco3:
			; return 0ffh if ready,
			;        000h if not
	in	sts3
	ani	01h
	rz
	mvi	a,0ffh
	ret
;
;
;  MP/M 1.0   Xios
;
;
polldevice:
			; Reg C = device # to be polled
			; return 0ffh if ready,
			;        000h if not
	mov	a,c
	cpi	nmbdev
	jc	devok
	mvi	a,nmbdev; if dev # >= nmbdev,
			; set to nmbdev
devok:
	call	tbljmp	; jump to dev poll code

devtbl:
	dw	pollpt	; poll printer output
	dw	polco0	; poll console #0 output
	dw	polco2	; poll console #1 output
	dw	polco3	; poll console #2 output
	dw	polci3	; poll console #2 input
	if	debug
	dw	polci0	; poll console #0 input
	endif
nmbdev	equ	($-devtbl)/2	; number of devices to poll
	dw	rtnempty; bad device handler
;

; Select / Protect Memory
;
selmemory:
			; Reg BC = adr of mem descriptor
			; BC ->  base   1 byte,
			;        size   1 byte,
			;        attrib 1 byte,
			;        bank   1 byte.
; this hardware does not have memory protection or
;  bank switching
	ret
;
; Start Clock
;
startclock:
			; will cause flag #1 to be set
			;  at each system time unit tick
	mvi	a,0ffh
	sta	tickn
	ret
;
; Stop Clock
;
stopclock:
			; will stop flag #1 setting at
			;  system time unit tick
	xra	a
	sta	tickn
	ret
;
; Exit Region
;
exitregion:
			; EI if not preempted or in dispatcher
	lda	preemp
	ora	a
	rnz
	ei
	ret
;
; Maximum Console Number
;
maxconsole:
	mvi	a,nmbcns
	ret
;
; System Initialization
;
systeminit:
;
;  This is the place to insert code to initialize
;  the time of day clock, if it is desired on each
;  booting of the system.
;
	mvi	a,0c3h
	sta	0038h
	lxi	h,inthnd
	shld	0039h		; JMP INTHND at 0038H

	mvi	c,create
	if	debug
	lxi	d,c2inpd
	else
	lxi	d,c0inpd
	endif
	call	xdos

	lda	intmsk
	out	60h		; init interrupt mask

	db	0edh,056h	; Interrupt Mode 1
				; ** Z80 Instruction **
	ei
	call	home
	mvi	c,flagwait
	mvi	e,5
	jmp	xdos		; clear first disk interrupt
;	ret			;   & return

;
; Idle procedure
;
;idle:
;	ret

;	-or-

;	ei
;	hlt
;	ret			; for full interrupt system

;
;  MP/M 1.0   Interrupt Handlers
;

flagwait equ	132
flagset	equ	133
dsptch	equ	142

inthnd:
			; Interrupt handler entry point
			;  All interrupts gen a RST 7
			;  Location 0038H contains a jmp
			;  to INTHND.
	shld	svdhl
	pop	h
	shld	svdret
	push	psw
	lxi	h,0
	dad	sp
	shld	svdsp		; save users stk ptr
	lxi	sp,lstintstk	; lcl stk for intr hndl
	push	d
	push	b

	mvi	a,0ffh
	sta	preemp	; set preempted flag

	in	60h		; read interrupt mask
	ani	01000000b	; test & jump if clk int
	jnz	clk60hz
;
	in	stat		; read disk status port
	ani	08h
	jnz	diskintr

	if	not debug
	in	sts0
	ani	2
	jnz	con0in
	endif

	in	sts2
	ani	2
	jnz	con2in

;	...			; test/handle other ints
;
	jmp	intdone

diskintr:
	xra	a
	out	cmd1		; reset disk interrupt
	mvi	e,5
	jmp	concmn		; set flag #5

	if	not debug
con0in:
	in	data0
	sta	ch0in
	mvi	e,6
	jmp	concmn		; set flag #6
	endif

con2in:
	in	data2
	sta	ch2in
	mvi	e,8
;	jmp	concmn		; set flag #8

concmn:
	mvi	c,flagset
	call	xdos
	jmp	intdone

clk60hz:
				; 60 Hz clock interrupt
	lda	tickn
	ora	a		; test tickn, indicates
				;  delayed process(es)
	jz	notickn
	mvi	c,flagset
	mvi	e,1
	call	xdos		; set flag #1 each tick
notickn:
	lxi	h,cnt60
	dcr	m		; dec 60 tick cntr
	jnz	not1sec
	mvi	m,60
	mvi	c,flagset
	mvi	e,2
	call	xdos		; set flag #2 @ 1 sec
not1sec:
	xra	a
	out	60h
	lda	intmsk
	out	60h		; ack clock interrupt
;	jmp	intdone
;
;	...
; Other interrupt handlers
;	...
;
intdone:
	xra	a
	sta	preemp	; clear preempted flag
	pop	b
	pop	d
	lhld	svdsp
	sphl			; restore stk ptr
	pop	psw
	lhld	svdret
	push	h
	lhld	svdhl
; The following dispatch call will force round robin
;  scheduling of processes executing at the same priority
;  each 1/60th of a second.
; Note: Interrupts are not enabled until the dispatcher
;  resumes the next process.  This prevents interrupt
;  over-run of the stacks when stuck or high frequency
;  interrupts are encountered.
	jmp	pdisp		; MP/M dispatch
;
;
;	Disk I/O Drivers
;
; Disk Port Equates
;
cmd1	equ	80h
stat	equ	80h
haddr	equ	81h
laddr	equ	82h
cmd2	equ	83h
;
;
home:	;move to the track o0 position of current drive
	call	headload
; h,l point to word with track for selected disk
homel:
	mvi	m,00	;set current track ptr back to 0
	in	stat	;read fdc status
	ani	4	;test track 0 bit
	rz		;return if at 0
	stc		;direction=out
	call	step	;step one track
	jmp	homel	;loop
;
seldsk:
	;drive number in c
	lxi	h,0	;0000 in hl produces select error
	mov	a,c	;a is disk number 0 ... ndisks-1
	cpi	ndisks	;less than ndisks?
	rnc		;return with HL = 0000 if not
;make sure dummy is 0 (for use in double add to h,l)
	xra	a
	sta	dummy
	mov	a,c
	ani	07h	;get only disk select bits
	sta	diskno
	mov	c,a
;set up the second command port
	lda	port
	ani	0f0h	;clear out old disk select bits
	ora	c	;put in new disk select bits
	ori	08h	; force double density
	sta	port
;	proper disk number, return dpb element address
	mov	l,c
	dad	h	;*2
	dad	h	;*4
	dad	h	;*8
	dad	h	;*16
	lxi	d,dpbase
	dad	d	;HL=.dpb
	shld	tran	;translate table base
	ret
;
;
;
settrk:	;set track given by register c
	call	headload
;h,l reference correct track indicator according to
;selected disk
	mov	a,c	;desired track
	cmp	m
	rz		;we are already on the track
settkx:
	call	step	;step track-carry has direction
			;step will update trk indicator
	mov	a,c
	cmp	m	;are we where we want to be
	jnz	settkx	;not yet
;have stepped enough
seekrt:
;need 10 msec delay for final step time and head settle time
	mvi	a,20d
;	call	delay
;	ret		;end of settrk routine

;
delay:	;delay for c[A] X .5 milliseconds
	push	b
delay1:
	mvi	c,dlycnst ;constant adjusted to .5 ms loop
delay2:
	dcr	c
	jnz	delay2
	dcr	a
	jnz	delay1
	pop	b
	ret		;end of delay routine

;
setsec:	;set sector given by register c
	inr	c
	mov	a,c
	sta	sector
	ret
;
sectran:
	;sector number in c
	;translate logical to physical sector
	lhld	tran	;hl=..translate
	mov	e,m	;E=low(.translate)
	inx	h
	mov	d,m	;DE=.translate
	mov	a,e	;zero?
	ora	d	;00 or 00 = 00
	mvi	h,0
	mov	l,c	;HL = untranslated sector
	rz		;skip if so
	xchg
	mov	b,d	;BC=00ss
	dad	b	;HL=.translate(sector)
	mov	l,m
	mov	h,d	;HL=translate(sector)
	ret
;
setdma:	;set dma address given by registers b and c
	mov	l,c	;low order address
	mov	h,b	;high order address
	shld	dmaad	;save the address
	ret
;
;
read:	;perform read operation.
	;this is similar to write, so set up read
	; command and use common code in write
	mvi	b,040h	;set read flag
	jmp	waitio	;to perform the actual I/O
;
write:	;perform a write operation
	mvi	b,080h	;set write command
;
waitio:
;enter here from read and write to perform the actual
; I/O  operation.  return a 00h in register a if the
; operation completes properly, and 01h if an error
; occurs during the read or write
;
;in this case, the disk number saved in 'diskno' 
;			the track number in 'track' 
;			the sector number in 'sector' 
;			the dma address in 'dmaad' 
			;b still has r/w flag
	mvi	a,10d	;set error count
	sta	errors	;retry some failures 10 times
			;before giving up
tryagn:
	push	b
	call	headload
;h,l point to track byte for selected disk
	pop	b
	mov	c,m
; decide whether to allow disk write precompenstation
	mvi	a,39d	;inhibit precomp on trks 0-39
	cmp	c
	jc	allowit
;inhibit precomp
	mvi	a,10h
	ora	b
	mov	b,a	;goes out on the same port
			; as read/write
allowit:
	lhld	dmaad	;get buffer address
	push	b	;b has r/w code   c has track
	dcx	h	;save and replace 3 bytes below
			;buf with trk,sctr,adr mark
	mov	e,m
;figure correct address mark

	lda	port
	ani	08h
	mvi	a,0fbh
	jz	sin
	ani	0fh	;was double 
			;0bh is double density 
			;0fbh is single density
sin:
	mov	m,a
;fill in sector
	dcx	h
	mov	d,m
	lda	sector	;note that invalid sector number
			;will result in head unloaded
			;error, so dont check
	mov	m,a
;fill in track
	dcx	h
	pop	b
	mov	a,c
	mov	c,m
	mov	m,a
	mov	a,h	;set up fdc dma address
	out	haddr	;high byte
	mov	a,l
	out	laddr	;low byte
	mov	a,b	;get r/w flag
	out	cmd1	;start disk read/write

rwwait:
	push	b
	push	d
	push	h

	mvi	c,flagwait
	mvi	e,5
	call	xdos		; wait for disk intrpt flag

	pop	h
	pop	d
	pop	b
	mov	m,c	;restore 3 bytes below buf
	inx	h
	mov	m,d
	inx	h
	mov	m,e
	in	stat	;test for errors
	ani	0f0h
	rz		;a will be 0 if no errors

; error from disk
	push	psw	;save error condition
;check for 10 errors
	lxi	h,errors
	dcr	m
	jnz	redo	;not ten yet.  do a retry
;we have too many errors. print out hex number for last
;received error type. cpm will print perm error message.
	pop	psw	;get code
;set error return for operating system
	mvi	a,1
	ret
redo:
;b still has read/write flag
	pop	psw	;get error code
	ani	0e0h	;retry if not track error
	jnz	tryagn	;
;was a track error so need to reseek
	push	b	;save	read/write indicator
;figure out the desired track
	lxi	d,track
	lhld	diskno	;selected disk
	dad	d	;point to correct trk indicator
	mov	a,m	;desired track
	push	psw	;save it
	call	home
	pop	psw
	mov	c,a
	call	settrk
	pop	b	;get read/write indicator
	jmp	tryagn
;
;
;
step:			;step head out towards zero
			;if carry is set; else
			;step in
; h,l point to correct track indicator word
	jc	outx
	inr	m	;increment current track byte
	mvi	a,04h	;set direction = in
dostep:
	ori	2
	out	cmd1	;pulse step bit
	ani	0fdh
	out	cmd1	;turn off pulse
;the fdc-2 had a stepp ready line. the fdc-3 relies on
;software time out
	mvi	a,16d	;delay 8 ms
	jmp	delay
;	ret
;
outx:
	dcr	m	;update track byte
	xra	a
	jmp	dostep
;
headload:
;select and load the head on the correct drive
	lxi	h,prtout	;old slect info
	mov	b,m
	dcx	h	;new select info
	mov	a,m
	inx	h
	mov	m,a

	ori	10h	; enable interrupt

	out	cmd2	;select the drive
	ani	0efh
;set up h.l to point to track byte for selected disk
	lxi	d,track
	lhld	diskno
	dad	d
;now check for needing a 35 ms delay
;if we have changed drives or if the head is unloaded
;we need to wait 35 ms for head settle
	cmp	b	;are we on the same drive
	jnz	needdly
;we are on the same drive
;is the head loaded?
	in	stat
	ani	80h
	rz		;already loaded
needdly:
	xra	a
	out	cmd1	;load the head
	mvi	a,70d
	jmp	delay
;	ret

;
; BIOS Data Segment
;
cnt60:	db	60	; 60 tick cntr = 1 sec
intstk:			; local intrpt stk
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
lstintstk:
svdhl:	dw	0	; saved Regs HL during int hndl
svdsp:	dw	0	; saved SP during int hndl
svdret:	dw	0	; saved return during int hndl
tickn:	db	0	; ticking boolean,true = delayed
	if	debug
intmsk:	db	44h	; intrpt msk, enables clk intrpt, & con2
	else
intmsk:	db	54h	; intrpt msk, enables clk intrpt, & con0/2
	endif
preemp:	db	0	; preempted boolean
;
scrat:			; start of scratch area
track:	db	0	; current trk on drive 0
trak1:	db	0	; current trk on drive 1
trak2:	db	0	
trak3:	db	0
sector:	db	0	; currently selected sctr
dmaad:	dw	0	; current dma address
diskno:	db	0	; current disk number
dummy:	db	0	; must be 0 for dbl add
errors:	db	0
port:	db	0
prtout:	db	0
dnsty:	db	0
;
	disks	2
bpb	equ	2*1024	;bytes per block
rpb	equ	bpb/128	;records per block
maxb	equ	255	;max block number
	diskdef	0,1,58,,bpb,maxb+1,128,128,2,0
	diskdef	1,0
;
tran:	ds	2
;
	endef

	db	0	;force out last byte in hex file

	end
