;-------------------------------------------------------------------------
;  MFAMON.ASM - 2K ROM monitor for the MFA German modular computer.
;     Includes a BOOT command to load the PROM-less version of
;     CP/M 2.2 that supports 720K disks.
; 
;     This monitor is based on the 2.0C monitor from Vector Graphic.
;     The original version has been updated to use the 8251 serial
;     ports in the MFA computer and a few commands have been added
;     and/or modified. 
;
;     This code resides in a PROM at address zero and when the computer
;     is reset, the PROM copies the monitor code into RAM at BASEADR
;     and jumps to it. BASEADR is typically in high RAM (max address
;     F800h) and must be on a 2K boundary.
;
;  Version     Date	Author/Notes
;  -------  ----------	--------------------------------------------------
;    1.4    02/03/2018  Robert Flossman, Mike Douglas
;			Add startup MFA banner
;
;    1.3    01/19/2019	Mike Douglas
;			Increase motor start time to one second, add
;			head settle time of 25ms after restore.
;
;    1.2    01/18/2019	Robert Flossmann
;			Add Help Menu
;
;    1.1    01/08/2019  Mike Douglas
;			Original
;
;  Command Summary:
;
;	B boot disk from track 0, sector 1 of 1st drive
;	C SSSS FFFF CCCC compare blocks
;	D SSSS FFFF dump in hex and ASCII
;	E SSSS FFFF DDDD exchange block
;	F SSSS FFFF DD DD find two byte sequence
;	G SSSS go to and execute
;	H Help menu
;	I PP input from I/O port
;	J SSSS go to and execute (G)
;	K SSSS FFFF DD fill with "K"onstant
;	L P load Intel hex file from serial port 0 or 1
;	M SSSS FFFF DDDD move block
;	O PP DD output to port
;	P LLLL program memory (deposit values)
;	Q SSSS FFFF compute checksum
;	S SSSS FFFF DD search for single byte sequence
;	T SSSS FFFF test memory
;
;-------------------------------------------------------------------------

; Set the location in RAM where the monitor should be loaded

BASEADR	equ	0F800h		;run-time location of the monitor

; 8251 equates

SIO1ST	equ	0A1h		;console status port
SIO1DT	equ	0A0h		;console data port
SIO2ST	equ	091h		;2nd port status
SIO2DT	equ	090h		;2nd port data
TBE	equ	1		;transmit buffer entry
RDA	equ	2		;receive data available

; Misc Equates

MOVRLEN	equ	22		;length of the code that moves PROM to RAM
ORGADDR	equ	BASEADR-MOVRLEN	;code is ORG'd to this address

CR	equ	13		;ASCII carriage return
LF	equ	10		;ASCII line feed
CTRLC	equ	3		;ASCII control-c
ESC	equ	27		;ASCII ESCAPE


;--------------------------------------------------------------------------
;  Code starting at address zero in PROM copies the monitor code to
;     RAM at BASEADR and then jumps to it.
;--------------------------------------------------------------------------
	org	ORGADDR

	lxi	h,BASEADR-ORGADDR   ;HL->start of monitor code in PROM
	lxi	d,BASEADR	    ;DE->destination address in RAM
	lxi	b,ENDADDR-BASEADR   ;BC=number of bytes to move

movLoop	mov	a,m		;copy from PROM to RAM
	stax	d
	inx	h
	inx	d
	dcx	b		;decrement and test byte count
	mov	a,b	
	ora	c
	jnz	movLoop-ORGADDR

	jmp	BASEADR		;execute the monitor in RAM


;---------------------------------------------------------
;  This is the start of the monitor code
;---------------------------------------------------------
	lxi	sp,STACK	;init stack pointer
	lda	0FF00h		;turn off PROM in case the run address didn't

	xra	a		;initialize two 8251 serial ports
	out	SIO1ST		;1st, three zeros to satisfy possible
	out	SIO2ST		;   sync mode power-up

	out	SIO1ST
	out	SIO2ST

	out	SIO1ST
	out	SIO2ST

	mvi	a,40h		;reset 8251's
	out	SIO1ST
	out	SIO2ST

	mvi	a,4Eh		;async,	8N1
	out	SIO1ST
	out	SIO2ST
	
	mvi	a,37h		;enable xmit, receive
	out	SIO1ST
	out	SIO2ST

	call	banner		;display MFA banner

	call	dspMsg		;display startup message
	db	CR,LF,'MFAMON ver 1.4',CR,LF,LF
	db	'Type H (Help) to list commands',CR,LF,0

; start - command processing loop

start	lxi	sp,STACK	;re-init stack pointer
	lxi	h,start		;RET's go back to start
	push	h

	call	crlf		;display '*' prompt after CR/LF
	mvi	a,'*'
	call	ptcn

	call	getCon		;read command from keyboard
	ani	05FH		;lower case to upper case
	sui	'B'		;zero index first entry
	rc			;too small
	cpi	'U'-'B'
	rnc			;too large

	lxi	h,cmdTbl	;HL->cmdTbl
	add	a		;2 bytes per entry
	add	l		;compute address in cmdTbl
	mov	l,a		;(assumes won't cross page boundary)

	mov	e,m		;e=lsb of jump address
	inx	h
	mov	d,m		;d=high byte of jump address
	xchg
	pchl			;away we go

; Command Table

cmdTbl	dw	doboot		;B boot disk
	dw	compr		;C SSSS FFFF CCCC compare blocks
	dw	disp		;D SSSS FFFF dump in hex
	dw	exchg		;E SSSS FFFF DDDD exchange block
	dw	srch2		;F SSSS FFFF DD DD two byte search
	dw	exec		;G SSSS go to and execute
	dw	help		;H print command table
	dw	pinpt		;I PP input from I/O port
	dw	exec		;J SSSS jump to and execute (G)
	dw	fill		;K SSSS FFFF DD fill RAM with "k"onstant
	dw	loadhex		;L P load Intel hex file from port
	dw	moveb		;M SSSS FFFF DDDD move block
	dw	start		;N (nothing)
	dw	poutp		;O PP DD output to port
	dw	pgm		;P LLLL program memory
	dw	chksum		;Q SSSS FFFF compute checksum
	dw	start		;R (nothing)
	dw	srch1		;S SSSS FFFF DD search for single byte
	dw	tmem		;T SSSS FFFF test memory

;--------------------------------------------------------------------------
; help
;--------------------------------------------------------------------------
help	call	dspMsg
	db	'HELP',CR,LF,0
	call	dspMsg
	db	CR
	db	'(B)oot CP/M',CR,LF
	db	'(C)ompare blocks',CR,LF
	db	'(D)ump in hex',CR,LF
	db	'(E)xchange block',CR,LF
	db	'(F)ind, two byte search',CR,LF
	db	'(G)o to and execute',CR,LF
	db	'(H)elp',CR,LF
	db	'(I)nput from I/O port',CR,LF
	db	'(J)ump to and execute',CR,LF
	db	'(K)fill RAM with "K"onstant',CR,LF
	db	'(L)oad Intel hex file from port',CR,LF
	db	'(M)ove block',CR,LF
	db	'(O)utput to I/O port',CR,LF
	db	'(P)rogram memory',CR,LF
	db	'(Q)compute checksum',CR,LF
	db	'(S)earch for single byte',CR,LF
	db	'(T)est memory',CR,LF,0

	ret
	
;--------------------------------------------------------------------------
; exec (G or J) - execute the program at the address
;--------------------------------------------------------------------------
exec	call	dspMsg
	db	'GOTO',0

	call	ahex		;read address from keyboard
	xchg
	pchl

;--------------------------------------------------------------------------
; doBoot (B) - boot floppy disk by loading track 0, sector 1 from
;    drive 1 into RAM and jumping to it.
;--------------------------------------------------------------------------
doBoot	call	dspMsg
	db	'BOOT',0

	jmp	mfaBoot		;at end of program

;--------------------------------------------------------------------------
; chksum (Q) - compute checksum
;--------------------------------------------------------------------------
chksum	call	dspMsg
	db	'CSUM',0

	call	tahex
	mvi	b,0		;start checksum = 0

csloop	mov	a,m		;get data from memory
	add	b		;add to checksum
	mov	b,a
	call	bmp
	jnz	csloop		;repeat loop

	mov	a,b		;a=checksum
	jmp	pt2		;print checksum and exit

;--------------------------------------------------------------------------
; tmem (T) - memory test routine
;--------------------------------------------------------------------------
tmem	call	dspMsg
	db	'TEST',0

	call	tahex		;read addresses
	lxi	b,05a5ah	;init b,c

cycl	mvi	a,'.'		;display '.' before each cycle
	call	ptcn
	call	rndm
	push	b		;keep all registers
	push	h
	push	d

tlop	mov	a,h		;don't overwrite self (2K block)
	ani	0F8h
	cpi	(BASEADR shr 8) and 0F8h
	jz	skipWr		;skip the write
	call	rndm
	mov	m,b		;write in memory
skipWr	call	bmp
	jnz	tlop		;repeat loop

	pop	d		
	pop	h		;restore original
	pop	b		;values
	push	h
	push	d

rlop	mov	a,h		;skip test if accessing self (2K block)
	ani	0F8h
	cpi	(BASEADR shr 8) and 0F8h
	jz	skipRd		;skip the read

	call	rndm		;generate new sequence
	mov	a,m		;read memory
	cmp	b		;compare memory
	cnz	err		;call error routine
skipRd	call	bmp
	jnz	rlop

	pop	d
	pop	h
	call	pause
	jmp	cycl

; rndm - this routine generates random numbers

rndm	mov	a,b		;look at b
	ani	0b4h		;mask bits
	ana	a		;clear carry
	jpe	peve		;jump if even
	stc
peve	mov	a,c		;look at c
	ral			;rotate carry in
	mov	c,a		;restore c
	mov	a,b		;look at b
	ral			;rotate carry in
	mov	b,a		;restore b
	ret			;return with new b,c

;--------------------------------------------------------------------------
; disp (D) - display memory contents
;--------------------------------------------------------------------------
disp	call	dspMsg
	db	'DUMP',0

	call	tahex		;read addresses

dmpLine	push	h		;save address at start of line
	mvi	c,16		;16 locations per line
	call	ptad		;print current address

; dump line in hex

dmpHex	mov	a,m		;a=byte to display
	call	pt2		;display it
	call	spce
	inx	h	
	dcr	c		;decrement line byte count
	jnz	dmpHex		;loop until 16 bytes done

; dump line in ASCII

	call	spce
	pop	h		;hl->start of line
	mvi	c,16		;16 locations per line

dmpAsc	mov	a,m		;a=byte to display
	cpi	7Fh		;test if >= 7Fh
	jnc	dspDot		;non printable, show '.'

	cpi	' '		;displayable character?
	jnc	dspAsc		;yes, go display it

dspDot	mvi	a,'.'		;display '.' instead

dspAsc	call	ptcn		;display the character
	call	bmp		;increment hl, possibly de
	dcr	c		;decrement line byte count
	jnz	dmpAsc		;loop until 16 bytes done

	call	bmp		;done?
	rz			;yes
	dcx	h		;undo extra bump of hl
	jmp	dmpLine		;do another line	

;--------------------------------------------------------------------------
; pgm (P) - program memory
;--------------------------------------------------------------------------
pgm	call	dspMsg
	db	'PGM',0

	call	ahex		;read address
	xchg
	call	crlf
		
pglp	mov	a,m		;read memory
	call	pt2		;print 2 digits
	mvi	a,'-'		;load dash
	call	ptcn		;print dash

crig	call	rdcn		;get user input
	cpi	' '		;space
	jz	con2		;skip if space
	cpi	CR		;skip if CR
	jnz	con1
	call	crlf		;print CR,LF
	jmp	crig		;back for more

con1	xchg			;HL->DE
	lxi	h,0		;get 16 bit zero
	mvi	c,2		;count 2 digits
	call	ahexNr		;convert to hex (no read)
	mov	m,e
con2	inx	h
	jmp	pglp

;--------------------------------------------------------------------------
; fill (K) - fill memory with a constant
;--------------------------------------------------------------------------
fill	call	dspMsg
	db	'FILL',0

	call	tahex		;read addresses
	push	h		;start addr on stack
	mvi	c,2		;reading 2 digits
	call	ahe0		;input fill byte
	xchg			;byte to write from e to l
	xthl			;hl=start addr, stack=fill byte
	pop	b		;c=fill byte from stack
		
zloop	mov	m,c		;write into memory
	call	bmp		;compare address, increment h
	rz
	jmp	zloop

;--------------------------------------------------------------------------
; moveb (M) - move a block of memory
; exchg (E) - exhange block of memory
;--------------------------------------------------------------------------
moveb	call	dspMsg
	db	'MOVE',0
	xra	a		;a=0 means "move" command
	jmp	doMove

exchg	call	dspMsg
	db	'EXCH',0
				;a returned <> 0 means "exchange" command
		
doMove	mov	b,a		;save move/exchange flag in b
	call	tahex		;read addresses
	push	h
	call	ahex
	xchg
	xthl			;HL->start, DE->end, stack has dest

mloop	mov	c,m		;c=byte from source
	xthl			;hl->destination

	mov	a,b		;move or exchange?
	ora	a
	jz	nexch		;0 means move only

	mov	a,m		;a=from destination
	xthl			;hl->source
	mov	m,a		;move destination to source
	xthl			;hl->destination

nexch	mov	m,c		;move source to destination
	inx	h		;increment destination
	xthl			;hl->source
	call	bmp		;increment source and compare to end
	jnz	mloop

	pop	h		;remove temp pointer from stack
	ret			;and exit

;--------------------------------------------------------------------------
; compr (C) - compare two blocks of memory
;--------------------------------------------------------------------------
compr	call	dspMsg
	db	'COMP',0

	call	tahex		;read addresses
	push	h		;source start on stack
	call	ahex
	xchg			;de=source end, hl=compare start

vmlop	mov	a,m		;a=compare byte
	inx	h
	xthl			;hl->source byte
	cmp	m		;same?
	mov	b,m		;b=source byte
	cnz	err		;display the error
	call	bmp		;increment pointers
	xthl			;hl->compare byte
	jnz	vmlop

	pop	h		;remove temp pointer from stack
	ret			;and exit

;--------------------------------------------------------------------------
; srch1 (S) - search for one byte
; srch2 (F) - search for two bytes
;--------------------------------------------------------------------------
srch1	call	dspMsg
	db	'FIND1',0
	xra	a		;zero flag means one byte search
	jmp	doSrch

srch2	call	dspMsg
	db	'FIND2',0
				;a returned <> 0 means two byte search

doSrch	push	psw		;save 1/2 byte flag on stack
	call	tahex

	push	h		;save h, getting 1st byte to find
	mvi	c,2		;reading 2 hex digits
	call	ahe0		;
	xchg			;h=code, d=f
	mov	b,l		;put code in b
	pop	h		;restore h

	pop	psw		;a=one/two byte flag
	ora	a		;zero true if one byte search
	push	psw
	jz	cont

	push	h		;save h, getting 2nd byte to find
	mvi	c,2
	call	ahe0
	xchg
	mov	c,l
	pop	h

cont	mov	a,m		;read memory
	cmp	b		;compare to code
	jnz	skp		;skip if no compare

	pop	psw		;a=one/two byte flag
	ora	a		;zero true if one byte serach
	push	psw
	jz	obcp

	inx	h		;two byte search
	mov	a,m
	dcx	h
	cmp	c
	jnz	skp

obcp	inx	h
	mov	a,m		;read next byte
	dcx	h		;decr address
	call	err		;print data found

skp	call	bmp		;check if done
	jnz	cont		;back for more
	pop	psw		;remove flag saved on stack
	ret

;--------------------------------------------------------------------------
; poutp (O) - output data to a port
;--------------------------------------------------------------------------
poutp	call	dspMsg
	db	'OUT',0

	mvi	c,2
	call	ahe0		;port number in e

	mvi	c,2
	call	ahe0		;port to l, data in e

	mov	d,l		;d=port
	lxi	h,tmpCode+2	;HL->RAM to put instructions
	mvi	m,RET		;store RET opcode
	dcx	h
	mov	m,d		;output port for OUT instruction
	dcx	h
	mvi	m,OUT		;OUT opcode
	mov	a,e		;data to output in accumulator
	pchl			;call OUT xx, RET

;--------------------------------------------------------------------------
; pinpt (I) - input data from a port
;--------------------------------------------------------------------------
pinpt	call	dspMsg
	db	'IN',0

	mvi	c,2
	call	ahe0		;port number to e

	lxi	h,tmpCode+2	;HL->RAM to put instructions
	mvi	m,RET		;store RET opcode
	dcx	h
	mov	m,e		;input port for IN instruction
	dcx	h
	mvi	m,IN		;IN opcode
	call	tmpCode		;call the IN instrution
	jmp	pt2

;---------------------------------------------------------------------
; loadHex (L) - load intel hex through serial port 0 or 1
;---------------------------------------------------------------------
loadhex	call	dspMsg
	db	'LOADHEX',0

	mvi	c,1		;read one hex digit
	call	ahe0		;digit is in e
	lxi	h,sioPort	;HL->saved port number to use
	mov	m,e		;sioPort = 0 or 1

	call	dspMsg
	db	CR,LF,'Send hex file now...',0

; rcvLine - receive a hex file line

rcvLine	call	crlf
	mvi	c,0		;clear echo character flag

wtMark	call	getChar		;read next character
	sui	':'		;record marker?
	jnz	wtMark		;no, keep looking

; Have start of new record. Save the byte count and load address.
;   The load address is echoed to the screen so the user can
;   see the file load progress.

	mov	d,a		;init checksum in D to zero

	call	iByte		;input two hex digits (byte count)
	mov	a,e		;test for zero byte count
	ora	a
	jz	flush		;count of 0 means end

	mov	b,e		;B = byte count on line

	inr	c		;set echo flag for address bytes
	call	iByte		;get MSB of address
	mov	h,e		;H = address MSB
	call	iByte		;get LSB of address
	mov	l,e		;L = address LSB
	dcr	c		;clear echo flag

	call	iByte		;ignore/discard record type

; Receive the data bytes of the record and move to memory

data	call	iByte		;read a data byte (2 hex digits)
	mov	m,e		;store in memory
	inx	h
	dcr	b
	jnz	data

; Validate checksum

	call	iByte		;read and add checksum
	jz	rcvLine		;checksum good, receive next line

	call	dspMsg		;display error message
	db	' ** Hex file error **',0
				;fall into flush

; flush - flush rest of file as it comes in until no characters
;    received for about 1/4 second to prevent incoming file
;    data looking like typed monitor commands. Only the console
;    port needs to be flushed. 

flush	in	SIO1DT		;clear possible received char
	lxi	d,9804		;.25s timeout for 51 cycle loop

flshLp	in	SIO1ST		;(10) look for character on console
	ani	RDA		;(7) byte present?
	jnz	flush		;(10) data received, restart

	dcx	d		;(5) decrement timeout
	mov	a,d		;(5)
	ora	e		;(4)
	jnz	flshLp		;(10) loop until zero
	ret			;done

;-----------------------------------------------------------
; iByte	- read two ascii hex bytes and return binary
;    value in e. 
;-----------------------------------------------------------
iByte	call	getChar		;get a character
	call	asc2Bin		;ascii hex digit to binary
	add	a		;put in msn, zero lsn
	add	a
	add	a
	add	a
	mov	e,a		;save byte with MSN in E

; 2nd byte (LSN)

	call	getChar		;get a character
	call	asc2Bin		;ascii hex digit to binary
	add	e		;combine msn and lsn
	mov	e,a		;save in EH
	add	d		;add character to checksum
	mov	d,a
	ret		

;-------------------------------------------------------------
; asc2Bin - ASCII hex digit to binary conversion. Digit
;    passed in a, returned in a. Errors ignored as checksum
;    will eventually kick this out.
;-------------------------------------------------------------
asc2Bin	sui	'0'		;'0' to 0
	cpi	10		;0-9 ?
	rc

	sui	7		;'A-F' to A-F
	ret

;-------------------------------------------------------------
; getChar - read a character from the serial port specified in
;    sioPort. The character is also echoed to the console port
;    if the echo flag (c) is set (non-zero)
;-------------------------------------------------------------
getChar	push	b		;save b,c
	lda	sioPort		;a=pseudo port to use
	ora	a		;port zero?
	jnz	inWait1		;no, use port 1

; in through 1st port (0) 

inWait0	call	cntlc		;test for character from console
	jz	inWait0
	jmp	haveChr

; in through 2nd port (1), check for ctrl-c on console
;    while waiting

inWait1	call	cntlc		;look for ctrl-c on console
	in	SIO2ST		;wait for character on 2nd port
	ani	RDA		;anything received?
	jz	inWait1		;no

	in	SIO2DT		;a=character read

; process new character in a. Echo to console if c is non-zero

haveChr	mov	b,a		;save character in b
	mov	a,c		;echo flag (c) set?
	ora	a
	jz	noEcho		;no echo

	mov	a,b		;a=character to send
	pop	b		;restore b,c
	jmp	ptcn		;display character and exit

noEcho	mov	a,b		;a=byte read
	pop	b		;restore b,c
	ret

;------------------------------------------------------------
; banner - display the startup banner. Done here as a mix
;    of code and strings to save EPROM space.
;------------------------------------------------------------
banner	call	banLine		;print line of ===='s
	lxi	h,logoTbl	;HL->logo table

banLoop	mov	a,m		;A=count of spaces
	ora	a		;end of line? (zero flags EOL)
	jz	banEol		;yes

	cpi	0FFh		;end of table?
	jz	banEnd		;yes

	mov	b,a		;B=number of spaces to print
	mvi	a,' '
	call	banRptA

	inx	h		;bump pointer
	mov	b,m		;B=number of #'s to print
	mvi	a,'#'
	call	banRptA

	inx	h		;bump pointer
	jmp	banLoop

banEol	call	crlf		;CR/LF at end of line
	inx	h		;bump pointer
	jmp	banLoop

banEnd	call	dspMsg
	db 	CR,LF
	db	'M   I   K   R   O   C   O   M   P   U   T   E   R',0
				;fall into banLine
banLine	call	crlf
	mvi	b,49		;49 ='s
	mvi	a,'='
	call	banRptA		;display 49 ='s
	call	crlf
	jmp	crlf		;2nd CR/LF and return	

; banRptA - display character in A the number of times specified in B

banRptA	call	ptcn
	dcr	b
	jnz	banRptA
	ret

; logoTbl - MFA logo as data. Pairs of number for each line specify a count
;    of spaces followed by a count of #'s. Line terminates with zero. Table
;    terminates with FFh.

logoTbl	db	13,3,9,15,6,3,0
	db	11,5,7,17,4,5,0
	db	9,3,1,3,5,3,1,3,14,3,1,3,0
	db	7,3,3,3,3,3,3,24,0
	db	5,3,5,3,1,3,5,24,0
	db	3,3,7,5,7,3,8,3,7,3,0
	db	1,3,9,3,9,3,6,3,9,3,0
	db	0FFh

;********************************************************************
;
;  Type conversion, input, output subroutines
;
;********************************************************************

;------------------------------------------------------------
; tahex - read two 16 bit addresses. 1st returned in HL, 2nd in DE
;------------------------------------------------------------
tahex	call	ahex		;get first address param
				;fall into ahex to get 2nd param

;------------------------------------------------------------
; ahex - read up to 4 hex digits to binary, return in de
;------------------------------------------------------------
ahex	mvi	c,4		;count of 4 digits
ahe0	lxi	h,0		;16 bit zero
ahe1	call	rdcn		;read a byte
ahexNr	cpi	'0'
	jc	start		;below '0', abort
	cpi	':'
	cnc	alph
	dad	h
	dad	h
	dad	h
	dad	h
	sui	'0'		;ascii bias
	cpi	10		;digit 0-10
	jc	alf
	sui	7		;alpha bias
alf	add	l
	mov	l,a
	dcr	c
	jnz	ahe1		;keep reading
	xchg			;result in de
				;fall through to print a space
;------------------------------------------------------------
; spce - print a space
; ptcn - print character passed in a
;------------------------------------------------------------
spce	mvi	a,' '		;print space
ptcn	push	psw

ptlop	in	SIO1ST		;wait for OK to transmit
	ani	TBE
	jz	ptlop

	pop	psw		;recover a
	ani	07Fh		;get rid of msbit
	out	SIO1DT		;and print it
	ret			;return from ptcn

;------------------------------------------------------------
; alph - verify valid hex digit, abort to command loop if not
;------------------------------------------------------------
alph	cpi	'A'
	jc	start
	ani	05fh
	cpi	'G'
	jnc	start
	ret

;------------------------------------------------------------
; crlf - print CR/LF
;------------------------------------------------------------
crlf	mvi	a,CR
	call	ptcn
	mvi	a,LF	
	jmp	ptcn

;------------------------------------------------------------
; err - display the address in hl followed by the value
;    in b, then the value in a.
;------------------------------------------------------------
err	push	psw		;save A
	call	ptad		;print address
	mov	a,b		;print B
	call	pt2
	call	spce
	pop	psw		;print A
pt2	push	psw
	call	binh
	pop	psw
	jmp	binl

;------------------------------------------------------------
; ptad - display the address in h
;------------------------------------------------------------
ptad	call	crlf		;print cr,lf
	call	pause
	mov	a,h		;print
	call	pt2		;ascii
	mov	a,l		;codes
	call	pt2		;for
	call	spce		;address
	ret

;------------------------------------------------------------
; binh - print MSN of byte passed in A
; binl - print LSN of byte passed in A
;------------------------------------------------------------
binh	rar
	rar
	rar
	rar
binl	ani	0fh		;low 4 bits
	adi	'0'		;ascii bias
	cpi	03ah		;digit 0-9
	jc	ptcn
	adi	7		;digit A-F
	jmp	ptcn

;------------------------------------------------------------
; dspMsg - display null-terminated in-line message.
;------------------------------------------------------------
dspMsg	pop	h		;hl->string to display

dspLoop	mov	a,m		;a=next character to display
	inx	h		;move to next character
	ora	a		;null terminator?
	jz	dmExit		;yes, done

	call	ptcn		;display character and repeat
	jmp	dspLoop

dmExit	call	spce		;display a trailing space
	pchl			;return past the string

;------------------------------------------------------------
; rdcn - read from console to A with echo to screen
; getCon - read from console to A without echo
;------------------------------------------------------------
rdcn	call	getCon		;get character from console
	cpi	ESC		;ESC confuses smart terminals
	rz			;    so don't echo escape
	jmp	ptcn		;echo onto printer

getCon	in	SIO1ST		;read keyboard status
	ani	RDA		;data available?
	jz	getCon		;no

	in	SIO1DT		;read from keyboard
	ani	07Fh		;strip off msb
	ret

;------------------------------------------------------------
; pause - pause/resume with spacebar. Also look for a ctrl-c
;    or ESC to abort.
;------------------------------------------------------------
pause	call	cntlc		;look for abort or other character
	cpi	' '
	rnz			;return if not space or abort

ploop	call	cntlc		;loop here until space or abort pressed
	cpi	' '
	jnz	ploop
	ret

;------------------------------------------------------------
; cntlc - see if a character has been typed. If not, return
;   zero true. If ctrl-c or ESC typed, abort and return to 
;   the command loop. Otherwise, return the character typed.
;------------------------------------------------------------
cntlc	in	SIO1ST		;anything typed?
	ani	RDA
	rz			;no, exit with zero true

	in	SIO1DT		;get the typed character
	ani	07Fh
	cpi	CTRLC		;abort with ctrl-c (2.0 style)
	jz	start
	cpi	ESC		;or ESC (4.x style)
	jz	start
	ret

;------------------------------------------------------------
; bmp - compare address and increment h. Return zero true
;   if HL=DE. Once HL=DE, then DE is incremented each time
;   so the comparison remains true for subsequent calls.
;------------------------------------------------------------
bmp	mov	a,e		;compare lsb's of HL,DE
	sub	l
	jnz	goon		;not equal

	mov	a,d		;compare msb's of hl,de
	sub	h		;gives zero true if equal

goon	inx	h		;increment HL
	rnz			;exit if HL <> DE yet

	inx	d		;increase DE as well so they will
	ret			;  still be equal next time

;------------------------------------------------------------
; mfaBoot - Load sector 1 from track 0, drive 0 into RAM
;   at 0100h and jump to it.
;------------------------------------------------------------

; Floppy disk controller equates (WD1793)

FDCBASE	equ	0C0H		;controller base address
FDCCMD	equ	FDCBASE		;1793 command register
FDCSTAT	equ	FDCBASE		;1793 status register
FDCTRK	equ	FDCBASE+1	;1793 track register
FDCSEC	equ	FDCBASE+2	;1793 sector register
FDCDATA	equ	FDCBASE+3	;1793 data in/out register
FDCSEL	equ	FDCBASE+4	;FDC drive select port
FDCWAIT	equ	FDCBASE+8	;FDC stall/wait port

; Commands for 1793

STPRATE	equ	0		;6ms step rate (1mhz clock)

cRESTOR	equ	008h+STPRATE	;restore, head load, no verify
cSEEK	equ	018h+STPRATE	;seek, head load, no verify
cSTPIN	equ	058h+STPRATE	;step in, update track reg, head loaded
cSTPOUT	equ	078h+STPRATE	;step out, update track reg, head loaded
cREAD	equ	080h		;read, head assumed settled
cWRITE	equ	0A8h		;write, head assumed settled
cRDADDR	equ	0C0h		;read address marker, assume head settled
cRESET	equ	0D0h		;reset 1793 to idle

cSTPTGL	equ	020h		;xor toggles between step in and out
cWAITHD	equ	004h		;bit for read/write to wait for head settle

; Status bits from 1793

sNOTRDY	equ	80h		;1=not ready
sWRPROT	equ	40h		;1=write protected
sHDLOAD	equ	20h		;1=head is loaded
sSKERR	equ	10h		;1=seek error
sCRCERR	equ	08h		;1=CRC error
sTRACK0	equ	04h		;1=on track zero
sINDEX	equ	02h		;1=index detected
sBUSY	equ	01h		;1=1793 busy

sSECERR	equ	10h		;1=sector not found
sWRTFLT	equ	20h		;1=write fault
sDLOST	equ	04h		;1=lost data 

; Combined status flag masks for status of a seek, read, and write

smTRK0	equ	sSKERR+sNOTRDY+sBUSY+sTRACK0
smSEEK	equ	sSKERR+sNOTRDY+sBUSY
smREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY+sBUSY
smWRITE	equ	smREAD+sWRPROT+sWRTFLT

; Drive select register parameters

pDRIVE0	equ	001h		;1=drive 0 select
pDRIVE1	equ	002h		;1=drive 1 select
pDENS	equ	004h		;1=single density, 0=double density
pSIDE	equ	008h		;1=side, 0=side 0

; Misc Equates

BUFADDR	equ	0100h		;sector loaded here
FDCINTV	equ	02Ch		;RST5.5 interrupt vector for FDC
SIM	equ	030h		;8085 SIM opcode

;------------------------------------------------------------------
; mfaBoot - Initialization
;    Select drive (starts the motor), reset the 1793, set up the
;    RST5.5 interrupt vector used by the FDC, then delay 0.5s to
;    ensure the motor is up to speed. There is no other way to know
;    the drive is ready and track stepping appears to be ignored
;    until the drive is ready.
;------------------------------------------------------------------
mfaBoot	mvi	a,pDRIVE0	;select disk 0, side 0, double density
	out	FDCSEL		;this also starts the drive motor

	mvi	a,cRESET	;reset the 1793
	out	FDCCMD

	di			;disable interrupts to be safe

	mvi	a,JMP		;set up the FDC interrupt vector
	sta	FDCINTV
	lxi	h,fdcIsr	;ISR address
	shld	FDCINTV+1

	lxi	b,1000		;delay about 1 second for drive motor
	call	delayMs

; Restore to track 0

	mvi	b,3		;try restore up to three times

rstrLp	mvi	a,pDRIVE0	;re-select to keep the motor running
	out	FDCSEL

	call	restore		;issue restore to track 0
	ani	smTRK0		;get bits we care about alone
	cpi	sTRACK0		;track zero and no errors?
	jz	doRead		;yes, go do the read

	dcr	b		;decrement retry counter
	jnz	rstrLp		;loop if more retries

	call	dspMsg
	db	CR,LF,'Seek to track 0 failed',0

	xra	a		;deselect drives and exit
	out	FDCSEL
	ret

; Read sector 1

doRead	lxi	b,25		;delay 25ms to let head settle
	call	delayMs

	mvi	b,10		;try read up to ten times

readLp	mvi	a,pDRIVE0	;re-select to keep the motor running
	out	FDCSEL

	lxi	h,BUFADDR	;HL->buffer address
	mvi	a,1		;request sector 1
	out	FDCSEC
	call	readSec		;execute the read
	ani	smREAD		;check for error
	jz	BUFADDR		;jump to loaded code
	
	dcr	b		;decrement retry counter
	jnz	readLp		;loop if more retries

	call	dspMsg
	db	CR,LF,'Boot sector read failure',0

	xra	a		;deselect drives and exit
	out	FDCSEL
	ret

;------------------------------------------------------------------
; restore - Issue restore to track zero command and wait for 
;    completion via interrupt. The ISR returns control directly
;    to the caller of this subroutine.
;------------------------------------------------------------------
restore	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	mvi	a,cRESTOR	;issue restore to track zero command
	out	FDCCMD

wtCmd	jmp	wtCmd		;wait for command to finish

;------------------------------------------------------------------
; readSec - Issue read sector command then go into a loop receiving
;    the bytes of the sector into the buffer pointed to by HL. The
;    ISR returns control directly to the caller of this subroutine
;    when the 1793 signals completion of the read operation.
;------------------------------------------------------------------
readSec	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	mvi	a,cREAD		;issue read sector command
	out	FDCCMD

rsLoop	out	FDCWAIT		;wait for data byte
	in	FDCDATA		;get byte
	mov	m,a		;save in memory
	inx	h
	jmp	rsLoop		;interrupt will exit this loop

;-----------------------------------------------------------------------
; fdcIsr - Interrupt service routine when the 1793 signals command
;    completion. The interrupted address is removed from the stack
;    and control returns to the caller of the interrupted routine.
;    The status from the 1793 is returned in A.
;-----------------------------------------------------------------------
fdcIsr	pop	psw		;remove ISR return address

wtBusy	in	FDCSTAT		;get 1793 status
	rrc			;make sure it's not busy
	jc	wtBusy		;shouldn't happen, but be safe

	rlc			;store status in A
	ret			;return to caller of command

;------------------------------------------------------------------------------
; delayMs - delay number of ms specified in BC
;------------------------------------------------------------------------------
delayMs	mvi	d,2000/15	;B=1ms through following loop

dmsLoop	dcr	d		;(5) decrement B
	jnz	dmsLoop		;(10)
	
	dcx	b		;decrement ms counter in BC
	mov	a,b		;test for zero	
	ora	c
	jnz	delayMs		;do another ms

	ret

ENDADDR	equ	$

;------------------------------------------------------------
; Data Area
;------------------------------------------------------------
sioPort	ds	1		;port for hex file load
tmpCode	ds	3		;IN/OUT instrution code space
	ds	32		;stack space
STACK	equ	$

	end
