; This is a reverse-engineering of the boot rom for the N* disk controller.
; Overall, this is what is going on:
;
;    1) The 256B boot prom, which contains this here code,
;       does not know what address the board containing has
;       been mapped to in the system.  It copies itself
;       down to the first page of memory, from 00030H to
;       00FFH, and begins execution.
;
;    2) It spins up the disk, steps the read head to track 0,
;       sector 4.  Sector 4 is conventionally where the boot
;       sector is on northstar disks.
;
;    3) The first byte of sector 4 contains the page address
;       of where the boot process should occur.  The remaining
;       511 bytes of the sector are copied to that address.
;
;    4) We jump to byte 10 of the just-loaded boot sector,
;       where a second-level bootstrap occurs.
;
;
; Just about all of it is understood except for a few things:
;
;     Down just after the W870 section a test is made to see
;     if the 0'th track has been recorded in double density.
;     If not, we step in one track and wait for sector 8 instead
;     of sector 4.  Huh?
;
;     If we really are in single density mode, the PAGE1/PAGE2
;     code blindly reads 512 bytes, plus a checksum byte.  But
;     single density mode only allows 256B plus 1 check byte
;     per sector, so this code violate two things: reading
;     across a sector boundary (OK, maybe the uP just is put
;     in a wait state for the inter sector gap), and what about
;     the other checksum byte?
;
;     The code surrounding "FILL59:" writes four bytes to the start
;     of the code we are loading.  Why?  It looks like we don't
;     execute that code anyway, as we begin execution at start+10.
;
;     What is the "RDT" gibberish?  Is is really just copyright
;     protection or programmer in-joke?


; ------------------------------------------------------------
; IO port mapping from north star disk controller
;
; The following docs are lifted more or less verbatim from
; NorthStar document "Micro-disk system MDS-A-D Double Density"

; Case 0: PROM addressing (typically E800H)
;
;  +----+----+----+----+----+----+----+----+
;  |             PROM Address              |
;  +----+----+----+----+----+----+----+----+
;
; Read byte from the 256 bytes of PROM.
;
;
; Case 1: Write byte of data (typically E900H)
;
;  +----+----+----+----+----+----+----+----+
;  |                 Data                  |
;  +----+----+----+----+----+----+----+----+
;
; Write a byte of data to the disk.  Wait if the write shift
; register is not empty.  The low order 8 bits specify the
; byte to be written.
;
;
; Case 2: Controller Orders (typically EA00H)
;
;  +----+----+----+----+----+----+----+----+
;  | DD | SS | DP | ST |        DS         |
;  +----+----+----+----+----+----+----+----+
;
; Load 8-bit order register from low order 8 address bits.
;
; DD  Controls density on write.
;     DD=1 for double density.
;     DD=0 for single density.
;
; SS  specifies the side of a double-sided diskette.  The
;     bottom side (and only side of a single-sided diskette)
;     is selecte when SS=0.  The second (top) side is
;     selected when SS=1.
;
; DP  has shared use.  During stepping operations, DP=0
;     specifies a step out and DP=1 specifies a step in.
;     During write operations, write precompensation is
;     invoked if and only if DP=1.
;
; ST  controls the level of the head step signal to the disk
;     drives.
;
; DS  is the drive select field, encoded as follows
;
;        0=no drive selected
;        1=drive 1 selected
;        2=drive 2 selected
;        4=drive 3 selected
;        8=drive 4 selected
;
;
; Case 3: Controller Command (typically EB00H)
;
;  +----+----+----+----+----+----+----+----+
;  |        DM         |        CC         |
;  +----+----+----+----+----+----+----+----+
;
; Perform a disk controller command.  The commands are
; specified by the 8 low order address bits.
;
; DM   The DM field controls what gets multiplexed onto the
;      DI bus during the command.
;
;         1=A-status
;         2=B-status
;         3=C-status
;         4=Read data (may enter wait state)
;
; CC   Command code.
;
;         0=no operation
;         1=reset sector flag
;         2=disarm interrupt
;         3=arm interrupt
;         4=set body (diagnostic)
;         5=turn on drive motors
;         6=begin write
;         7=reset controller, deselect drives, stop motors
;
; DISK CONTROLLER STATUS BYTES
;
; There are three status bytes that can be read on the Data Input
; Bus.
;
;     A-Status
;          +----+----+----+----+----+----+----+----+
;          | SF | IX | DD | MO | WI | RE | SP | BD |
;          +----+----+----+----+----+----+----+----+
;
; SF   Sector Flag: set when sector hole detected, reset by
;      software.
;
; IX   Index Detect: true if index hole detected during previous
;      sector.
;
; DD   Double Density Indicator: true if data being read is encoded
;      in double density.
;
; MO   Motor On: true while motor(s) are on.
;
; WI   Window: true during 96-microsecond window at beginning of
;      sector.
;
; RE   Read Enable: true while phase-locked loop is enabled.
;
; BD   Body: set when sync character(s) is detected.
;
; SP   Spare: reserved for future use.
;
;
;     B-Status
;          +----+----+----+----+----+----+----+----+
;          | SF | IX | DD | MO | WR | SP | WP | T0 |
;          +----+----+----+----+----+----+----+----+
;
; SF, IX, DD, MO, SP: same as A-Status
;
; WR   Write: true during valid write operation.
;
; WP   Write Protect: true while the diskette installed in the
;      selected drive is write protected.
;
; T0   Track 0: true if selected drive is at sector 0.
;        [ NOTE: I think this is in error: it is true if the
;                head for the selected drive is at track 0,
;                not sector 0.  This isn't based on any
;                experiment, just the name and reason.  ]
;
;     C-Status
;          +----+----+----+----+----+----+----+----+
;          | SF | IX | DD | MO |        SC         |
;          +----+----+----+----+----+----+----+----+
;
; SF, IX, DD, MO: same as A-Status
;
; SC   Sector Counter: indicates the current sector position.


; DISK CONTROLLER DATA FORMAT
;
; Each diskette has 35 tracks of data.  Each track is divided into
; 10 sectors.  The rotational position of the beginning of the
; sectors is marked by sector holes in the diskette.  Each sector
; is recorded using the following format.  This information is
; recorded starting about 96 microseconds after the sector hole is
; detected.
;
;                       Single Density  Double Density
;        Zeros             16 bytes        32 bytes
;        Sync Char(FB)      1 byte          2 bytes
;        Data             256 bytes       512 bytes
;        Check Char         1 byte          1 byte
;
; [ NOTE: a bit further on the docs claim 15/31 leading 00H
;         bytes for each sector, not 16/32. ]
;
; The check character is computed iteratively by setting it to zero
; and then exclusive ORing each successive data byte value with the
; current value of the check character and left cycling the result.


; This is a tricky boot prom.  Because the disk controller can
; be configured to appear at a number of locations in the 8080
; memory space, the PROM starts off by copying itself in a
; position independent way down to location 0000H in the system.
; it them jumps to the new copy and subsequent code is then free
; to use absolute jumps.
;
; this code is assembled at 0000H, but keep in mind that it really
; is initially executing at a higher address.

	ORG	0000H


; I must admit, I don't completely understand what is going on here.
; There are a lot of dead variables here.  Perhaps there it is some
; type of copyright tomfoolery, but it doesn't seem like it.

;			  vvvv actual address
BOOT:	MOV	B,H	; ??00: huh?  appears to be dead
	LXI	B,0101H	; ??01: BC <= 0101H
	ADD	D	; ??04: huh?  appears to be dead
	ADD	H	; ??05: huh?  appears to be dead
	MOV	A,B	; ??06: A <= 01H
	ANI	07H	; ??07: A <= 01H  (huh?)
	MOV	C,A	; ??09: C <= 01H  (still)
	NOP		; ??0A

; write a piece of code into page 00H of RAM.
;    0028: C9		RET
;    0029: 1A		LDAX	D
;    002A: 77		MOV	M,A
;    002B: 13		INX	D
;    002C: 2C		INR	L
;    002D: C2 29 00	JNZ	$0029

	LXI	SP,0030H	; ??0B
	LXI	H,0029H		; ??0E
	PUSH	H		; ??11
	LXI	H,0C22CH	; ??12
	PUSH	H		; ??15
	LXI	H,1377H		; ??16
	PUSH	H		; ??19
	LXI	H,1AC9H		; ??1A
	PUSH	H		; ??1D
; at this point, SP=0028H

	CALL	0028H		; ??1E
; this just called the RET instruction, but the real point of this is
; to leave the address of the caller on the stack (assuming a boot
; address of E800H):
;    0026: 21
;    0027: E8
; (0027H) is read out later.

; again, what the heck is going on?
	LXI	H,0030H	; ??21: HL <= 0030H
	MOV	E,E	; ??24: DE <= ????H	"["
	MOV	D,D	; ??25: DE <= ????H	"R"
	MOV	B,H	; ??26: BC <= 0001H	"D"
	MOV	D,H	; ??27: DE <= 00??H	"T"
	MOV	E,L	; ??28: DE <= 0030H	"]"

L0029:	; although this is a label, it really is ??29.  when we jump
	; to L0029 below, it is the relocated code we are jumping to.

	LDA	0027H	; ??29: read page of caller (assuming no interrupts)
	MOV	D,A	; ??2C: DE <= E830H
	JMP	L0029	; ??2D: jump to code we just wrote to RAM:

;    0029: 1A		LDAX	D
;    002A: 77		MOV	M,A
;    002B: 13		INX	D
;    002C: 2C		INR	L
;    002D: C2 29 00	JNZ	0029H
;
; So this section of code copies [ E830H .. E8FFH ] to [ 0030H .. 00FFH ],
; and then falls through to the code just loaded into that range.  All
; the code in the boot rom from 0030H to 00FFH is effectively relocated
; to the first page of RAM.
;
; At this point, the known register values are:
;
;     HL = 0000H  (we only did "INR L", and it just wrapped around)
;     DE = E900H  (we just did "INX D" and the LSB wrapped)
;     BC = 0001H
;     A  = 54H    (last byte transferred)

	INR	D	; DE <= EA00H, the order register addr
	INR	D	; DE <= EB00H, the command register addr

; after this point DE is always EBxxH, the disk command register.
; D is not modified again, and E is twiddled in various ways to
; point to various memory mapped locations when (de) is read.
; (de) is never written.

	MVI	E,15H	; DE <= EB15H
	LDAX	D	; turn on drive motors
	MVI	H,30H	; HL <= 3000H
	CALL	HSEC	; wait for spin up: 48 sectors
	MOV	B,D	; BC <= EB01H
	DCR	B	; BC <= EA01H
	LDAX	B	; single density, bottom side, drive 1
	CALL	TWOSEC	; wait for two sector holes to go past

; seek index hole.  hang if 12 sectors go by without one.
	MVI	L,13	; L=attempt counter
SKTRY:	DCR	L
SKHANG:	JZ	SKHANG	; hang if we don't succeed
	CALL	NXTSEC	; DE <= EB10H
	LDAX	D	; read A-status
	ANI	40H	; IX: index hole detected?
	JZ	SKTRY	; if it no index hole, try again

; -----------------------------------------
; big outer wrapper --
; attempt to load the boot program from disk, starting at retry.
; If something goes wrong, we start over again from that point.
; If fail after ten attempts, we simply hang.

	MVI	A,10	; # of attempts to try to load boot program
	PUSH	PSW	; the attempt counter is kept on the stack
	CALL	STEPIN	; step in; BC = EA01H before and after call

	; step the read head in until we find track 0
SEEK0:	MVI	E,20H	; DE <= EB20H
	LDAX	D	; get B-status
	ANI	01H	; check for track 0 (hmm, why not RCA/JC?)
	JNZ	W8SEC4	; jump if we're at track 0
	CALL	STEP	; step out; BC = EA01H before and after call
	JMP	SEEK0	; assume we'll eventually get there

	; we've found track 0; wait for sector 4 and subsequent phase lock
W8SEC4: MVI	L,04H
	CALL	W84SEC	; wait for sector counter to equal 4
W84RE:	MVI	E,10H	; DE <= EB10H
	LDAX	D	; get A-status
	ANI	04H	; wait for RE (read enable) flag
	JZ	W84RE	; try again

	; kill 7+9*14, or 133 cycles, or ~70 uS @ 2MHz
	MVI	A,09H	; [  7 cycles ]
W870:	DCR	A	; [  4 cycles ]
	JNZ	W870	; [ 10 cycles ]

	LDAX	D	; get A-status
	ANI	20H	; check DD (double density) indicator
	JNZ	W84BD	; branch if double density; BC=EA01 here
	CALL	STEPIN	; step in; return with BC=EA01
	MVI	L,08H
	CALL	W84SEC	; wait for sector counter to equal 8

	; wait for sync flag at start of sector, indicating start of data
	; 7 + 35*n cycles.  @ 2MHz & n=162, this is 2.84 ms.  it takes
	; about (60000ms/min)/(300 RPM)/(10 sectors/revolution), or about
	; 20 ms per sector.  so 2.84 ms is about 14% of a sector.

W84BD:	MVI	B,0A3H	; countdown timer: 162 attempts
	MVI	E,10H	; [ 7] DE <= EB10H
BDLOOP:	DCR	B	; [ 4]
	JZ	FAIL	; [10] timed out
	LDAX	D	; [ 7] get A-status
	RRC		; [ 4] put BD (BODY) flag into carry
	JNC	BDLOOP	; [10] wait for sync char(s)

	; we've received the sync char(s)
	MVI	E,40H	; DE <= EB40H
	LDAX	D	; read the first data byte
	MOV	H,A	; 256B page to load boot program to
	MVI	L,00H	; hl = (boot page) << 8
	MVI	M,59H	; strange -- done again later
	RLC		; A=H rotatated left 1
	MOV	B,A	; 1st byte of CRC checksum
	INX	H	; start transfer at second byte

; transfer 511 more bytes from (de) to (hl).  HL points to memory,
; but DE is reading from a memory mapped register.  As the bytes
; are transferred, each byte is CRC checksummed into B.

PAGE1:	LDAX	D	; get next data byte
	MOV	M,A
	XRA	B	; \
	RLC		;  >-- checksum A into B
	MOV	B,A	; /
	INR	L
	JNZ	PAGE1
	INR	H	; done with this page: go to next page

PAGE2:	LDAX	D	; get next data byte
	MOV	M,A
	XRA	B	; \
	RLC		;  >-- checksum A into B
	MOV	B,A	; /
	INR	L
	JNZ	PAGE2

	; test checksum
	LDAX	D	; read checksum byte
	XRA	B	; compare it to our computed checksum
	JNZ	FAIL	; mismatch

	; write 59H,59H,59H,01H to first four bytes of first page.
	DCR	H	; go back to first page
	MVI	L,03H	; first page, fourth byte
	MOV	M,C	; C=01H as far as I know
FILL59:	DCR	L	; first page, bytes 2, 1, 0
	MVI	M,59H
	JNZ	FILL59
	MVI	L,0AH
	PCHL		; start running boot pgm, first page byte 10


; like next entry point, but BC is bumped up by 20H first.
; on exit, BC is the same for either case, though.
; It would appear that both callers of this routine come in with
; BC=EA01H, so I don't know why this code can't simply load C with 21H.

STEPIN:	MVI	A,20H	; \
	ADD	C	;  >-- add 20H to C, w/no carry into B
	MOV	C,A	; /

; step the read head in or out one track:
;    if BC is EA01, we will step out.
;    if BC is EA21, we will step in.
; after giving the step command, we wait for two sector holes to be
; detected to give us some reassurance that it is on the new track.
;
; return with H of 00H.
; return with DE of EB10H.
; return with BC of EA01.

STEP:	LDAX	B	; BC=EA21H or EA01H; read but ignored
	MVI	A,10H	; \
	ADD	C     	;  >-- add 10H to C
	MOV	C,A     ; /
	LDAX	B	; step bit is high
	MVI	A,0F0H	; \
	ADD	C     	;  >-- subtract 10H from C
	MOV	C,A     ; /
	LDAX	B	; step bit is low
	MOV	A,C
	ANI	0FH
	MOV	C,A   	; BC=EA01H

; wait for the detection of two sector holes.
; return with H of 00H.
; return with DE of EB10H.
TWOSEC:	CALL	NXTSEC
	; ... then fall through to do it again

; wait for the detection of the next sector hole.
; return with H of 00H.
; return with DE of EB10H.
NXTSEC:
	MVI	H,01H	; one pass through polling routine

; wait for the detection of a sector hole 'H' times.
; return with H of 00H.
; return with DE of EB10H.
HSEC:	MVI	E,11H	; DE <= EB11H
	LDAX	D	; reset sector flag
	DCR	E	; DE <= EB10H
W84SF:	LDAX	D	; read A-status
	ORA	A
	JP	W84SF	; wait until sector flag is 1
	DCR	H
	JNZ	HSEC
	RET


; wait until the sector counter matches what is in L.
; return with H of 00H.
; return with DE of EB35H.
W84SEC:	CALL	NXTSEC
	MVI	E,35H	; DE <= EB35H
	LDAX	D	; get C-status and kick drive motor
	ANI	0FH	; inspect bits [3:0]
	CMP	L	; ... vs L
	JNZ	W84SEC	; try again if they don't match
	RET		; they matched


; we failed to boot.  decrement the boot attempt counter which
; is kept on the stack and try again if appropriate.
FAIL:	POP	PSW	; recover attempt countdown
	DCR	A
	PUSH	PSW
	JNZ	SEEK0	; try again if we haven't reached 0

; we've attempted to boot 10 times and failed every time.
; call it quits.
HANG:	JMP	HANG	; spin wheels forever


; Deadwood, and the second time we've seen this sequence.
; The initials of the programmer, perhaps?

	MOV	D,D	; "R"  \
	MOV	B,H	; "D"   >-- someone's signature?
	MOV	D,H	; "T"  /

