;
; sysmon.asm - system monitor
;
; revision 02.00 - 05/12/81
;
; Copyright (C) 1978, 1979, 1980, 1981 by Terence M. Kennedy
; All Rights Reserved Worldwide
;
; Permission is hereby granted to the SIG/M library to distribute
; this work in whatever form and manner it pleases. This permission
; is non-transferable.
;
; Computer hobbyists may use this code, in any form, modified or
; unmodified, for non-commercial purposes.
;
; Refer all requests for commercial distribution to the SIG/M Library.
;
; I define 'Commercial distribution' to include distribution by
; the 'CP/M User's Group' run by Intersoft Corp. (Lifeboat Associates)
;
; NOTE: CP/M is a (registered) trademark of Digital Research, Inc.,
;	Pacific Grove, California. Reference to this trademark within
;	the body of this text is only to indicate that this code will
;	interface with said trademarked product, and does not imply
;	that the owner of the trademark owns, approves, or uses this
;	code.
;
false	equ	0		;logical false value
true	equ	not false	;logical true value
;
testing	equ	true		;if testing
help	equ	true		;if '?' command to display commands desired
cpmio	equ	true		;if using CP/M console I/O instead of hardware
;
	if	(not testing) and cpmio
--> cannot have cpmio true and testing false <--
	endif
;
	if	testing
pbase	equ	00100h		;program base
dbase	equ	02000h		;data area base
	endif
;
	if	not testing
pbase	equ	0e000h		;program base in bytesaver
dbase	equ	0bf00h		;data area base (below bytesaver)
	endif
;
	if	cpmio		;if using CP/M I/O calls
conrdy	equ	006h		;console status
conin	equ	009h		;console input
conout	equ	00ch		;console output
	endif
;
wboot	equ	0000h		;CP/M warm boot entry point
;
; misc. equates
;
cr	equ	0dh		;carriage return
lf	equ	0ah		;line feed
bs	equ	08h		;back space
sl	equ	'/'		;slash
cntrlc	equ	'C'-40h		;control-C
cntrlq	equ	'Q'-40h		;control-Q
cntrls	equ	'S'-40h		;control-S
cntrlx	equ	'X'-40h		;control-X
cntrlz	equ	'Z'-40h		;control-Z
bell	equ	07h		;ring console bell
;
; device equates
;
fpanel	equ	0ffh		;front panel device address
consta	equ	000h		;console status port
condat	equ	001h		;console data port
rdrsta	equ	002h		;reader status port
rdrdat	equ	003h		;reader data port
dsksta	equ	07fh		;disk drive status
dskcmd	equ	07fh		;disk drive command port
dskc2	equ	07ch		;disk drive 2nd command port
dmah	equ	07eh		;high order byte of dma address
dmal	equ	07dh		;low order byte of dma address
;
; device control bits
;
trk0	equ	004h		;if at track 0
iof	equ	008h		;if I/O is complete
notrdy	equ	080h		;if drive is not ready
done	equ	notrdy or iof	;done w/ command (one way or another)
errors	equ	0f0h		;error bits
tbe	equ	080h		;transmit buffer empty (all serial ports)
rda	equ	040h		;data available (all serial ports)
readf	equ	0bfh		;read disk function
;
; data areas (uninitialized)
;
	org	dbase
;
userpc:	ds	2		;user's PC for R command
svc:	ds	1		;supervisor call no.
ibuf:	ds	128		;input buffer
stack:	ds	64		;program stack
pflag1:	ds	1		;parameter flags
pflag2:	ds	1
param1:	ds	2		;parameters
param2:	ds	2
cbuf:	ds	1		;command buffer
dmpad:	ds	2		;dump address
temp:	ds	10h		;temp. workspace for B, I & O commands
oflst	equ	temp		;object file loader state
oflce	equ	temp+1		;object file loader checksum error flag
oflcs	equ	temp+2		;object file loader checksum
oflbc	equ	temp+3		;object file loader byte count
oflbb	equ	temp+4		;object file loader byte buffer
oflla	equ	temp+5		;object file loader load address (word value)
oflfrr	equ	temp+7		;object file loader first record read flag
addr:	ds	2		;hexmon current address
data:	ds	1		;hexmon working data
diagfg:	ds	1		;diagnostic load flag
srcnam:	ds	10h		;name of tape file searching for
fndnam:	ds	10h		;name of tape file found
;
; code area
;
	org	pbase
;
monitr:	jmp	main		;jump to main code
super:	jmp	supvsr		;jump to supervisor
main:	lxi	sp,stack+64	;set up sp
	lxi	h,0		;clear user PC to 0
	shld	userpc
	lxi	h,cs		;clear screen code
	call	wasc		;send it
	in	fpanel		;read sense switches
	cpi	0ffh		;test for no front panel
	jz	main1		;if no panel
	cpi	80h		;test if entering sysmon
	jnz	boot		;no, boot disk right away
main1:	lxi	h,signon	;get signon message
	call	wasc		;send to con:
	call	weolc		;do cr/lf
prompt:	mvi	a,'@'		;our prompt
	call	wacc		;to console
	lxi	h,ibuf		;input buffer pointer
	call	rasc		;get command line
	mov	a,m		;test if terminated with '/'
	cpi	sl
	jz	hexmon		;if so, use hex monitor
	mvi	m,' '		;insure at least one blank at end
	inx	h		;followed by cr
	mvi	m,cr
	lxi	d,ibuf		;points to 1st character of input line
parse:	ldax	d		;get command
	cpi	' '		;test for null command
	jz	prompt		;if so, re-issue prompt
	sta	cbuf		;if not, put command keyword in cbuf
	inx	d		;points to param1 now
parse1:	xra	a		;clear parameter flags
	sta	pflag1
	sta	pflag2
	call	dhs		;decode first parameter
	jc	comerr		;if command error in param.
	sta	pflag1		;save parameter flag
	shld	param1		;and parameter
	call	dhs		;decode second parameter (same as first)
	jc	comerr
	sta	pflag2
	shld	param2
	lda	pflag1		;get first parameter flag
	ora	a		;see if set
	jnz	$+9		;yes, skip next code
	lxi	h,100h		;no, default to 100 hex
	shld	param1
	lda	pflag2		;get second parameter flag
	ora	a		;test it
	jnz	$+9		;if already got one
	lhld	param1		;else default it to same as first one
	shld	param2
decode:	lxi	h,ctable	;lookup table for keywords
decod1:	lxi	d,cbuf		;points to command input by user
	mvi	c,1		;compare only 1 character
	mvi	b,1		;set match flag
decod2:	ldax	d		;get command
	cmp	m		;compare w/ keyword table
	jz	$+5		;if match
	mvi	b,0		;else reset match flag
	inx	d
	inx	h
	dcr	c		;this would flush rest of keyword if >1 char.
	jnz	decod2
	dcr	b		;test match flag
	jz	match		;if successful match
	inx	h		;else skip address field
	inx	h
	mov	a,m		;test if end of table
	ora	a
	jnz	decod1		;if not at end
	lxi	h,badcom	;at end, send 'bad command' message
	call	wasc
	jmp	prompt		;and go back to main prompt
match:	mov	e,m		;copy address to DE
	inx	h
	mov	d,m
	lxi	b,prompt	;create return address
	push	b		;this puts prompt on stack for second RET
	push	d		;this puts the routine address on the stack
	lhld	param2		;put second parameter in DE (see xchg below)
	xchg
	lhld	param1		;put first parameter in HL
	ret			;this causes jmp to address in D, pushed above
comerr:	lxi	h,badstx	;give 'bad syntax' message
	call	wasc
	jmp	prompt
;
; command table
;
ctable:	db	'D'		;dump command
	dw	dump
	db	'H'		;halt command
	dw	halt
	db	'B'		;boot standard system
	dw	boot
	db	'S'		;boot diagnostic system
	dw	diag
	db	'R'		;display register set
	dw	regdis
	db	'E'		;enter command
	dw	enter
	db	'M'		;move command
	dw	move
	db	'F'		;fill command
	dw	fill
	db	'G'		;go command
	dw	go
	db	'I'		;input command
	dw	rip
	db	'O'		;output command
	dw	wop
	db	'L'		;load diagnostic tape
	dw	loader
;
	if	help
	db	'?'		;output command summary
	dw	hlpdis
	endif
;
	if	testing
	db	'Q'		;quit command
	dw	wboot		;to CP/M warm boot
	db	'T'		;supervisor call test
	dw	svctst
	endif
;
	db	0		;end of command table
;
; supvsr - supervisor call handler
;
supvsr:	push	h		;save user parameters
	push	d
	push	b
	push	psw
sup1:	lxi	h,stable	;supervisor call table pointer
sup2:	lda	svc		;get supervisor call no.
	cmp	m		;compare with call no. in table
	inx	h		;point to address in any case
	jz	sup3		;if found
	inx	h		;skip to next svc no.
	inx	h
	mov	a,m
	cpi	0ffh
	jz	sup4		;if at end of table
	jmp	sup2		;and retry
sup3:	mvi	a,jmp		;stuff a jump in temp. area
	sta	temp
	mov	e,m		;get address from table
	inx	h
	mov	d,m
	xchg			;put in hl
	shld	temp+1		;save address in temp+1
	pop	psw		;restore user's registers
	pop	b
	pop	d
	pop	h
	jmp	temp		;go to routine
sup4:	mvi	b,8		;8 bytes to remove
sup5:	inx	sp
	dcr	b
	jnz	sup5		;if not done
	lxi	h,ilsum1	;print illegal supervisor call message
	call	wasc
	lda	svc		;print svc no. causing error
	call	whbc
	lxi	h,ilsum2	;print rest of message
	call	wasc
	inx	sp		;get high byte
	lxi	h,0		;copy sp to hl
	dad	sp
	mov	d,m		;get byte 1
	dcx	h
	mov	e,m
	xchg
	dcx	h		;point to call address
	dcx	h
	dcx	h
	mov	a,h		;output hi byte
	call	whbc
	mov	a,l		;output low byte
	call	whbc
	call	weolc		;do 2 cr/lf's
sup6:	call	weolc		;do cr/lf
	lxi	sp,stack+64	;reset sp
	jmp	prompt		;and issue prompt
;
; supervisor call table
;
stable:	db	0		;return to monitor
	dw	sup6
	db	1		;return version number
	dw	vers
	db	2		;console in
	dw	racc
	db	3		;console out
	dw	wacc
	db	4		;console status
	dw	const
	db	5		;console string in
	dw	rasc
	db	6		;console string out
	dw	wasc
	db	7		;do cr/lf on console
	dw	weolc
	db	8		;ring bell on console
	dw	ding
	db	9		;test for legal digit
	dw	legal
	db	10		;decode hex string
	dw	dhs
	db	11		;write hex digit to console
	dw	whdc
	db	12		;write hex byte to console
	dw	whbc
	db	13		;dump hex & ascii on console
	dw	dmp
	db	14		;dump register set
	dw	regdis
	db	15		;compute difference
	dw	diff
	db	16		;move up
	dw	moveup
	db	17		;move down
	dw	movedn
	db	18		;boot dos
	dw	boot
	db	19		;boot ddos
	dw	diag
	db	20		;delay
	dw	delay
	db	21		;read byte from reader
	dw	reader
	db	22		;process object byte
	dw	ofl
	db	0ffh		;end of table
;
; loader - load specified diagnostic from tape
;
loader:	lxi	h,askfil	;ask for filename
	call	wasc
	lxi	h,srcnam	;input buffer address
	call	rasc		;read user's response
	lxi	h,askply	;tell him to press play
	call	wasc		;do cr/lf
load1:	call	reader		;get character
	cpi	';'		;test for header
	jnz	load1		;skip till found
	lxi	h,fndnam	;pointer to 'found name'
load2:	call	reader		;get filename
	cpi	cr		;see if all done
	jz	load3
	mov	m,a		;store away
	inx	h		;next free loc.
	jmp	load2		;do next char
load3:	mvi	m,0		;force end of string
	lxi	h,found		;print 'Found '
	call	wasc
	lxi	h,fndnam	;send name we found on tape
	call	wasc		;to console
	lxi	d,srcnam	;pointer to search name
	lxi	h,fndnam	;pointer to name found on tape
	mvi	b,10h		;max. no. of bytes to compare
load4:	mov	c,m		;byte from found name
	ldax	d		;byte from wanted name
	cpi	cr		;test if last byte in source
	jz	load5		;if so...
	cmp	c		;compare
	jnz	load1		;if not a match
	inx	h		;next bytes
	inx	d
	dcr	b		;see if have done 10h yet...
	jnz	load4		;if not
load5:	lxi	h,loadng	;print 'Loading'
	call	wasc
	xra	a		;initialize loader
	sta	oflst
	sta	oflce
	sta	oflfrr
load6:	call	reader		;get character
	cpi	cntrlz		;test if eof
	jz	load7		;if so...
	call	ofl		;process byte
	lda	oflce		;test for checksum error
	ora	a
	jz	load6		;if ok
	jmp	load8		;if error
load7:	lxi	h,execad	;print 'Execute address: '
	call	wasc
	lhld	addr		;now print address
	mov	a,h		;hi byte first
	call	whbc
	mov	a,l		;next, the low byte
	call	whbc
	call	weolc		;do cr/lf
	call	weolc
	ret			;return to main monitor
load8:	lxi	h,cksume	;print 'Checksum error'
	call	wasc
	ret
;
; diag - boot diagnostic system from disk
;
diag:	mvi	a,0ffh		;set diagnostic flag
	jmp	bootd		;proceed as a standard boot
;
; boot - boot DOS from diskette (maybe)
;
boot:
;
	if	testing		;prevent odd things from
	jmp	main1		;happening to first-time SIG/M users
	endif
;
	mvi	a,0		;reset diag. flag
bootd:	sta	diagfg
	in	dsksta		;get disk status
	cpi	0ffh		;test if no disk in computer
	jnz	boot2		;if disk is there
boot1:	lxi	h,wutdsk	;what disk?
	call	wasc
	jmp	main1		;back to system monitor
boot2:	mvi	a,0ffh		;issue null commands to update status
	out	dskc2
	out	dskcmd
	mvi	a,35		;do 35 ms head load delay
	call	delay
boot21:	in	dsksta		;get status again
	ani	trk0		;test track 0 bit
	jz	boot3		;if at track 0
	mvi	a,0ffh		;this code steps the head in
	out	dskcmd
	ani	0fdh
	out	dskcmd
	ori	002h
	out	dskcmd
	mvi	a,8		;delay 8 ms between steps
	call	delay		;do delay
	jmp	boot21		;test track 0 again
boot3:	mvi	a,0f0h		;set dma address to 1000h(-3)
	out	dmah
	mvi	a,002h
	out	dmal
	mvi	a,0		;track 0
	sta	0ffdh		;@ dma-3
	mvi	a,1		;sector 1
	sta	0ffeh		;@ dma-2
	mvi	a,0fbh		;sd id mark (not really needed)
	sta	0fffh
	mvi	a,readf		;issue read function
	out	dskcmd		;issue as command
boot4:	in	dsksta		;read status
	ani	done		;test if done
	jz	boot4		;not yet...
	in	dsksta		;read status again
	push	psw		;save full byte for later
	ani	errors		;test for errors
	jz	boot5		;if no errors
	lxi	h,booter	;say boot error
	call	wasc		;send
	pop	psw		;get status back
	call	whbc		;to console
	call	weolc		;do cr/lf
	jmp	main1		;to main monitor
boot5:	pop	psw		;clean up stack
	lxi	h,1000h		;source address of move
	lxi	d,80h		;no. of bytes to move
	lxi	b,0		;destination address
	call	moveup		;do move
	lda	0		;get first byte of bootstrap
	cpi	0e5h		;test for push h
	jnz	boot6		;if not, execute code
	lxi	h,nosys		;say no system
	call	wasc		;send
	jmp	main1		;go to monitor
;
	if	not testing	;if running from rom
boot6:	lda	diagfg		;get diagnostic flag
	cpi	0ffh		;see if set
	jz	0		;if so, proceed directly to boot
	lxi	h,bootc		;source
	lxi	d,0bh		;no. of bytes
	lxi	b,temp		;destination
	call	moveup		;do move
	jmp	temp		;turn off rom, turn on ram, jmp to bootstrap
	endif
;
	if	testing		;if running from ram
boot6:	jmp	0		;we don't need to turn off rom board
	endif
;
	if	not testing
bootc:	db	3eh,0		;mvi a,0
	db	0d3h,40h	;out 40h
	db	3eh,1		;mvi a,1
	db	0d3h,41h	;out 41h
	db	0c3h,0,0	;jmp 0
	endif
;
	if	help
;
; hlpdis - display help menu [optional]
;
hlpdis:	lxi	h,hlpmsg	;print the whole thing
	call	wasc
	jmp	weolc		;exit w/ cr/lf
	endif
;
	if	testing
;
; svctst - test supervisor call
;
svctst:	mov	a,l		;put svc no. into A
	sta	svc		;save in svc
	call	super		;call supervisor
	jmp	weolc		;exit w/ cr/lf
	endif
;
; regdis - display 8080 register set
;
regdis:	push	h		;save registers
	push	d
	push	b
	push	psw
	lxi	h,regmsg	;issue register message
	call	wasc
	lhld	userpc		;get user's PC
	call	reg1		;display it
	lxi	h,regsp		;issue SP message
	call	wasc
	lxi	h,0
	dad	sp		;sneaky way to get SP
	call	reg1
	lxi	h,regaf		;issue AF message
	call	wasc
	pop	h		;get AF
	call	reg1
	lxi	h,regbc		;issue BC message
	call	wasc
	pop	h		;get BC
	call	reg1
	lxi	h,regde		;issue DE message
	call	wasc
	pop	h		;get DE
	call	reg1
	lxi	h,reghl		;issue HL message
	call	wasc
	pop	h		;get HL
	call	reg1
	jmp	weolc		;exit via cr/lf
reg1:	mov	a,h		;get high byte
	call	whbc
	mov	a,l		;get low byte
	call	whbc
	ret			;return to caller
;
; rip - read input port
;
rip:	mvi	a,in		;input instruction
	sta	temp		;save in temp code area
	mov	a,l		;HL (param1) has input port address
	sta	temp+1
	mvi	a,ret		;return instruction
	sta	temp+2
	call	temp		;execute code just built
	call	whbc		;write result on console
	jmp	weolc		;equal to call weolc ! ret
;
; wop - write output port
;
wop:	mvi	a,out		;output instruction
	sta	temp		;mainly the same as above
	mov	a,l
	sta	temp+1
	mvi	a,ret
	sta	temp+2
	mov	a,e		;get value to send from param2
	call	temp
	jmp	weolc
;
; halt - halt computer
;
halt:	hlt			;that was simple!!!
;
; dump - dump memory to console in hex & ascii
;
dump:	lda	pflag1		;see if address was typed
	ora	a
	jz	weolc		;no, exit
	call	diff		;get difference between param1 & param2 in DE
	inx	d		;+1
	shld	dmpad		;save param1 as starting dump address
	jmp	dmp		;do actual dump
;
; enter - enter bytes into memory
;
enter:	mov	a,h		;prompt w/ current load address
	call	whbc
	mov	a,l
	call	whbc
	mvi	a,':'
	call	wacc
	mvi	a,' '
	call	wacc
	push	h		;save load address
	lxi	h,ibuf		;read user's input
	call	rasc
	dcr	b		;test for empty line
	jz	enter3		;empty, exit
	lxi	d,ibuf		;pointer to buffer
enter1:	call	dhs		;decode 1 byte
	jc	enterr		;if error
	ora	a		;test for end of line
	jz	enter2
	mov	a,l		;L has low byte of entered value
	pop	h		;restore load address
	mov	m,a		;move byte into memory
	inx	h		;increment load address
	push	h		;save again...
	jmp	enter1		;do next byte on line
enter2:	pop	h		;restore load address
	jmp	enter		;get next line
enter3:	pop	h		;clean up stack
	jmp	weolc		;exit w/ cr/lf
enterr:	pop	h		;clean up stack
	lxi	h,badval	;send 'bad hex value' message
	jmp	wasc		;exit
;
move:	lda	pflag1		;make sure we have two parameters
	ora	a
	jz	moverr
	lda	pflag2
	ora	a
	jz	moverr
	call	diff		;find difference between param1 & param2
	inx	d		;+1
	push	d		;save param1 & param2
	push	h
	lxi	h,moveto	;ask 'to?'
	call	wasc
	lxi	h,ibuf		;get answer
	call	rasc
	lxi	d,ibuf		;reset pointer
	call	dhs		;decode answer
	jnc	move1		;if ok
	pop	h		;else clean up stack
	pop	d
	jmp	enterr+1	;print 'bad hex value' message
move1:	xchg			;DE= address to move to
	mov	b,d		;copy to BC
	mov	c,e
	pop	h		;get source address
	call	diff		;compute distance to move
	pop	d		;restore size of block to move
	jnc	movedn		;move downward
	jmp	moveup		;move upward
moverr:	lxi	h,twopar	;issue 'two parameters' message
	jmp	wasc
;
; fill - fill memory with specified value
;
fill:	lda	pflag1		;test for two parameters
	ora	a
	jz	moverr
	lda	pflag2
	ora	a
	jz	moverr
	call	diff		;compute # of bytes to fill
	inx	d		;+1
	push	h		;save parameters (h now has length)
	push	d
	lxi	h,fillw		;print 'With?'
	call	wasc
	lxi	h,ibuf		;get answer
	call	rasc
	lxi	d,ibuf
	call	dhs		;decode it
	jnc	fill1		;if ok
	pop	d
	pop	h		;clean up stack
	jmp	enterr+1	;print 'bad hex value'
fill1:	mov	b,l		;byte to fill w/ --> B
	pop	d
	pop	h		;restore source & length
fill2:	mov	a,d		;return when count=0
	ora	e
	rz
	mov	m,b		;place fill byte
	inx	h		;loc=loc+1
	dcx	d		;# to do=# to do-1
	jmp	fill2
;
; go - go to location specified by param1
;
go:	mvi	a,call		;stuff call instruction
	sta	temp		;place in temp code
	shld	temp+1		;save param1 as address of call
	mvi	a,jmp		;save jump
	sta	temp+3
	lxi	h,go1		;where to return to...
	shld	temp+4
	jmp	temp		;execute code
go1:	jmp	weolc		;exit w/ cr/lf
;
; hexmon - hexadecimal monitor
;
hexmon:	call	wacc		;echo '/'
	lxi	d,ibuf		;decode address(?) just entered
	call	dhs
	jc	hex8		;if not valid
	shld	addr		;save address
hex1:	mvi	a,' '		;send a blank
	call	wacc
	mov	a,m		;get data that's there
	call	whbc		;and send to console
hex2:	mvi	a,' '		;another blank
	call	wacc
hex3:	call	racc		;get character
	cpi	cr		;test cr
	push	psw		;save flags
	cz	weolc		;do cr/lf if cr
	pop	psw		;restore flags
	jz	prompt		;if so, go back to main prompt
	cpi	'^'		;test for open previous
	jz	hex5		;if match...
	cpi	lf		;test for open next
	jz	hex7		;if match...
	push	psw		;save value
	call	legal		;test for legal value
	jc	hex10		;if invalid, kill stack and retry
	rlc			;move to high nibble
	rlc
	rlc
	rlc
	sta	data		;save as data byte
	pop	psw		;restore ascii version
	call	wacc		;echo to console
hex4:	call	racc		;get next nibble
	push	psw
	call	legal		;test if valid
	jc	hex9		;if not, kill pushed stuff & try again
	mov	b,a		;save in B
	lda	data		;data already has hi nibble
	ora	b		;add in low nibble
	sta	data		;save as data again
	pop	psw		;restore ascii
	call	wacc
	lhld	addr		;get working address
	lda	data		;get data
	mov	m,a		;move
	call	weolc		;do cr/lf
	inx	h		;next address
	jmp	hex6
hex5:	call	wacc		;echo '^'
	call	weolc		;do cr/lf
	lhld	addr
	dcx	h		;prev. address
hex6:	shld	addr		;save as current
	mov	a,h		;print on console
	call	whbc
	mov	a,l
	call	whbc
	mvi	a,sl		;put up slash
	call	wacc
	jmp	hex1		;go to entry mode
hex7:	call	weolc		;open next address
	lhld	addr
	inx	h
	jmp	hex6
hex8:	lxi	h,baddr		;say bad address
	call	wasc
	jmp	prompt		;return to prompt
hex9:	pop	psw		;kill old char.
	call	ding		;ring bell
	jmp	hex4		;try again
hex10:	pop	psw		;kill old
	call	ding
	jmp	hex3		;and retry
;
; utility routine section - many are available by external programs
;
;
; ding - ring console bell
;
ding:	push	psw		;save user's A
	mvi	a,bell
	call	wacc		;print a bell
	pop	psw		;restore user's A
	ret			;return to caller
;
; vers - return version number in hl
;
vers:	lxi	h,0100h		;version 01.00
	ret			;give to user
;
; moveup - move memory towards zero
;
moveup:	mov	a,d		;de=# of bytes to move
	ora	e
	rz			;if done moving
	mov	a,m		;get byte from source
	inx	h		;increment source address
	stax	b		;save in destination
	inx	b		;increment destination address
	dcx	d		;decrement # of bytes left to move
	jmp	moveup		;go do another...
;
; movedn - move memory towards ffff
;
movedn:	dad	d		;add source address to # of bytes, giving last
	mov	a,c		;byte to move
	add	e
	mov	c,a
	mov	a,b
	adc	d
	mov	b,a
movdn1:	mov	a,d		;see if done
	ora	e
	rz			;if done
	dcx	h		;decrement source address
	mov	a,m		;get byte
	dcx	b		;decrement destination address
	stax	b		;store at dest. address
	dcx	d		;1 less byte to move...
	jmp	movdn1		;go again
;
; diff - compute difference between de & hl and place in de
;
diff:	mov	a,e		;do first 8-bit subtract
	sub	l
	mov	e,a		;return to E
	mov	a,d		;now do second
	sbb	h
	mov	d,a		;and return to D
	ret			;return to caller
;
; dmp - dump memory in hex and ascii to console
;
dmp:	mov	a,d		;see if done
	ora	e
	jz	weolc		;if so, exit via weolc
;
	if	not cpmio	;if hardware I/O
	in	condat		;get current data from console
	cpi	cntrlc		;see if ^C typed...
	jnz	dmp1
	endif
;
	if	cpmio
	jmp	dmp1		;can't stop output under CP/M
	endif
;
	jmp	weolc		;...and exit
dmp1:	call	weolc		;start new line
	lda	dmpad+1		;get high byte of address
	call	whbc		;write to console
	lda	dmpad		;do same w/ low byte
	call	whbc
	push	h		;save address
	push	d		;save count
	mvi	c,16		;16 bytes per line
dmp2:	mov	a,d		;test if done with hex for this line
	ora	e
	jz	dmp3
	mvi	a,' '		;put a blank between bytes
	call	wacc
	mov	a,m		;get byte to be dumped
	inx	h		;increment pointer
	call	whbc		;print hex byte on console
	push	h		;save address
	lhld	dmpad		;increment user's relative address
	inx	h
	shld	dmpad
	pop	h		;restore our address
	dcx	d		;decrement # of bytes left
	dcr	c		;decrement # of bytes left on this line
	jnz	dmp2		;if more
	jmp	dmp4		;if not more, do ascii
dmp3:	inr	c		;this puts ascii in right place if last address
	dcr	c		;to dump was not on 16 byte boundary
	jz	dmp4		;if done
	mvi	a,' '		;print 3 spaces
	call	wacc
	call	wacc
	call	wacc
	jmp	dmp3+1		;continue
dmp4:	pop	d		;restore byte count
	pop	h		;restore pointer
	mvi	a,' '		;print 2 spaces
	call	wacc
	call	wacc
	mvi	c,16		;16 ascii bytes / line
dmp5:	mov	a,d		;test if done
	ora	e
	jz	weolc
	mov	a,m		;fetch next byte
	inx	h		;increment pointer
	ani	7fh		;mask out parity bit
	cpi	20h		;test if printable
	jnc	$+5		;if it isn't .lt. 20h
	mvi	a,'.'		;else use .
	cpi	7fh		;see if rubout
	jc	$+5		;if not
	mvi	a,'.'		;it is, substitute rubout
	call	wacc		;output to console
	dcx	d		;1 less byte to do
	dcr	c		;1 less on this line also
	jnz	dmp5		;if still more on this line
	jmp	dmp		;start a new line
;
; whbc - write hex byte to console
;
whbc:	push	psw		;save byte
	rrc			;do high nibble first
	rrc
	rrc
	rrc
	call	whdc		;do digit
	pop	psw		;get byte back
	push	psw		;save again
	call	whdc		;do low nibble
	pop	psw		;return w/ value
	ret
;
; whdc - write hex digit to console
;
whdc:	ani	0fh		;only look at low nibble
	cpi	10		;see if A-F
	jc	$+5		;no, 0-9
	adi	7		;make A-F
	adi	'0'		;make ascii
	jmp	wacc		;send & exit
;
; weolc - do cr/lf on console
;
weolc:	push	psw		;save acc
	mvi	a,cr		;send cr
	call	wacc
	mvi	a,lf		;send lf
	call	wacc
	pop	psw		;restore acc
	ret			;exit
;
; wasc - write ascii string to console
;
wasc:	mov	a,m		;get character
	inx	h		;increment pointer
	ora	a		;see if terminator
	rz			;return if so
	call	wacc		;else send character
	jmp	wasc		;and do another...
;
; rasc - read ascii string from console
;
rasc:	push	h		;save buffer pointer
	mvi	b,0		;0 characters entered
rasc1:	call	racc		;get a character
	cpi	cntrlx		;test for control-x
	jnz	rasc2		;no it, continue
	call	rasc7		;skip old line
	pop	h		;restore buffer pointer
	jmp	rasc		;try again
rasc2:	cpi	bs		;test for backspace
	jnz	rasc3		;if not
	inr	b		;see if at beginning of line
	dcr	b
	jz	rasc1		;if so, simply input again
	dcx	h		;decrement pointer
	dcr	b		;decrement count
	mvi	a,bs		;do bs/space/bs
	call	wacc
	mvi	a,' '
	call	wacc
	mvi	a,bs
	call	wacc
	jmp	rasc1		;get another character
rasc3:	mov	m,a		;put in buffer
	inr	b		;increment count
	mov	a,b		;test if room left in buffer
	cpi	126
	jm	rasc4		;if still room
	mvi	m,cr		;else stuff cr
	pop	h		;restore pointer
	ret			;and exit
rasc4:	mov	a,m		;get character back
	cpi	cr		;test for cr
	jz	rasc5		;if so, exit
	cpi	sl
	jz	rasc6		;test for slash
	inx	h		;increment pointer
	call	wacc		;echo character
	jmp	rasc1		;and go get another
rasc5:	inx	sp		;kill stuff on stack
	inx	sp
	jmp	weolc		;and exit
rasc6:	inx	sp		;kill stuff on stack
	inx	sp
	ret			;return to user w/o cr
rasc7:	mov	a,b		;get count to backspace over
	cpi	0
	rz			;if none, exit...
	mov	a,m		;get character
	dcx	h
	cpi	20h
	jc	rasc8		;if not printing
	cpi	7fh
	jz	rasc8		;likewise
	mvi	a,bs		;backspace over current char
	call	wacc
	mvi	a,' '
	call	wacc
	mvi	a,bs
	call	wacc
rasc8:	dcr	b
	jnz	rasc7
	ret
;
; dhs - decode hex string (ascii string @DE into binary in HL)
;
dhs:	lxi	h,0		;set result=0
	push	b		;save B register
	mvi	b,0		;clear 'digits found' counter
dhs1:	ldax	d		;get character
	cpi	cr		;test for cr
	jz	dhs5
	inx	d
	cpi	' '		;test for space
	jz	dhs1		;skip if so
	cpi	','		;test for comma
	jz	dhs1		;skip if so
dhs2:	dad	h		;HL=HL*16
	dad	h
	dad	h
	dad	h
	cpi	61h		;see if l/c
	jc	dhs3		;no
	sui	20h		;make it u/c
dhs3:	sui	'0'		;convert to binary
	jc	dhserr		;if error
	cpi	10		;see if <10
	jc	dhs4		;yes
	sui	7		;no, adjust for A-F
	cpi	10		;see if maybe between 9 & A
	jc	dhserr
dhs4:	cpi	16		;test if within bounds
	jnc	dhserr		;no, error
	add	l		;add to result
	mov	l,a		;replace
	jnc	$+4
	inr	h		;if carry
	inr	b		;increment 'digits found' counter
	ldax	d		;get next byte
	cpi	cr		;all these cause an exit
	jz	dhs5
	cpi	' '
	jz	dhs5
	cpi	','
	jz	dhs5
	cpi	sl		;test for slash
	jz	dhs5
	inx	d		;points to next
	jmp	dhs2		;decode value in A
dhs5:	ldax	d		;normal exit
	mov	a,b		;count to A
	pop	b		;restore old B (user's)
yup:	stc			;general purpose 'yes'
	cmc
	ret
dhserr:	dcx	d		;error exit
	ldax	d		;put offending character in A
	pop	b		;restore user's BC
nope:	stc			;general purpose 'no'
	ret			;exit
;
; legal - test if an ascii character is a legal hex value
;
legal:	sui	'0'		;remove ascii offset
	jc	nope		;if less, it isn't
	cpi	10		;if less than 10...
	jc	legal1		;...it's ok
	sui	7		;test for a-f
	cpi	10
	jc	nope
legal1:	cpi	16		;now see if < 16
	jnc	nope		;if greater
	jmp	yup		;if less than
;
; ofl - Intel object file loader
;	Thanks to: L. E. Hughes / 8080 SDC / Florida
;
ofl:	push	psw		;save character
	lda	oflst		;check state
	cpi	1
	jnc	ofl1
	pop	psw		;state 0 - wait for a colon
	cpi	':'
	rnz			;wasn't it - throw away
	xra	a		;clear checksum
	sta	oflcs
	inr	a		;set state=1
	sta	oflst
	ret
ofl1:	cpi	2		;test if state 1
	jnc	ofl2		;no, skip
	inr	a		;set state=2
	sta	oflst
	call	oflcb		;clear byte buffer
	pop	psw
	jmp	oflad		;accum. first digit of byte count
ofl2:	cpi	3		;test if state 2
	jnc	ofl3		;no, skip
	inr	a		;set state=3
	sta	oflst
	pop	psw
	call	oflad		;get 2nd digit of byte count
	sta	oflbc		;save result as byte count
	jmp	ofluc		;add result into checksum
ofl3:	cpi	4		;test if state 3
	jnc	ofl4		;no, skip
	inr	a
	sta	oflst		;set state=4
	call	oflcb		;clear byte buffer
	pop	psw
	jmp	oflad		;accum. 1st digit of hi address byte
ofl4:	cpi	5		;test if state 4
	jnc	ofl5		;no, skip
	inr	a
	sta	oflst		;set state=5
	pop	psw
	call	oflad		;accum. 2nd digit of hi address byte
	sta	oflla+1		;save hi byte of load address
	jmp	ofluc		;update checksum
ofl5:	cpi	6		;test if state 5
	jnc	ofl6		;no, skip
	inr	a
	sta	oflst		;set state=6
	call	oflcb		;clear byte buffer
	pop	psw		;accum. 1st digit of low address byte
	jmp	oflad
ofl6:	cpi	7		;test if state 6
	jnc	ofl7		;no, skip
	inr	a
	sta	oflst		;set state=7
	pop	psw		;accum. 2nd digit of low address byte
	call	oflad
	sta	oflla		;save low byte of load address
	push	psw		;save for later
	lda	oflfrr		;see if first address
	cpi	0
	jnz	ofl61		;no, skip this...
	inr	a		;say not first time
	sta	oflfrr
	lhld	oflla		;get load address
	shld	addr		;save for user
ofl61:	pop	psw		;restore low byte
	jmp	ofluc		;update checksum
ofl7:	cpi	9		;test if state 8
	jnc	ofl8		;no, skip
	inr	a
	sta	oflst		;set state=9
	pop	psw		;ignore record type
	ret
ofl8:	cpi	10		;test if state 9
	jnc	ofl10		;no, skip
	inr	a
	sta	oflst		;set state=10
	lda	oflbc		;get byte count
	ora	a
	jz	ofl9		;skip if no data bytes in record
	call	oflcb		;clear byte buffer
	pop	psw
	jmp	oflad		;accum. 1st digit of next data byte
ofl9:	mvi	a,11		;set state=12 (all data bytes read)
	sta	oflst
	jmp	ofl11
ofl10:	cpi	11		;test if state 10
	jnc	ofl11		;no, skip
	dcr	a		;loop at previous state
	sta	oflst
	pop	psw
	call	oflad		;accum. 2nd digit of next data byte
	push	h
	lhld	oflla
	mov	m,a		;load byte
	inx	h		;increment load address
	shld	oflla
	pop	h
	call	ofluc		;update checksum
	lda	oflbc		;decrement byte count
	dcr	a
	sta	oflbc
	ret
ofl11:	cpi	12		;test if state 11
	jnc	ofl12		;no, skip
	inr	a
	sta	oflst		;set state=12
	call	oflcb		;clear byte buffer
	pop	psw
	jmp	oflad		;accum. 1st digit of checksum
ofl12:	pop	psw
	call	oflad		;accum. 2nd byte of checksum
	call	ofluc		;add into new checksum
	sta	oflce		;save as checksum
	xra	a
	sta	oflst		;back at state 0
	ret
oflcb:	xra	a		;clear byte buffer
	sta	oflbb
	ret
oflad:	call	legal		;convert to binary
	push	b		;accum. hex digit
	mov	b,a
	lda	oflbb		;fetch byte buffer
	add	a		; * 16
	add	a
	add	a
	add	a
	add	b		;add in new digit
	sta	oflbb		;replace old value
	pop	b
	ret
ofluc:	push	h		;update checksum
	lxi	h,oflcs		;point to checksum
	add	m		;add in new byte
	mov	m,a
	pop	h
	ret	
;
; this is the console driver
;
;
; racc - read ascii character from console
;
	if	not cpmio	;if hardware I/O
racc:	in	consta		;read status port
	ani	rda		;test rda bit
	jz	racc
	in	condat		;get data
	ani	7fh		;strip parity bit
	endif
;
	if	cpmio		;if running w/ CP/M I/O calls
racc:	push	b		;save user registers
	push	d
	push	h
	mvi	a,conin		;perform conin function
	call	cbios
	pop	h		;restore registers
	pop	d
	pop	b
	endif			;fall thru to case changer
;
	cpi	61h		;translate to u/c
	rm
	cpi	7bh
	rp
	sui	20h
	ret
;
; wacc - write ascii character to console, handling ^S/^Q
;
	if	not cpmio	;if hardware I/O
wacc:	push	psw		;save user's A
	in	consta		;get status
	ani	rda		;see if something typed
	jz	wacc1		;no, do straight output
	in	condat		;yes, get char.
	cpi	cntrls		;test for ^S
	jz	wacc2		;yes, do wait loop
wacc1:	in	consta		;get status again
	ani	tbe		;test tbe
	jz	wacc+1		;not there, wait
	pop	psw		;restore character
	out	condat		;send it
	ret			;exit
wacc2:	in	consta		;wait for ^Q
	ani	rda
	jz	wacc2		;wait for a key
	in	condat
	cpi	cntrlq		;test if ^Q
	jnz	wacc2		;no, get another
	jmp	wacc1		;return & output
	endif
;
	if	cpmio		;if running w/ CP/M I/O calls
wacc:	push	b		;save user registers
	push	d
	push	h
	push	psw
	mov	c,a		;put char in C
	mvi	a,conout	;do conout function
	call	cbios
	pop	psw
	pop	h		;restore user registers
	pop	d
	pop	b
	ret
	endif
;
	if	cpmio		;link to CP/M I/O handler
;
; cbios - call bios routine
;
cbios:	push	h
	push	d
	lhld	wboot+1
	sui	3
	mov	e,a
	mvi	d,0
	dad	d
	pop	d
	xthl
	ret
	endif
;
; delay - delay @(A) milliseconds
;
delay:	mvi	c,0b0h		;time constant (2 mhz)
delay1:	dcr	c
	jnz	delay1
	dcr	a
	jnz	delay
	ret
;
; const - see if user has typed anything
;
	if	not cpmio	;if using hardware I/O calls
const:	in	consta		;get status
	ani	rda		;test rda
	rz			;A=0=nothing
	mvi	a,0ffh		;A=FF=something
	ret
	endif
;
	if	cpmio		;if using CP/M I/O calls
const:	push	h		;save registers
	push	d
	push	b
	mvi	a,conrdy	;issue cbios call
	call	cbios
	pop	b
	pop	d
	pop	h
	ווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווווin	rdrdat		;get data
	ani	7fh		;strip parity
	ret			;exit to caller
;
; message text area - all texts go here
;
;
cs:	db	27,28,0		;clear screen code, terminated w/ a zero
;
signon:	db	'SYSMON 02.00 - 04/10/81',cr,lf
;
	if	help
	db	'Type ? for command summary...',cr,lf
	endif
;
	db	0
;
badcom:	db	bell,'Unknown command',cr,lf,0
;
badstx:	db	bell,'Syntax error in command',cr,lf,0
;
badval:	db	bell,'Non-hexadecimal value entered',cr,lf,0
;
moveto:	db	'Move to? ',0
;
fillw:	db	'Fill with? ',0
;
twopar:	db	bell,'Two parameters are required',cr,lf,0
;
wutdsk:	db	bell,'Disk drive not ready...',cr,lf,0
;
booter:	db	bell,'Boot error, controller status: ',0
;
nosys:	db	bell,'No system image on disk...',cr,lf,0
;
baddr:	db	bell,bs,' <-- Bad address entered...',cr,lf,0
;
askfil:	db	'Filename? ',0
;
askply:	db	cr,lf,'Press PLAY on tape unit...',cr,lf,0
;
found:	db	cr,lf,'Found ',0
;
loadng:	db	cr,lf,'Loading...',0
;
execad:	db	cr,lf,'Execute address: ',0
;
cksume:	db	cr,lf,bell,'Checksum error!',cr,lf,0
;
regmsg:	db	'Register set:'
	db	cr,lf,'  PC: ',0
regsp:	db	cr,lf,'  SP: ',0
regaf:	db	cr,lf,'  AF: ',0
regbc:	db	cr,lf,'  BC: ',0
regde:	db	cr,lf,'  DE: ',0
reghl:	db	cr,lf,'  HL: ',0
;
ilsum1:	db	cr,lf,bell,'Illegal SVC (',0
ilsum2:	db	') from user PC: ',0
;
	if	help
hlpmsg:	db	cr,lf,'                  *** Command Summary ***',cr,lf,lf
	db	'  B               - Boot operating system from disk.',cr,lf
	db	'  D [start] [end] - Dump memory in ascii & hex from',cr,lf
	db	'                    [start] to [end], inclusive.',cr,lf
	db	'  E [address]     - Enter hex data beginning at [address].',cr,lf
	db	'                    Terminate w/ blank line.',cr,lf
	db	'  F [start] [end] - Fill memory with constant value. The',cr,lf
	db	'                    system will ask for the [constant].',cr,lf
	db	'  G [address]     - Jump to [address] and execute the code',cr,lf
	db	'                    there. Exit w/ a RET instruction.',cr,lf
	db	'  H               - Halt the computer.',cr,lf
	db	'  I [port]        - Input and display the data at [port].',cr,lf
	db	'  L               - Load diagnostic tape from tape unit.',cr,lf
	db	'                  - The system will ask for the [filename].',cr,lf
	db	'  M [from] [len]  - Move [len] bytes of data from address',cr,lf
	db	'                    [from]. The system will ask for the',cr,lf
	db	'                    [to] address.',cr,lf
	db	'  O [port] [data] - Output [data] to [port].',cr,lf
;
	if	testing
	db	'  Q               - Quit and return to operating system.',cr,lf
	endif
;
	db	'  R               - Display 8080 register set.',cr,lf
	db	'  S               - Perform load of diagnostics disk.',cr,lf
;
	if	testing
	db	'  T [call]        - Tests user SVC [call] with whatever is',cr,lf
	db	'                    currently in BC, DE, & HL.',cr,lf
	endif
;

	db	lf
	db	'  [addr]/         - Open memory location [addr] and display',cr,lf
	db	'                    its contents. Enter CR to return to the',cr,lf
	db	'                    prompt, LF to open the next location, ^',cr,lf
	db	'                    to open the previous one, or 2 hex dig-',cr,lf
	db	'                    its to replace the ones displayed.',cr,lf
	db	lf
	db	'  Arguments default to 100h except for M & F, which must be',cr,lf
	db	'explicitly entered. Do not enter the [], they are used only',cr,lf
	db	'for illustration. Arguments may be seperated by '','' or '' ''.',cr,lf
	db	lf
	db	0
	endif
;
	end
