;-----------------------------------------------------------------
;
;  HLOADSIO.ASM - Intel Hex File Loader
;
;    Hex file loader for an Altair 88-SIO serial port at I/O
;    address 0/1. This code is fits in a 256 byte 1702a EPROM.
;    The PROM will run at any 256 byte boundary without having to
;    re-assemble. The PROM automatically sizes RAM and relocates
;    itself and a stack to the top of RAM.
;
;    Jump to address zero in the PROM to run. Cycles in an
;    endless loop after file reception is complete.
;
;  Version     Date	Notes
;  -------  ----------	-------------------------
;    1.0    12/16/24    Mike Douglas  (Original)
;
;-----------------------------------------------------------------

;  ORG statement
;     Run-time relocation of addresses is done by replacing any
;     byte that matches the MSB of the ORG address with the MSB
;     of the destination RAM address. This requires that the value
;     of the ORG MSB never appears in the assembled code other
;     than as the MSB of an address. FD00 works well for this.

	org	0FD00h
ADRMARK	equ	($ SHR 8)	;address mark value		

; Equates

STACK0	equ	80h		;page zero stack

SIOCSR	equ	00h		;SIO control register
SIODR	equ	01h		;SIO data register

CR	equ	13
LF	equ	10

;--------------------------------------------------------------
;  Size RAM (position independent). Assumes page 0 exists,
;     then checks byte C9 in each page until a page is found
;     that is not RAM.
;--------------------------------------------------------------
	lxi	sp,STACK0	;setup a stack on page zero
	lxi	h,ret		;H=0, L=RET instruction
	push	h		;RET at STACK0-2
	call	STACK0-2	;puts addr of szLoop in stack RAM

; szLoop - check a byte in each page until RAM not found

szLoop	dcx	sp		;point SP to szLoop address
	dcx	sp		;    in stack memory

	inr	h		;move to next 256 byte page
	mov	a,m		;read from address in hl
	mov	b,a		;save original value in b
	cma			;form and write inverted value
	mov	m,a
	cmp	m		;read and compare
	mov	m,b		;restore original value
	rz	;jz szLoop

;--------------------------------------------------------------
;  Move PROM image to high RAM (position independent).
;     On entry, HL points within the 1st page AFTER end of RAM
;     and at the current stack pointer is the address of
;     szLoop from the loop above.
;--------------------------------------------------------------
	pop	b		;BC->szLoop in PROM
	mvi	c,notRelo AND 0ffH	;BC->notRelo in PROM

	dcr	h		;point HL inside last page of RAM
	xchg			;form RAM destination in DE
	mvi	e,movLoop AND 0ffH	;DE->movLoop in RAM

	lxi	h,0E9E1h	;H=PCHL,L=POP H
	push	h		;POP H, PCHL at STACK-4, STACK-3
	call	STACK0-4	;PROM movLoop in stack mem and HL
	
;  move code from PROM to RAM. If a byte matches the MSB of the 
;     assembled ORG address (i.e., it is an address MSB), then
;     it is replaced with the destination RAM MSB.

movLoop	dcx	sp		;point SP to MLOOP address
	dcx	sp		;    in stack memory

	mov	a,m		;Get next EPROM byte

	cpi	ADRMARK		;relocatable address byte?
	push	b		;put jump address on stack
	rnz 	;jnz notRelo
	pop	b		;remove unused jump address
	mov	a,d		;a=MSB of code image in RAM

notRelo	stax	d		;move byte to RAM

	inr	e		;bump pointers
	inr	l
	rnz 	;jnz movLoop  	copy to end of 256 byte page

; code image has been copied to high RAM and addresses relocated. Init
;   the stack pointer just below the code, then jump to the code.

	mov	h,d		;HL->start of RAM page
	mvi	l,init AND 0ffh	;HL->start of hex loader in RAM
	sphl			;stack grows down from start
	pchl			;jump to start
	
;------------------------------------------------------------
; Display message to send the file, then loop receiving 
;    and storing the hex file.
;------------------------------------------------------------
init	call	dspMsg		;display "Send file now"
	db	CR,LF,'Send File No','w'+80h

	mvi	c,0		;clear echo character flag

; rcvLine - receive a hex file line

rcvLine	call	dspMsg		;new line
	db	CR,LF+80h

wtMark	call	getChar		;read next character
	sui	':'		;record marker?
	jnz	wtMark		;no, keep looking

; Have start of new record. Get the byte count in b and the load
;   address in hl. 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	inByte		;input two hex digits (byte count)
	mov	a,e		;test for zero byte count
	ora	a
	jz	done		;count of 0 means end

	mov	b,e		;B = byte count on line

	inr	c		;set echo flag for address bytes
	call	inByte		;get MSB of address
	mov	h,e		;H = address MSB
	call	inByte		;get LSB of address
	mov	l,e		;L = address LSB
	dcr	c		;clear echo flag

	call	inByte		;ignore/discard record type

; Receive the data bytes of the record and move to memory

data	call	inByte		;read a data byte (2 hex digits)
	mov	m,e		;store in memory
	inx	h
	dcr	b
	jnz	data

	call	inByte		;read and add checksum
	jz	rcvLine		;checksum good, receive next line

; error - hex conversion or checksum error

error	call	dspMsg		;display "error"
	db	CR,LF,'Error',CR,LF+80h

	jmp	die	

; done - hex file loaded without error

done	call	dspMsg		;display "done"
	db	'Done',CR,LF+80h

; die - stay here in an endless loop discarding any more
;    characters that may be received.

die	in	SIODR		;clear out any garbage
	jmp	die

;-----------------------------------------------------------
; inByte - read two ascii hex digits and return binary
;    value in e. If a conversion error occurs, exit
;    directly to error label above.
;
; returns e = received byte
;	  z flag set if current checksum is zero
; clobbers d, e
;-----------------------------------------------------------
inByte	call	getChar		;get a character
	call	asc2Bin
	jc	error

	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
	jc	error

	add	e		;combine msn and lsn
	mov	e,a		;save in E
	add	d		;add character to checksum
	mov	d,a
	ret

;-------------------------------------------------------------
; asc2Bin - ASCII hex digit to binary conversion. Digit
;    passed in a, returned in a. Carry returned clear
;    for valid conversion, carry set for error.
;-------------------------------------------------------------
asc2Bin	sui	30h		;'0' to 0
	rc			;error - below zero

	cpi	10		;0-9 ?
	jc	ascExit		;yes

	sui	11h		;A-F to 0-5
	rc			;error - below A

	adi	10		;convert back to A-F
	cpi	16		;too big? (carry clear if so)

ascExit	cmc			;complement carry
	ret

;-------------------------------------------------------------
; getChar - read a character from the 6850 (2SIO), and
;    return it in a. The character is also echoed
;    back out the serial port if the echo flag (c)
;    is set (non-zero)
;-------------------------------------------------------------
getChar	in	SIOCSR		;wait for a character
	rrc	
	jc	getChar

	mov	a,c		;echo flag (c) set?
	ora	a
	jz	noEcho		;no, don't echo

	in	SIODR		;read the character
	out	SIODR		;echo it
	ret

noEcho	in	SIODR		;read the character
	ret

;------------------------------------------------------------
; dspMsg - display in-line message. String terminated by byte
;      with msbit set.
;------------------------------------------------------------
dspMsg	pop	h		;hl->string to display

dspLoop	in	SIOCSR		;wait for OK to transmit
	rlc
	jc	dspLoop

	mov	a,m		;a=next character to display
	ani	7fh		
	out	SIODR		;display it

	ora	m		;MSB set? (last byte)
	inx	h		;point to next character
	jp	dspLoop		;no, keep looping

	pchl			;return past the string

	end
