;---------------------------------------------------------------------------
;
;   Altair Disk Boot Loader (DBL). Possibly version 3.2
;	This is an older version than the 4.1 DBL most typically seen.
;	This version differs from version 4.1 as follows:
;
;	1) Includes the cassette bootstrap loader for Extended BASIC v3.2
;	   at FF13h.
;
;	2) Does not check or wait for the disk to be ready before loading
;	   the head and looking for the first sector.
;
;	3) Verifies the head settled status bit is asserted before doing a 
;	   sector number search. This is not required since a sector true
;	   status is not asserted unless the head status is good anyway.
;
;	4) Performs an over-write check in case the loaded image over-writes
;	   the executing loader code. This logic, however, is probably 
;	   incorrect since it checks for over-writing the PROM location
;	   instead of the code in RAM. This check is not present at all
;	   in the 4.1 loader.
;
;	5) On failure, the error code is written only to the SIO port at 0/1
;	   The 4.1 PROM also writes to the 2SIO, PIO, 4PIO, saves the error
;	   code and address in RAM at addresses 0-2, and turns on the INTE
;	   LED on the front panel.
;
;   Disassembled by Mike Douglas, 9/17/2017
;
;-----------------------------------------------------------------------------

; Memory location equates

ROMLOC	equ	0FF00h		;location of PROM
RAMLOC	equ	04C00h		;location of image run in RAM

IMGOFF	equ	27h		;offset within PROM to the RAM image code
ROMOFF	set	ROMLOC-RAMLOC+IMGOFF	;offset from code in RAM to PROM

LOADADR	equ	0		;final address for payload

; Altair Floppy Disk Controller Equates

DSELECT	equ	08h		;drive select register
cDSLCT	equ	80h		;deselect drive

DCMD	equ	09h		;command register
cSTEPI	equ	01h		;step in
cSTEPO	equ	02h		;step out
cHLOAD	equ	04h		;head load
cHUNLD	equ	08h		;head unload
cINTEN	equ	10h		;interrupt enable
cINTDIS	equ	20h		;interrupt disable
cHCSON	equ	40h		;reduce head current switch
cWRTEN	equ	80h		;write enable

DSTATUS	equ	08h		;status register
sENWD	equ	01h		;enter new write data
sSTEPOK	equ	02h		;ok to step
sHDSTAT	equ	04h		;head status
sDSKEN	equ	08h		;disk is enabled
sINTEN	equ	20h		;processor interrupts enabled
sTRK0	equ	40h		;on track zero
sNRDA	equ	80h		;new read data available

DSECTOR	equ	09h		;sector register
sNEWSEC	equ	01h		;new sector flag (sector true)

DDATA	equ	0Ah		;data regsiter

; Console and cassette I/O equates

ACRCTL	equ	06h		;cassette control register
ACRDATA	equ	07h		;cassette data register

SIODATA	equ	01h		;SIO serial port data register

; Misc Equates

RETRIES	equ	16		;retry counter

;-----------------------------------------------------------------------
;  Start of code. The PROM is located at FF00, however, this code
;     is ORG'd such that the code that gets copies to RAM assembles
;     at the RAM execution address (4C00)
;-----------------------------------------------------------------------
	org	RAMLOC-IMGOFF

; Copy the code starting at ramImg from this PROM into RAM at 4C00
;   for execution, then jump to it.

start	lxi	h,ramImg+ROMOFF	;HL->ramImg to move in PROM
	lxi	d,RAMLOC	;DE->destination in RAM
	mvi	c,IMGEND-ramImg	;C = count of bytes to move

moveLp	mov	a,m		;move bytes from ROM to RAM
	stax	d
	inx	h
	inx	d
	dcr	c
	jnz	moveLp+ROMOFF	;repeat for all bytes

	jmp	ramImg		;jump to code in RAM

;----------------------------------------------------------------
;  Cassette bootstrap loader for Extended BASIC 3.2 is located
;     here at FF13h. The leader code (and length of the checksum
;     loader) is AEh which matches version 3.2 products.
;----------------------------------------------------------------
	lxi	h,04EAEh	;HL->destination, L is also leader byte

casLoop	lxi	sp,jmpStk+ROMOFF  ;(SP)->casLoop (ret jumps to here)
	in	ACRCTL		;data byte received?
	rar			;flag in bit 0
	rc			;no, jump back to casLoop

	in	ACRDATA		;get the next byte
	cmp	l		;still leader byte?
	rz			;yes, back to casLoop

	dcr	l		;decrement address and bytes left
	mov	m,a		;store loader in reverse order
	rnz			;back to casLoop

	pchl			;jump to the checksum loader just read
		
jmpStk	dw	casLoop+ROMOFF	;ret's to jump to casLoop

;----------------------------------------------------------------
;  ramImg - this is the code copied to RAM and executed to
;     read the boot sectors from the disk. This code assembles
;     and executes at 4C00.
;----------------------------------------------------------------
ramImg	di			;run with interrupts off
	lxi	sp,STACK	;init stack pointer

	xra	a		;select drive 0
	out	DSELECT

	mvi	a,cHLOAD	;load head
	out	DCMD

	jmp	chkTrk0		;go check for track 0

; home - seek to track zero

home	in	DSTATUS		;A=drive status
	ani	sSTEPOK		;is step OK status asserted?
	jnz	home		;no, keep waiting

	mvi	a,cSTEPO	;step out
	out	DCMD

chkTrk0	in	DSTATUS		;A=drive status
	ani	sTRK0		;at track zero?
	jnz	home		;no, keep stepping

	lxi	d,LOADADR	;DE->final read destination

; loadTrk - load a track, even sectors first, odd sectors second

loadTrk	mvi	b,0		;B=starting sector number

waitHd	in	DSTATUS		;A=drive status
	ani	sHDSTAT		;head status settled?
	jnz	waitHd		;no, keep waiting

; nextSec - load next sector

nextSec	mvi	a,RETRIES	;A=retry counter

readSec	push	psw		;save retry counter
	push	d		;save final destination
	push	b		;save sector number
	push	d		;save final destination again

	lxi	d,08086H	;E=read 134 bytes/sector, D not used
	lxi	h,dskBuf	;HL->disk buffer

waitSec	in	DSECTOR		;A=sector register
	rar			;check sector true bit
	jc	waitSec		;not asserted, keep waiting

	ani	01Fh		;get sector number alone
	cmp	b		;sector we want?
	jnz	waitSec		;no, keep looking

rdLoop	in	DSTATUS		;A=drive status
	ora	a		;msbit is data available flag
	jm	rdLoop		;no data, keep waiting

	in	DDATA		;A=byte from disk
	mov	m,a		;store in buffer
	inx	h		;point to next location
	dcr	e		;decrement byte count
	jz	secDone		;exit if byte count=0

	dcr	e		;reading two bytes per loop
	in	DDATA		;A=second byte from disk
	mov	m,a		;save it
	inx	h
	jnz	rdLoop		;loop until count=0

secDone	pop	h		;HL->final destination
	lxi	d,dskBuf+3	;DE->start of data in dskBuf
	lxi	b,0080h		;B=checksum (zero), c=128 bytes to move

; movLoop - move data from disk buffer to destination address
;    pointed to by DE

movLoop	ldax	d		;move byte from dskBuf to final location
	mov	m,a
	cmp	m		;is RAM ok where stored?
	jnz	memErr		;no, RAM error

	add	b		;update checksum
	mov	b,a

	inx	d		;bump pointers
	inx	h
	dcr	c		;decrement byte count
	jnz	movLoop		;repeat for 128 data bytes

; check for errors

	ldax	d		;next byte should be FFh marker
	cpi	0FFh
	jnz	movDone		;marker byte bad

	inx	d		;move to checksum byte
	ldax	d		;A=checksum read
	cmp	b		;compare to computed checksum

movDone	pop	b		;restore sector number just read
	xchg			;put destination pointer back in DE
	jnz	readErr		;exit with read error

	pop	psw		;trash destination address
	pop	psw		;restore retry counter in A

	lhld	dskBuf+1	;HL=max address to load (from sector header)
	push	d		;save current destination pointer

; Over-write check - This code should actually check if the destination buffer
;    pointer is running into the executing code in RAM (e.g., 4C00h). But instead,
;    it checks to see if it is running into the ROM at FF00h

	lxi	d,ROMLOC	;DE->ROM for over-write check
	call	cmpDEHL		;compare FF00 to destination pointer
	pop	d		;restore destination pointer
	jc	oWrtErr		;over-write error

	call	cmpDEHL		;compare dest ptr to specified end address
	jnc	done		;done if dest ptr >= specified end

	inr	b		;bump sector number by 2
	inr	b

	mov	a,b		;last even/odd sector on track?
	cpi	32
	jc	nextSec		;no, read next sector

	mvi	b,1		;assume odd sectors on track are next
	jz	nextSec		;yes, just finished last even sector

; stepNext - full track loaded, step in to next track

stepNxt	in	DSTATUS		;A=drive status
	ani	sSTEPOK		;is step OK status asserted
	jnz	stepNxt		;no, keep waiting

	mvi	a,cSTEPI	;step in one track
	out	DCMD
	jmp	loadTrk		;go read next track

done	mvi	a,cDSLCT	;deselect the drive
	out	DSELECT		
	jmp	LOADADR		;jump to the loaded code

; readErr - decrement retry count and try again. When retry count expired,
;    give up and display error code continuously.

readErr	pop	d		;restore destination pointer
	pop	psw		;A=retry counter
	dcr	a		;decrement retries
	jnz	readSec		;try until retries = 0

secErr	mvi	a,'C'		;A=checksum error code
	db	01h		;use LXI B to skip next two bytes

oWrtErr	mvi	a,'O'		;A=over-write error code
	db	01h		;use LXI B to skip next two bytes

memErr	mvi	a,'M'		;A=memory error code

	mov	b,a		;save error code in B
	mvi	a,cDSLCT	;deselect the drive
	out	DSELECT
	mov	a,b		;restore error code in A

errHang	out	SIODATA		;write error to SIO data port
	jmp	errHang		;die here

; cmpDEHL - Compare DE to HL

cmpDEHL	mov	a,d		;compare ms byte
	cmp	h
	rnz			;exit if not the same

	mov	a,e		;compare ls byte
	cmp	l
	ret

IMGEND	equ	$

;---------------------------------------------------------------------------
; disk buffer and stack
;---------------------------------------------------------------------------
dskBuf	db	0,0,0,0,0	;fill last 5 bytes of PROM with zero
	ds	129		;disk buffer is 134 bytes long

	ds	8		;reserve 8 bytes for stack
STACK	equ	$

	end
