;==============================================================
; MBLe - Enhanced Multi Boot Loader for the Altair 8800
; Modified to replace the 88-HSR with the 2nd 88-2SIO port
;
; Loads and runs Altair-style file from input device specified
; by the front panel sense-switch settings. Normally run in
; ROM at address 0FE00h.
;
; Vers.   Date      Author    Comments
; 1.00  01SEP2013  MEBERHARD  Disassembled from MITS EPROMS
; 1.01  01SEP2013  MEBERHARD  Modified to support e.g. a fast
;                             reader on the 2SIO's 2nd port
; 1.02  03SEP2013  MEBERHARD  initial read from parallel ports
;                             to clear latches (for OP-80)
; 1.03  09SEP2013  MEBERHARD  fix for no checksum loader (see
;                             8 below.)
;
; Written to assemble with ASM by Digital Research. 
;
; Thanks to Geoff Harrison for his MBL disassembly, which I
; plagerized freely.
;
;** Differences between MITS MBL and this MBe vers. 1.03 **
;
; 1) All 88-4PIO Port 1 initialization (for the HSR) is deleted
; 2) 88-2PIO port 0 stop bits are set according to the sense
;    switch settings. (The original MBL always set the 88-2SIO
;    for 2 stop bits, even if 1 stop bit was selected on the
;    sense switches.)
; 3) The 88-2SIO port 1 is initialized (for 1 stop bit)
; 4) The 88-HSR entry in PTABLE is replaced with an entry for
;    the 8-2SIO port 1. See sense switch table below.
; 5) code for starting the HSR transport is removed.
; 6) in initial read is performed for both the 88-PIO and the
;    88-4PIO Port 0, to clear data handshake latches in
;    external devices such as the OP-80 paper tape reader
; 7) The code length has been crafted to keep the code and
;    PTABLE at their original addresses.
; 8) If the leader character is 0, then no checksum loader
;    will be skipped. This fixes the loader, so you can load
;    a file that was punched by the TURMON "D" command, which
;    creates a tape leader with 60 bytes of 302 octal, followed
;    by 60 bytes of null. Position the tape someplace in the
;    leader nulls (past the 302s) and load it using this MBL.
;
; All code changes from the original MBL are marked with "<-ME"
;
;==============================================================
; An Altair-style file has 4 sections, which may be separated
; by any number of nulls. These sections are:
;
; 1) The Leader, which comprises 2 or more identical bytes, the
;    value of which is the length of the Checksum Loader.
;
; 2) The Checksum Loader, which is a program that is normally
;    used to load the subsequent sections
;
; 3) Zero or more Load Records, each structured as follows:
;       Byte 0: Sync Byte = 3Ch (identifies a Load Record)
;       Byte 1: NN = number of data bytes in record
;       Byte 2: LL = Load address low byte
;       Byte 3: HH = Load address high byte
; Bytes 4-NN+3: NN data bytes to store at HHLL, NN>0
;    Byte NN+4: CC = checksum of bytes 2 through NN+3
;
; 4) The Go Record, structured as follows
;       Byte 0: Sync Byte = 78H (identifies the Go Record)
;       Byte 1: LL = low byte of go address
;       Byte 2: HH = high byte of go address
;
; Altair file Leaders and Checksum Loaders are specific to
; both the version of the particular software and the memory
; size. For example, the Checksum Loader for 4K Basic 3.2 is
; different than the Checksum Loader for 8K Basic 3.2. And
; both the Leader and Checksum Loader for 8K Basic 3.2 are
; different than those for 8K Basic 4.0.
;
; MBL is able to read any such Altair file by simply skipping
; over the Leader and Checksum Loader, and loading the Load
; and Go Records directly.
;
; MBL chooses its input port based on the front panel Sense
; Switch settings, using the conventions set up in Basic 4.X:
;
;  Device                   bits 3:0
;  88-2SIO Port 0 (2 stops)   0000b
;  88-2SIO Port 0 (1 stop)    0001b
;  88-SIO                     0010b
;  88-ACR                     0011b
;  88-4PIO                    0100b
;  88-PIO                     0101b
;  88-2SIO Port 1 (1 stop)    0110b
;
; Prior to Basic 4.0, MITS used different Sense Switch settings
; to specify the console device.  You can load an older tape
; with MBL by setting the switches according to the above table
; and starting the load. After MBL has skipped over the
; Checksum Loader on the tape and has begun to load the Load
; Records (but before the load completes) change the Sense
; Switch settings as required by the earlier version of Basic
; (or other program) that you are loading.
;--------------------------------------------------------------
; 8080 EQUATES
;--------------------------------------------------------------
JZOP	EQU	0CAH	;JZ OPCODE
JNZOP	EQU	0C2H	;JNZ OPCODE
;--------------------------------------------------------------
; ALTAIR FILE EQUATES
;--------------------------------------------------------------
ALTPLR	EQU	3CH	;PROGRAM LOAD RECORD
ALTEOF	EQU	78H	;EOF/GO ADDRESS RECORD
ALTBNR	EQU	55H	;BEGIN/PROGRAM NAME (NOT SUPPORTED)
ALTBND	EQU	0DH	;END-OF-NAME MARK (NOT SUPPORTED)
;--------------------------------------------------------------
;ALTAIR I/O EQUATES
;--------------------------------------------------------------
SWTCHS	EQU	0FFH	;FRONT PANEL SWITCH REGISTER
;--------------------------------------------------------------
;ALTAIR FILE EQUATES
;--------------------------------------------------------------
ALTPLR	EQU	3CH	;PROGRAM LOAD RECORD
ALTEOF	EQU	78H	;EOF/GO ADDRESS RECORD
ALTBNR	EQU	55H	;BEGIN/PROGRAM NAME (NOT SUPPORTED)
ALTBND	EQU	0DH	;END-OF-NAME MARK (NOT SUPPORTED)
;--------------------------------------------------------------
;88-2SIO EQUATES
;--------------------------------------------------------------
;88-2SIO REGISTERS

S2CTL0	EQU	10H	;ACIA 0 CONTROL OUTPUT PORT
S2STA0	EQU	10H	;ACIA 0 STATUS INPUT PORT
S2TXD0	EQU	11H	;ACIA 0 TX DATA REGISTER
S2RXD0	EQU	11H	;ACIA 0 RX DATA REGISTER
S2CTL1	EQU	12H	;ACIA 1 CONTROL OUTPUT PORT
S2STA1	EQU	12H	;ACIA 1 STATUS INPUT PORT
S2TXD1	EQU	13H	;ACIA 1 TX DATA REGISTER
S2RXD1	EQU	13H	;ACIA 1 RX DATA REGISTER

;ACIA CONTROL REGISTER BITS

S2DS1	EQU	00000001B	;COUNTER DIVIDE SEL 1
S2DS2	EQU	00000010B	;COUNTER DIVIDE SEL 2
S2WS1	EQU	00000100B	;WORD SELECT 1
S2WS2	EQU	00001000B	;WORD SELECT 2
S2WS3	EQU	00010000B	;WORD SELECT 3
S2TC1	EQU	00100000B	;TX CONTROL 1
S2TC2	EQU	01000000B	;TX CONTROL 2
S2RIE	EQU	10000000B	;RX INT ENABLE

S2RST	EQU	00000011B	;MASTER RESET

;ACIA STATUS REGISTER BITS

S2RDF	EQU	00000001B	;RX DATA REG FULL
S2TDE	EQU	00000010B	;TX DATA REG EMPTY
S2DCD	EQU	00000100B	;DATA CARRIER DETECT
S2CTS	EQU	00001000B	;CLEAR TO SEND
S2FE	EQU	00010000B	;FRAMING ERROR
S2ORE	EQU	00100000B	;RX OVERRUN ERROR
S2PE	EQU	01000000B	;PARITY ERROR
S2IRQ	EQU	10000000B	;INTERRUPT REQUEST
;--------------------------------------------------------------
;88-SIO EQUATES
;--------------------------------------------------------------
;88-SIO REGISTERS	

SIOCTL	EQU	00		;CONTROL PORT
SIOSTA	EQU	00		;STATUS
SIOTXD	EQU	01		;TRANSMIT DATA
SIORXD	EQU	01		;RECEIVE DATA

;STATUS REGISTER BITS

SIOIDR	EQU	00000001B	;INPUT DEV RDY (RX BUF FULL)
SIOPE	EQU	00000100B	;PARITY ERROR
SIOFE	EQU	00001000B	;FRAMING ERROR
SIODOV	EQU	00010000B	;DATA OVERFLOW
SIOODR	EQU	10000000B	;OUTPUT DEV RDY (TX BUF EMPTY)
;--------------------------------------------------------------
;88-ACR (AUDIO CASSETTE RECORDER) EQUATES
;NOTE: THE ALTAIR 88-ACR IS BUILT AROUND AN ALTAIR 88-SIO
;--------------------------------------------------------------
;88-ACR REGISTERS	

ACRCTL	EQU	06		;CONTROL PORT
ACRSTA	EQU	06		;STATUS
ACRTXD	EQU	07		;TRANSMIT DATA
ACRRXD	EQU	07		;RECEUVE DATA

;STATUS REGISTER BITS

ACRIDR	EQU	00000001B	;INPUT DEV RDY (RX BUF FULL)
ACRPE	EQU	00000100B	;PARITY ERROR
ACRFE	EQU	00001000B	;FRAMING ERROR
ACRDOV	EQU	00010000B	;DATA OVERFLOW
ACRODR	EQU	10000000B	;OUTPUT DEV RDY (TX BUF EMPTY)
;--------------------------------------------------------------
;88-4PIO EQUATES
;NOTE: THE 88-HSR USES PORT 1 OF THE 88-4PIO
;--------------------------------------------------------------
;88-4PIO REGISTERS	

P4CA0	EQU	20H		;PORT 0 SECTION A CTRL/STATUS
P4DA0	EQU	21H		;PORT 0 SECTION A DATA
P4CB0	EQU	22H		;PORT 0 SECTION B CTRL/STATUS
P4DB0	EQU	23H		;PORT 0 SECTION B DATA
P4CA1	EQU	24H		;PORT 1 SECTION A CTRL/STATUS
P4DA1	EQU	25H		;PORT 1 SECTION A DATA
P4CB1	EQU	26H		;PORT 1 SECTION B CTRL/STATUS
P4DB1	EQU	27H		;PORT 1 SECTION B DATA

;CONTROL REGISTER BITS

P4C1C0	EQU	00000001B	;C1 CONTROL BIT 0
P4C1C1	EQU	00000010B	;C1 CONTROL BIT 1
P4DDR	EQU	00000100B	;DATA DIRECTION REGISTER
P4C2C3	EQU	00001000B	;C2 CONTROL BIT 3
P4C2C4	EQU	00010000B	;C2 CONTROL BIT 4
P4C2C5	EQU	00100000B	;C2 CONTROL BIT 5
P4IC2	EQU	01000000B	;C2 INTERRUPT CONTROL BIT
P4IC1	EQU	10000000B	;C1 INTERRUPT CONTROL BIT

;STATUS REGISTER BITS

P4RDF	EQU	10000000B	;RX DATA REG FULL
HSRRDF	EQU	01000000B	;RX DATA REG FULL for HSR
;--------------------------------------------------------------
;88-PIO EQUATES
;--------------------------------------------------------------
;88-PIO REGISTERS	

PIOCTL	EQU	04		;CONTROL PORT
PIOSTA	EQU	04		;STATUS
PIOTXD	EQU	05		;TRANSMIT DATA
PIORXD	EQU	05		;RECEUVE DATA

;STATUS REGISTER BITS

PIORDF	EQU	00000010B	;RX DATA REG FULL
;--------------------------------------------------------------
;SINGLE-BYTE ERROR MESSAGES
;--------------------------------------------------------------
CERMSG	equ	'C'	;checksum error
IERMSG	equ	'I'	;invalid load device error
MERMSG	equ	'M'	;memory error
OERMSG	equ	'O'	;overwrite error

;--------------------------------------------------------------
; Start of Code
;--------------------------------------------------------------
	org	0fe00H

;--------------------------------------------------------------
;Reset all known load devices
;--------------------------------------------------------------
	di			;interrupts off during load

;Make 4PIO 'A' channels inputs and 'B' channels outputs
	xra	A		;00H
	out	P4CA0		;access 4PIO Port 0A DDR
	out	P4DA0		;set 4PIO Port 0A as input

;<-ME	out	P4CA1		;access 4PIO Port 1A DDR (HSR)
;<-ME	out	P4DA1		;set 4PIO Port 1A as input (HSR)
;<-ME	out	P4CB1		;access 4PIO Port 1B DDR

	out	P4CB0		;access 4PIO Port 0B DDR
	cma			;0FFH
	out	P4DB0		;set 4PIO Port 0B as output

;<-ME	out	P4DB1		;set 4PIO Port 1B as output (HSR)

;Set up handshake controls for 4PIO Port A1 (HSR input)

;<-ME	mvi	A,0CH	;bits 0,1: C1 input active low, int off
			;bit 2: access data reg
			;bits 3-5: C2 active low input, int on
;<-ME	out	P4CA1

;Set up the other 3 4PIO ports all the same

	mvi	A,02cH	;bits 0,1: C1 input active low, int off
			;bit 2: access data reg
			;bits 3-5: C2 output handshake

	out	P4CA0		;4PIO Port 0A control
	out	P4CB0		;4PIO Port 0B control

;<-ME	out	P4CB1		;4PIO Port 1B control (HSR)

;Turn HSR tape transport off with bit 2=0

	mvi	A,S2RST		;2SIO reset (also 03H for HSR)
;<-ME	out	P4DB1		;4PIO Port 1B data (HSR out)

;Send reset command to both 2SIO ports

	out	S2CTL0		;2SIO Port 0
	out	S2CTL1		;2SIO Port 1 <-ME

;Set up 2SIO port 0: 8 data bits, 1 or 2 stop bits per front
;panel sense switch 0, no parity,clock divide by 16 

;<-ME	mvi	A,11H		;2 stop bits, /16

	in	SWTCHS		;1 or 2 stop bits? <-ME
	ani	01H		;0 means 2 stop bits <-ME
	rlc			;<-ME
	rlc			;put bit in place for ACIA<-ME
	ori	011H		;1 or 2 stop bits<-ME

	out	S2CTL0		;2SIO Port 0 control

;Set up 2SIO port 1: 8 data bits, 1 stop bit, no parity,
;clock divide by 16 

	mvi	A,15H		;<-ME
	out	S2CTL1		;2SIO Port 1 control<-ME

;flush external data latches for e.g. the OP-80

	in	PIORXD		;clear PIO port <-ME
	in	P4DA0		;clear 4PIO port <-ME

;--------------------------------------------------------------
;Find first non-RAM address, and make sure it is on a 256-byte
;page boundary (and report memory error if not.). This loop
;must stop eventually, since MBL exists in ROM.
;
;On Exit:
;  HL=first non-RAM address
;  MERR if RAM end is not on 256-byte boundary
;--------------------------------------------------------------
	lxi	H,-1		;Actually start at address 0

FNDEND:	inx	H
	mov	A,M		;Get existing data
	mov	B,A		;Remember
	cma			;Invert
	mov	M,A		;Write inverted
	cmp	M		;Write correctly?
	mov	M,B		;Restore original value
	jz	FNDEND 		;Keep going if okay

;HL = 1st memory location that won't write correctly.

	xra	A
	cmp	L		;256-byte page boundary?
	jnz	MERR	 	;no: memory error

;--------------------------------------------------------------
;Prepare to construct GETBYT subroutine in RAM, starting 14
;bytes below the top of RAM (why?), and working down toward 0.
;The GETBYT port configuration is based on the front panel
;sense switch settings.
;
;On Entry:
;  HL=first non-RAM address
;--------------------------------------------------------------
	lxi	B,-14		;leave 14-bytes above GETBYT
	dad	B		;(why?)
	sphl			;SP=end of GETBYT+1

;Compute RAM address for start of GETBYT subroutine
	dad	B		;GETBYT is 14 bytes long
	push	H		;temp save GETBYT address

;Look at sense switches to determine load device
	in	SWTCHS		;read sense switches
	ani	0FH		;low nib specifies load device
	cpi	07H		;out of bounds?
	jp	IERR	 	;y: 'I' error

;Look up load port parameters in PTABLE, based on low nibble
;of sense switches. On entry: B=0FFh
	lxi	H,PTABLE
	inr	B		;set B=0
	mov	C,A		;\
	add	A		; \ C = A * 3
	add	C		; /
	mov	C,A		;/
	dad	B		;HL=PTABLE((SWITCHS) <3:0>)

	pop	D		;DE=GETBYT address

;--------------------------------------------------------------
;Construct GETBYT subroutine in reverse order, using SP
;
;On Entry:
;     DE = HHLL = GETBYT=RAM last address-28
;     SP = GETBYT last address+1
;   (HL) = PP = data port address
; (HL+1) = JJ = JZ or JNZ opcode
; (HL+2) = MM = status register mask
; (HL+3) = SS = status port address
;
;On Exit:
;     HL = GETBYT
;     SP = GETBYT <--MBL's stack is just below GETBYT
;     Completed GETBYT is in RAM, like this:
;
;;Subroutine GETBYT gets one data byte from load device
;;On Entry: B = checksum so far
;;On Exit: A = data byte, B = updated checksum
;
;                  ORG    RAM_END-28
;
; DB SS    GETBYT: in     SS       ;read status
; E6 MM	           ani    MM
; JJ LL HH         jz/jnz GETBYT   ;wait for data
;
; DB PP		   in     PP       ;get data byte
; F5               push   PSW      ;temp save
; 80               add    B        ;update checksum in B
; 47               mov    B,A
; F1               pop    PSW      ;recover data byte
; C9               ret             ;A=byte, B=checksum
;--------------------------------------------------------------
	lxi	B,0C9F1H	;construct RET & POP PSW
	push	B

	lxi	B,04780H	;construct MOV B,A & ADD B
	push	B

	mvi	B,0F5H		;construct  PUSH PSW & PP
	mov	C,M		;look up PP in PTABLE
	mov	A,C
	push	B

	mvi	B,0DBH		;construct IN & HH
	mov	C,D
	push	B

	mov	B,E		;construct LL & JZ/JNZ
	inx	h		;look up JZ/JNZ in PTABLE
	mov	C,M
	push	B

	inx	H		;construct ANI & MM
	mov	B,M		;look up JZ/JNZ in table
	mvi	C,0E6H
	push	B

	dcr	A		;construct IN & SS
	mov	B,A		;look up SS in table
	mvi	C,0DBH
	push	B

	xchg			;HL=GETBYT

;--------------------------------------------------------------
;Start the HSR tape transport
;--------------------------------------------------------------
;<-ME	mvi	A,04H
;<-ME	out	P4DB1		;PIO port 1B data 

;--------------------------------------------------------------
;Skip over leader - a sequence of identical bytes, the value
;of which is the length of the checksum loader
;
;On Entry:
;  HL=address of GETBYT
;--------------------------------------------------------------
	call	GOTOHL		;flush UART
	call 	GOTOHL		;get 1st byte
	mov	C,A		;remember for comparison

	ora	a		;<-ME Null leader?
	jz	RCHUNT		;<-ME no cksm loader to skip

LDSKIP:	call	GOTOHL		;get another byte
	cmp	C
	jz	LDSKIP		;loop until different

;--------------------------------------------------------------
;Skip over checksum loader
;
;On Entry:
;   C=checksum loader length
;  HL=address of GETBYT
;--------------------------------------------------------------
	dcr	C

CLSKIP:	call	GOTOHL		;get a byte
	dcr	C
	jnz	CLSKIP

;--------------------------------------------------------------
;Get/store data from load records until we get an EOF record
;On Entry:
;  HL=address of GETBYT
;--------------------------------------------------------------
RCHUNT:	call	GOTOHL		;get a byte
	cpi	ALTPLR		;load record sync byte?
	jz	LDREC		;Y: go load the record
	cpi	ALTEOF		;EOF record sync byte?
	jnz	RCHUNT		;N: ignore

;--------------------------------------------------------------
;EOF Record: Get GO address and go there
;
;On Entry:
;  HL=address of GETBYT
;  Sync byte has already been read
;--------------------------------------------------------------
EOF:	call	GOTOHL		;get low byte
	mov	C,A
	call	GOTOHL		;get high byte
	mov	L,C		;into HL for GOTOHL
	mov	H,A

;--------------------------------------------------------------
;Go to address in HL
;--------------------------------------------------------------
GOTOHL:	pchl			;jmp to (HL)

;--------------------------------------------------------------
;Load Record: Read and store data from a load record
;
;On Entry:
;  HL=address of GETBYT
;  Sync byte has already been read
;--------------------------------------------------------------
LDREC:	call	GOTOHL		;get C=byte count
	mov	C,A
	mvi	B,0		;initialize checksum
	call	GOTOHL		;get E=load address low byte
	mov	E,A
	call	GOTOHL		;get D=load address high byte
	mov	D,A		;DE=load address

;Loop to read C data bytes into memory at DE.
;Make sure data won't overwrite GETBYT or stack.
LRLOOP:	mov	A,D
	cmp	H		;error if same page as GETBYT
	mvi	A,OERMSG	;overwrite error message
	jz	ERDONE		;error exit if overwrite

	call	GOTOHL		;get a data byte
	xchg			;HL=dest address, DE=GETBYT
	mov	M,A		;store data byte
	cmp	M		;did it store correctly?

;Entry at MERR from end-of-RAM search
MERR:	mvi	A,MERMSG	;Memory Error message
	jnz	ERDONE		;error exit if mismatch

	inx	H		;bump dest pointer
	xchg			;HL=GETBYT, DE=dest address
	dcr	C		;bump byte count
	jnz	LRLOOP		;loop through all bytes

; Validate checksum
	mov	C,B		;C=computed checksum
	call	GOTOHL		;get record's checksum
	cmp	C		;match?
	jz	RCHUNT		;Y: get another record

;--------------------------------------------------------------
;Report checksum error
;--------------------------------------------------------------
	mvi	A,CERMSG	;Checksum Error message
	db	01H		;'lxi' op skips next 2 bytes

IERR:	mvi	A, IERMSG	;Invalid Load Device error msg

;--------------------------------------------------------------
;Error handler:
; Save error code and address at beginning of memory
; Hang writing the error code forever, to all known consoles.
;--------------------------------------------------------------
ERDONE:	sta	00000H
	shld	00001H
	ei

ERHANG:	out 	SIOTXD		;SIO
	out 	S2TXD0		;2SIO
	out 	PIOTXD		;PIO
	out 	P4DB0		;4PIO port 0
	jmp 	ERHANG

;--------------------------------------------------------------
;Port table: One 3-byte entry for each input port:
; Byte 1 = Data port address (The status port is assumed
;          to immediately preceed the data port.)
; Byte 2 = either JNZ or JZ opcode
; Byte 3 = ready mask for data input
;--------------------------------------------------------------
PTABLE:	db	S2RXD0,JZOP,S2RDF	;2SIO P0 (2 stop bits)
	db	S2RXD0,JZOP,S2RDF	;2SIO P0 (1 stop bit)
	db	SIORXD,JNZOP,SIOIDR	;SIO
	db	ACRRXD,JNZOP,ACRIDR	;ACR
	db	P4DA0,JZOP,P4RDF	;4PIO Port 0
	db	PIORXD,JZOP,PIORDF	;PIO
;<-ME	db	P4DA1,JZOP,HSRRDF	;HSR
	db	S2RXD1,JZOP,S2RDF	;2SIO P1 (1 stop)<-ME

