;==============================================================
; Hard Disk Turnkey Boot Loader (HD-TBL)
;
; 88-HDSK Hard Disk Loader and Hexadecimal Monitor
;
; This is disassembled source code for two 1702A ROMs, which
; are addressed at FC00H and FD00H. The first ROM contains the
; hard-disk loader. Entry at FC00H will initiate a hard-disk
; load from an 88-HDSK subsystem.
;
; The second ROM contains a hexadecimal version of the TURMON
; monitor program, with entry at FD00H. This monitor has an
; additional 'L' load command, which jumps to the hard disk
; load routine in the first ROM. The loader program uses print
; routines and strings that are stored in the second ROM, so
; the two ROMs need to be assembled together.
;
; This is written to assemble with ASM by Digital Research
;
; Supported Monitor Commands:
;  L: load from 88-HDSK
;  Maaaa: modify memory at address aaaa hex
;  Jaaaa: Jump to address aaaa hex
;
;  Disassembled by Martin Eberhard 2013/09/03
;==============================================================

;88-HDSK ports (The interface board is actually an 88-4PIO.)

CTLRDY	equ	0A0h	;IN: controller ready (bit7)
ERRFLG	equ	0A1h	;IN: error flags, reset ctlr ready
CTLACK	equ	0A2h	;IN: controller ACK (bit 7)
RESACK	equ	0A3h	;IN: reset command ack
CMDHI	equ	0A3h	;OUT: command high byte
CDSRDY	equ	0A4h	;IN: Ctlr ready for data/stat
DDATA	equ	0A5h	;IN: Disk data or status
CMDLO	equ	0A7h	;OUT: command low byte

;88-HDSK Commands

CSEEK	equ	00h	;Bits 15:12 = 0000b
			;Bits 11:10 = Unit #
			;Bits  9:0  = Cylinder #

CRDSEC	equ	30h	;Bits 15:12 = 0011b
			;Bits 11:10 = Unit #
			;Bits  9:8  = Buffer #
			;Bits  7:5  = Head #
			;Bits  4:0  = Sector #

CRDBUF	equ	50h	;Bits 15:12 = 0101b
			;Bits 11:10 = not used
			;Bits  9:8  = buffer #
			;Bits  7:0  = # bytes (mod 256) to transfer

;ERRFLG error bits

ERDNR	equ	01h	;drive not ready
ERBADS	equ	02h	;illegal sector
ERSCRC	equ	04h	;CRC error during sector read
ERHCRC	equ	08h	;CRC error during header read
ERSWRG	equ	10h	;header has wrong sector
ERCWRG	equ	20h	;hesder has wrong cylinder
ERHWRG	equ	40h	;header has wrong head
WPROT	equ	80h	;Write Protect
ERMASK	equ	7Fh	;all the error bits

;88-HDSK Limits

OSOFF	equ	40	;Page 0 offset to opsys parameters
HDSPT	equ	24	;sectors per track

;Memory allocation
LDADDR	equ	0		;Always load at this address
STACK	equ	0C000h		;First address above the stack
HDTBL1	equ	0FC00h		;org of disk loader ROM
HDTBL2	equ	0FD00h		;org of monitor ROM

;ASCII characters

CR	equ	0Dh
LF	equ	0Ah

;==============================================================
; Start of first ROM (HD-TBL 1)
	org	HDTBL1
;==============================================================

	lxi	sp,STACK	;Set up stack
	call	INIT		;Initialize 2SIO & HDSK
;----------------------------
; Execute 'L' (Load) command
;----------------------------
DOLCMD:	call	PCRLF		;print CR-LF

	in	CTLRDY		;is the controller ready?
	rlc			;MSB is ready bit
	jc	GPARMS		;Y:no need to reset it

	lxi	d,RSTSTR	;RESET CTR message
	call	PRINTF

;-------------------------------------------
; Read Page 0 (the Pack Descriptor Page) to
; get the opsys parameters.
; Bytes 41:40 = Initial page number
; Bytes 43:42 = sector count
;-------------------------------------------
GPARMS:	lxi	h,0		;Seek Page 0
	call	GTPAGE

;read and discard everything until we get to the opsys params

	mvi	b,OSOFF

OFFLP:	in	DDATA
	dcr	b
	jnz	OFFLP

;Get the only 4 bytes we care about

	in	DDATA		;Get low byte of start page
	mov	l,a		;..into L
	in	DDATA		;Get high byte of start page
	mov	h,a		;..into H

	in	DDATA		;Get low byte of page count
	mov	e,a		;..into E
	in	DDATA		;Get high byte of page count
	mov	d,a		;..into D
	push	d		;Save page count on stack

;read and discard the rest of the page

	mvi	b,256-OSOFF-4

EATLP:	in	DDATA
	dcr	b
	jnz	EATLP

	lxi	d,LODSTR	;Tell user we are loading
	call	PRINTF		;LOADING

;------------------------------------------------
; Read (SP) pages from disk, starting at page HL
; into memory starting at address 0000
;------------------------------------------------
	lxi	d,LDADDR	;initial memory address

PAGELP:	call	GTPAGE		;Seek page # HL


;load 256 bytes of buffer data into memory at DE
	mvi	b,0		;byte counter

BYTELP:	in	DDATA		;get a data byte
	stax	d		;write it to RAM
	inx	d		;next address
	dcr	b		;bump counter
	jnz	BYTELP		;until done


	inx	h		;Next page
	xthl			;save, look at page count
	dcx	h		;bump page count
	mov	a,h		;done?
	ora	l
	jz	XEQUTE		;Y: go execute address 0000

	xthl			;save count, get page #
	jmp	PAGELP		;Next sector

;------------------------------------------
; Go execute what we loaded, at address HL
;------------------------------------------
XEQUTE:	pchl			;go execute the loaded code

;-----------------------------------
; Seek and read page HL into buffer
;-----------------------------------
GTPAGE:	push	h		;Save requested page
	push	d		;Save regs
	push	b

;-----------------------------------------------
; Compute cylinder and sector from Page # in HL
;  HL := HL / (2*HDSPT)
;   B := HL MOD (2*HDSPT)
;-----------------------------------------------
	mvi	b,2*HDSPT	;2 "sectors" per track - 1 on each side
	lxi	d,0010h		;D=0 (initial sector #)
				;E=10h (loop counter)

DIV1:	dad	h		;shift HL left 1
	mov	a,d		;get incomplete sector #
	ral			;Times 2
	cmp	b		;Bigger than max?
	jc	DIV2		;N: shift in 0

	sub	b		;Y: modulo sector number
	inr	l		;and shift in 1		,

DIV2:	mov	d,a		;save incomplete sector #

	dcr	e		;loop 16 times
	jnz	DIV1

	mov	b,a		;save sector #

;--------------------------------------------------
;  B = sector number, where a "sector" is actually
;      2 sectors, one for each head
; HL = cylinder #
;--------------------------------------------------


;------------------
; Seek cylinder HL
;------------------
	mov	a,l		;L= cylinder # <7:0>
	out	CMDLO		;
	in	RESACK		;clear ack
	mov	a,h		;H= cylinder # <9:8>

	ori	CSEEK		;Issue SEEK command (00)
	out	CMDHI
	call	WATRDY		;wait for seek to compete

;---------------------------------------------
; switch platters if sector # > sectors/track
;---------------------------------------------
	mov	a,b		;get sector #
	cpi	HDSPT		;past end of side 0?
	jc	RDSECT		;N: sector number is good

	sui	HDSPT		;Next track starts at 0
	adi	20h		;Other head 

;------------------------------------------
; Read sector A into controller's buffer 0
;------------------------------------------
RDSECT:	out	CMDLO		;A<5>=side, A<4:0>=sector #
	in	RESACK		;clear ack
	mvi	a,CRDSEC	;Issue Read Sector command
	out	CMDHI
	call	WATRDY		;Wait for command to complete

	in	RESACK		;reset command acknowledge
	in	DDATA		;flush data port

;-------------------------------------------------------
; Issue command to read 256 bytes from the controller's
; buffer 0, and return from subroutine
;-------------------------------------------------------
	xra	a		;transfer 256 bytes
	out	CMDLO
	mvi	a,CRDBUF	;Read buffer command
	out	CMDHI

RDYWAT:	in	CDSRDY		;Wait for data port to be ready
	rlc
	jnc	RDYWAT

	pop	b		;Restore all registers
	pop	d
	pop	h
	ret

;---------------------------------------------
; Subroutine: Wait for controller to be ready
;---------------------------------------------
WATRDY:	in	CTLRDY		;Is the controller ready? 
	rlc			;look at msb
	jnc	WATRDY		;N: keep waiting

	in	ERRFLG		;reset controller ready flag
	ani	ERMASK		;any errors?
	rz			;N: happy return

;-----------------------------------------
; Report load error and quit
; On Entry: A=error code
; Note: this leaves the stack hosed. Many
; bad loads will cause stack overflow.
;-----------------------------------------

	lxi	d,LERSTR	;LOAD ERROR
	call	PRINTF
	call	PRBYTE		;print error code
	call	PCRLF
	jmp	MAIN		;done

;-----------------------------------------
; Subroutine: Initialize the 88-HDSK
; (the interface is actually an 88-4PIO.)
;-----------------------------------------
HDINIT:	xra	a
	out	0a0h		;Select port 0A DDR 
	out	0a2h		;Select port 0B DDR
	out	0a4h		;Select port 1A DDR
	out	0a6h		;Select port 1B DDR
	out	0a1h		;Port 0A is an input port
	out	0a5h		;Port 1A is an input port
	cma
	out	0a3h		;Port 0B is an output port
	out	0a7h		;Port 1B is an output port

	mvi	a,2ch		;Set up input port handshakes
	out	0a0h
	out	0a4h
	out	0a6h		;and also output port 1B handshakes

	mvi	a,24h		;Set up port 0B handshakes
	out	0a2h

	in	ERRFLG		;clear Controller Ready bit
	ret

;==============================================================
; Start of second ROM (HD-TBL 2)
	org	HDTBL2
;==============================================================

	lxi	sp,STACK	;Initialize stack
	call	INIT		;Initialize 2SIO & 88-HDSK

;---------------------------------------
; Main monitor loop - scan for commands
;---------------------------------------
MAIN:	call	PCRLF		;Print a CR-LF
	mvi	a,'.'		;Print prompt on 2SIO
	call	PRINTA

	call	GETCHR		;Get and echo a console chr
	cpi	'L'		;L command?
	jz	DOLCMD		;Y: go (to other ROM) to do it

	cpi	'M'		;M command?
	jz	DOMCMD		;Y:Go do M command

	cpi	'J'		;J command?
	jnz	MAIN		;ignore anything else

;--------------------------
; Process J (jump) command
;--------------------------
	call	GTADDR		;get jump address from user
	pchl			;and go there

;--------------------------------------------------
; Process M (Modify Memory) command
;--------------------------------------------------
DOMCMD:	call	PSPACE		;Print a space
	call	GTADDR		;get address from user

	db	3eh		;mvi a opcode skips next byte

NXTADR:	inx	h		;bump address in HL

	call	PCRLF		;Print a CR-LF
	mov	a,h		;Print address - high byte first
	call	PRBYTE
	mov	a,l		;then the low byte
	call	PRBYTE
	call	PSPACE		;a space before the data

	mov	a,m		;read existing memory data
	call	PRBYTE		;and print it
	call	PSPACE

	call	GTBYTE		;get user input for this address
	jc	NXTADR		;Carry means don't change it

	mov	m,a		;write user input into memory
	cmp	m		;successful write?
	jz	NXTADR		;Y: keep going

;--------------------------------------------
; Bad input from user. Print? and start over
; Note that this leaves the stack hosed.
; Many typos will cause stack overflow.
;--------------------------------------------
BADINP:	mvi	a,'?'		;brief error message
	call	PRINTA
	jmp	MAIN

;---------------------------------------------------------
; Subroutine: Get a 4-character address from user into HL
;---------------------------------------------------------
GTADDR:	call	GTBYTE		;get address high byte
	mov	h,a		;into H
	call	GTBYTE		;get address low byte
	mov	l,a		;into L
	ret			;result in HL

;------------------------------------------
; Subroutine: Get a hex byte from the user
;------------------------------------------
GTBYTE:	call	GETCHR
	cpi	20h		;space means don't change the value 
	stc			;carry means we got a space
	rz			;done getting if so

	call	HX2BIN		;convert first chr to binary
	rlc			;and put it in the high nibble
	rlc
	rlc
	rlc
	mov	c,a		;temp save while we get next chr
	call	GETCHR		;get 2nd hex chr
	call	HX2BIN		;and convert to binary
	ora	c		;combine nibbles
	ret			;result in A, carry cleared

;------------------------------------------
; Subroutine: Convert A from hex to binary
;------------------------------------------
HX2BIN:	cpi	'0'		;below 0?
	jc	BADINP		;Y: bad input

	cpi	'9'+1		;Above 9?
	jc	H2BDON		;N: just about done

	ani	5fh		;convert to upper case
	cpi	'A'		;below A?
	jc	BADINP		;Y: bad input

	cpi	'F'+1		;Above F?
	jnc	BADINP		;Y: bad input

	sui	7		;bring A-F down

H2BDON:	ani	0fh		;strip everything else off
	ret			;result in A

;------------------------------------
; Subroutine Print A as 2 hex digits
;------------------------------------
PRBYTE:	push	psw		;save while we do gigh nibble
	rlc			;push high nibble to low nibble
	rlc
	rlc
	rlc
	call	PRNIBL		;Print high nibble as ascii hex
	pop	psw		;recover low nibble

; fall into PRNIBL

;-----------------------------------------------
; Subroutine Print A<3:0> as an ASCII hex digit
;-----------------------------------------------
PRNIBL:	ani	0fh		;strip off high nibble
	cpi	10		;0-9?
	jc	NIBDON		;Y: almost done
	adi	7		;ascii offset for 'A'

NIBDON:	adi	'0'		;convert to ascii
	jmp	PRINTA		;and print it

;------------------------------------------
; Subroutine: Initialize 88-2SIO & 88-HDSK
;------------------------------------------
INIT:	mvi	a,3		;ACIA Reset
	out	10h		;Control Port
	mvi	a,11h		;2 stop bits, /16
	out	10h		;Control Port
	jmp	HDINIT		;initialize 88-HDSK

;------------------------------------------
; Subroutine: Print string at DE, which is
; terminated by bit 7 set in the last chr
;------------------------------------------
PRINTF:	push	psw		;don't trash A or flags
PFLOOP:	ldax	d		;get string chr
	call	PRINTA		;and print it
	inx	d		;next chr
	ani	80h		;end of string?
	jz	PFLOOP		;N: keep going

	pop	psw		;restore A
	ret			;done

;-------------------------
; Subroutine: Print CR-LF
;-------------------------
PCRLF:	mvi	a,0dh		;CR
	call	PRINTA
	mvi	a,0ah		;LF
	jmp	PRINTA

;--------------------------------------------------
; Subroutine: get & echo a character from the 2SIO
;  Received character is returned in A
;--------------------------------------------------
GETCHR:	in	10h		;Wait for chr available
	rrc
	jnc	GETCHR

	in	11h		;Get the chr
	ani	7fh		;Strip parity
	db	0DAh		;JC opcode skips PSPACE, never taken

;the above skip falls into PRINTA to echo

;---------------------------
; Subroutine: Print a space
;---------------------------
PSPACE:	mvi	a,' '

;fall into PRINTA

;------------------------------------
; Subroutine: Print A on 2SIO port 0
;------------------------------------

PRINTA:	push	psw		;temp save chr
PAWAIT:	in	10h		;Wait for 2SIO to be ready
	ani	2
	jz	PAWAIT		;Wait until transmitter is empty
	pop	psw		;recover chr
	out	11h		;and send it
	ret

;------------------------
; MSB-terminated Strings
;------------------------
RSTSTR:	db	'RESET CTL',CR,LF+80H
LODSTR:	db	'LOADING',CR,LF+80H
LERSTR:	db	'LOAD ERROR',':'+80H

	end


