1; MBL - MULTI BOOT LOADER FOR THE ALTAIR 8800
2;
3; DISAASEMBLED AND COMMENTED BY GEOFF HARRISON (GHOV SOLIVANT COM)
4;
5; THIS IS A BOOT LOADER THAT CAN BE LOACATED IN ROM AND WHICH
6; CAN READ ANY OF THE PUNCHED TAPE FORMATS THAT MITS DESIGNED.
7; IT RUNS OUT OF ROM BY CREATING A READBYTE ROUTINE IN RAM WHICH
8; IS CUSTOMIZED FOR WHATEVER I/O BOARD THE USER SPECIFIES AT
9; RUN TIME ON THE FRONT PANEL SWITCHES. IT THEN SKIPS OVER THE
10; LEADIN BYTES AND THE SECOND STAGE LOADER WHICH ALWAYS PRECEED
11; THE PAYLOAD ON A MITS TAPE.  SINCE IT DOESN'T USE THE
12; SECOND STAGE LOADER, IT DOESN'T CARE WHICH OF SEVERAL POSSIBLE
13; VERSIONS OF TAPE IT IS LOADING.  NORMALLY YOU HAVE TO BE SURE
14; TO USE THE CORRECT 1ST STAGE LOADER FOR THE TAPE YOU ARE LOADING,
15; WITH THE MBL IT DOESN'T MATTER.  AFTER SKIPPING THE 2ND STAGE
16; LOADER ON THE TAPE, IT STARTS READING AND STORING PAYLOAD
17; BYTES AND CHECKING THAT THE CHECKSUM IS CORRECT FOR EACH PACKET
18; OF BYTES READ.  WHEN THE ENTIRE PAYLOAD HAS BEEN READ INTO MEMORY,
19; IT READS THE ENTRY POINT FOR THE PROGRAM FROM THE TAPE AND JUMPS
20; TO THAT LOCATION.
21;
22; THE FORMAT OF A MITS PAPER TAPE IS:
23;       - SOME NUMBER OF IDENTICAL BYTES, EACH CONTAINING
24;               THE LENGTH (N) OF THE STAGE2 LOADER
25;       - N BYTES OF STAGE 2 LOADER
26;       - OPTIONALLY SOME NUMBER OF NULLS
27;       - ONE OR MORE LOAD RECORDS (SEE BELOW)
28;       - AN EOF RECORD (SEE BELOW)
29;
30; A LOAD RECORD CONSISTS OF:
31;       074     - SYNC BYTE (HEX 03CH)
32;       NNN     - # BYTES IN RECORD
33;       LLL     - LOW BYTE OF LOAD ADDRESS
34;       HHH     - HIGH BYTE OF LOAD ADDRESS
35;   <NNN BYTES> - NNN BYTES OF PROGRAM DATA (THE PAYLOAD)
36;       CCC     - CHECKSUM BYTE
37;                (CHECKSUM INCLUDES LLL & HHH)
38; THERE WILL BE 1 OR MORE PROG LOAD RECORDS,
39; EACH ONE WITH UP TO 256 BYTES OF PAYLOAD
40; BYTES.
41;
42; THE EOF RECORD CONSISTS OF:
43;       0170    - SYNC BYTE (HEX 078H)
44;       LLL     - LOW BYTE OF START ADDRESS
45;       HHH     - HIGH BYTE OF START ADDRESS
46;
47; BEFORE EXECUTING THIS PROGRAM, THE FRONT PANEL SENSE SWITCHES
48; MUST BE SET TO INDICATE WHAT DEVICE TO READ THE TAPE FROM AND
49; WHAT DEVICE THE TERMINAL IS ATTACHED TO.  THIS PROGRAM USES
50; A11..A8 TO DETERMINE THE DEVICE ATTACHED TO THE TAPE READER.
51; IF THE PAYLOAD IS NOT EARLIER THAN BASIC 4.0, IT USES A15..A12
52; TO DETERMINE WHERE THE TERMINAL DEVICE IS.
53; POSSIBLE SWITCH VALUES ARE
54;
55; DEVICE                SWITCH VALUE
56; 2SIO (2 STOP BITS)      0000B
57; 2SIO (1 STOP BIT)       0001B
58; SIO                     0010B
59; ACR                     0011B
60; 4PIO                    0100B
61; PIO                     0101B
62; HSR                     0110B
63;
64; IF A VALUE LARGER THAN 7 IS ENTERED THIS PROGRAM WILL RETURN
65; AN ERROR.
66;
67; PRIOR TO BASIC 4.0, MITS USED DIFFERENT SENSE SWITCH SETTINGS
68; TO SPECIFY THE TERMINAL DEVICE.  YOU SHOULD STILL BE ABLE TO
69; LOAD AN OLDER TAPE WITH THIS LOADER BY SETTING THE SWITCHES AS
70; ABOVE, STOPPING THE PROGRAM AFTER IT LOADS AND STARTS, CHANGING
71; THE SENSE SWITCHES (SEE THE APPROPRIATE MITS MANUAL FOR SWITCH
72; SETTINGS) AND RESTARTING THE PROGRAM AT ITS ENTRY POINT (E.G.
73; 00000H FOR BASIC 3.2).
74;
75; THIS LISTING WAS GENERATED FROM A HEX DUMP OF MBL PROVIDED BY
76; GRANT STOCKLY.  THE ASSEMBLY CODE AND COMMENTS WERE REVERSE
77; ENGINEERED FROM THAT DUMP BY GEOFF HARRISON.
78
79; DEFAULT LOCATION IS AT 0FE00H.  MAY BE ASSEMBLED TO RUN
80; AT ANY LOCATION.
81;
82; ASSEMBLER SYNTAX IS FOR THE SPASM ASSEMBLER.  TO ASSEMBLE USE:
83;      SPASM MBL /F
84;      SLINK MBL=MBL.OBJ /C:0FE00
85;
86; FOR OTHER ASSEMBLERS, UNCOMMENT THE FOLLOWING LINE AND
87; (POSSIBLY) CHANGE THE SYNTAX OF ORG, DB, ETC.
88;
89                ORG 0FE00H
90
91;----------------------------------------------------------
92; WE DON'T KNOW WHAT I/O CARDS ARE IN THE SYSTEM, SO
93; TRY TO INITIALIZE JUST ABOUT ANYTHING MITS HAD AVAILABLE
94; AT THE TIME.
95;
96                DI
97                XRA A
98                OUT 020H
99                OUT 021H
100                OUT 024H
101                OUT 025H
102                OUT 026H
103                OUT 022H
104                CMA
105                OUT 023H
106                OUT 027H
107                MVI A,00CH
108                OUT 024H
109                MVI A,02CH
110                OUT 020H
111                OUT 022H
112                OUT 026H
113                MVI A,003H
114                OUT 027H
115                OUT 010H
116                MVI A,011H
117                OUT 010H
118
119;----------------------------------------------------------
120; FIND TOP OF RAM.  ASSUMES THAT THERE IS NOT 64K OF RAM
121; AVAILABLE, A REASONABLE ASSUMPTION GIVEN THAT THIS ROUTINE
122; RESIDES IN ROM SOMEWHERE AT THE TOP OF MEMORY.
123;
124                LXI H, 0FFFFH
125SCANRAM:        INX H           ; POINT @ NEXT BYTE
126                MOV A,M         ; GET IT
127                MOV B,A         ; SAVE IT
128                CMA             ; INVERT IT
129                MOV M,A         ; TRY TO WRITE IT BACK OUT
130                CMP M           ; SEE IF IT WROTE OUT CORRECTLY
131                MOV M,B         ; RESTORE THE BYTE TO ITS ORIG VALUE
132                JZ SCANRAM      ; LOOP IF THE BYTE WROTE CORRECTLY
133
134                ; SOMETHING'S WRONG IF THE FAILURE TO WRITE TO RAM
135                ; HAPPENED ON OTHER THAN A PAGE BOUNDARY
136                XRA A           ; A = 0
137                CMP L
138                JNZ MERROR      ; IF L != 0 GO TO 'M' ERROR HANDLER
139
140                ; HL NOW POINTS TO THE LAST WRITABLE BYTE IN RAM PLUS 1.
141                ; SUBTRACT 14 TO MAKE ROOM FOR A STACK & INITIALIZE SP.
142                LXI B,0FFF2H
143                DAD B           ; HL += BC
144                SPHL            ; SP = HL
145
146                DAD B           ; SET HL TO POINT TO WHAT WILL BE
147                                ; THE START OF THE READBYTE ROUTINE.
148                PUSH H          ; SAVE THE START ADDRESS ON THE STACK.
149
150                IN 0FFH         ; READ FRONT PANEL SWITCHES
151                ANI 00FH        ; MASK LOWER 4 BITS
152                CPI 007H        ; SWITCHES SET TO >= 7?
153                JP IERROR       ; Y - GO TO 'I' ERROR HANDLER
154
155                ; POINT HL AT THE NTH ENTRY IN THE MOD TABLE,
156                ; WHERE N = THE FRONT PANEL SWITCH SETTING
157                LXI H, TABLE1   ; POINT HL TO START OF THE MOD TABLE
158                INR B           ; B CURRENTLY CONTAINS 0FFH, INC IT TO 0
159                MOV C,A         ; \
160                ADD A           ;  \ C = A * 3
161                ADD C           ;  /
162                MOV C,A         ; /
163                DAD B           ; HL += BC
164
165;----------------------------------------------------------
166; CONSTRUCT AN INPUT FUNCTION ON THE STACK.
167; SINCE THIS IS A PUSH DOWN STACK, THE ROUTINE IS BUILT IN REVERSE.
168; WHEN FINISHED, THE ROUTINE WILL LOOK LIKE THIS. (XX VALUES ARE
169; CALCULATED ON THE FLY OR READ FROM THE MOD TABLE.  THE ROUTINE
170; HAS TO BE BUILT DYNAMICALLY LIKE THIS BECAUSE DIFFERENT I/O CARDS
171; REQUIRE DIFFERENT CODE TO READ THEIR DATA AND STATUS.  THE USER
172; SPECIFIED WHICH CARD IS IN USE ON THE FRONT PANEL SWITCHES.)
173; READBYTE:
174;       DB XX           IN XX           ; CHECK PORT STATUS
175;       E6 XX           ANI XX
176;       XX LO HI        JZ/JNZ READBYTE ; LOOP 'TILL BYTE ARRIVES
177;       DB XX           IN XX           ; GET INPUT BYTE
178;       F5              PUSH PSW        ; SAVE IT
179;       80              ADD B           ; UPDATE CHECKSUM IN B
180;       47              MOV B,A
181;       F1              POP PSW         ; RETRIEVE INPUT BYTE
182;       C9              RET             ; RETURN A=BYTE, B=CHECKSUM
183;
184                POP D           ; DE = THE START ADRESS OF THIS ROUTINE
185
186                ; CONSTRUCT RET; POP PSW
187                LXI B,0C9F1H
188                PUSH B
189
190                ; CONSTRUCT MOV B,A; ADD B
191                LXI B,04780H
192                PUSH B
193
194                ; CONSTRUCT  PUSH PSW; {INPUT PORT RETRIEVED FROM MOD TABLE}
195                MVI B,0F5H
196                MOV C,M
197                MOV A,C
198                PUSH B
199
200                ; CONSTRUCT IN (USED WITH PORT CONSTRUCTED ABOVE); {HIGH BYTE OF JMP INSTR}
201                MVI B,0DBH
202                MOV C,D
203                PUSH B
204
205                ; CONSTRUCT {LOW BYTE OF JMP}; JZ/JNZ (FROM MOD TABLE)
206                MOV B,E
207                INX H
208                MOV C,M
209                PUSH B
210
211                ; CONSTRUCT ANI {MASK FROM MOD TABLE}
212                INX H
213                MOV B,M
214                MVI C,0E6H
215                PUSH B
216
217                ; CONSTRUCT IN {PORT STATUS ADDRESS (PORT NUMBER - 1)}
218                DCR A
219                MOV B,A
220                MVI C,0DBH
221                PUSH B
222;----------------------------------------------------------
223; READ A STREAM OF INPUT BYTES.
224;
225                XCHG            ; HL = START OF READBYTE ROUTINE, DE = JUNK
226
227                ; ???????????????????????????????????????????????????
228                MVI A,004H      ; WHY ARE WE SENDING 04H TO DEVICE 027H?
229                OUT 027H        ; IN FACT, WHAT IS DEVICE 027H? ANYONE KNOW? IS IT THE
230                                ; 88-HSR?  PERHAPS WE'RE SWITCHING ON THE DRIVE MOTOR.
231                ; ???????????????????????????????????????????????????
232
233                ; SKIP OVER LEADIN BYTES.
234                CALL LINK       ; FLUSH INPUT BUFFER
235                CALL LINK       ; GET A BYTE
236                MOV C,A         ; REMEMBER IT
237LEADINSKIP:     CALL LINK       ; GET ANOTHER BYTE
238                CMP C
239                JZ LEADINSKIP   ; LOOP UNTIL WE RECEIVE A DIFFERENT BYTE VALUE
240
241                ; AT THIS POINT, C CONTAINS THE FIRST LEADIN BYTE
242                ; THAT WAS ON THE TAPE, WHICH SHOULD REPRESENT THE
243                ; LENGTH OF THE STAGE 2 LOADER.
244                ; SKIP OVER STAGE 2 LOADER.
245                DCR C
246STAGE2SKIP:     CALL LINK       ; GET A BYTE
247                DCR C
248                JNZ STAGE2SKIP  ; LOOP WHILE C > 0
249
250                ; NOW WE'VE SKIPPED OVER ALL THE UNNEEDED STUFF, START
251                ; LOOKING FOR THE FIRST LOAD RECORD.
252FINDTOKEN:      CALL LINK       ; GET A BYTE
253                CPI 03CH        ; IS IT A LOAD RECORD TOKEN?
254                JZ LOADRECORD   ; Y - GO AND PROCESS A LOAD RECORD
255                CPI 078H        ; IS IT AN EOF TOKEN?
256                JNZ FINDTOKEN   ; N - KEEP LOOKING
257
258                ; GOT AN EOF TOKEN.  READ NEXT TWO BYTES AS PROGRAM
259                ; START ADDRESS AND JUMP TO THAT ADDRESS.
260EOF:            CALL LINK       ; GET PROG START LOW
261                MOV C,A
262                CALL LINK       ; GET PROG START HIGH
263                MOV L,C
264                MOV H,A
265
266                ; THIS IS MOSTLY USED TO CALL THE BYTE INPUT ROUTINE
267                ; ON THE STACK, BUT IS ALSO USED TO JUMP TO THE ENTRY
268                ; POINT OF THE DOWNLOADED PROGRAM.
269LINK:           PCHL            ; JMP TO (HL)
270
271                ; PROCESS A LOAD RECORD
272LOADRECORD:     CALL LINK       ; GET BYTE COUNT IN THIS RECORD
273                MOV C,A         ; SAVE IT
274                MVI B,000H      ; INITIALIZE CHECKSUM
275                CALL LINK       ; GET LOAD ADDRESS LOW
276                MOV E,A
277                CALL LINK       ; GET LOAD ADDRESS HIGH
278                MOV D,A
279
280LOREC2:         MOV A,D
281                CMP H           ; ARE THE INCOMING BYTES ABOUT TO
282                                ;    OVERWRITE THE READBYTE ROUTINE?
283                MVI A,04FH      ; PREPARE TO SEND 'O', IF NECESSARY
284                JZ ERREXIT      ; Y - ERROR EXIT
285                CALL LINK       ; N - GET ANOTHER BYTE
286                XCHG            ; HL = DEST POINTER, DE = START OF READBYTE
287                MOV M,A         ; STORE RECEIVED BYTE AT DESTINATION
288                CMP M           ; DID IT STORE CORRECTLY?
289MERROR:         MVI A,04DH      ; PREPARE TO SEND 'M', IF NECESSARY
290                JNZ ERREXIT     ; N - ERROR EXIT
291                INX H           ; Y - INCREMENT DESTINATION POINTER
292                XCHG            ; HL = START OF READBYTE, DE = DEST POINTER
293                DCR C           ; DECREMENT BYTE COUNTER
294                JNZ LOREC2      ; IF NOT 0 THEN LOOP TO GET MORE BYTES
295
296                MOV C,B         ; SAVE CALCULATED CHECKSUM VALUE BEFORE WE UPDATE IT AGAIN
297                CALL LINK       ; GET EXPECTED CHECKSUM FROM INPUT STREAM
298                CMP C           ; DOES IT MATCH OUR CALCULATED SUM?
299                JZ FINDTOKEN    ; Y - LOOK FOR MORE RECORDS ON THE TAPE
300                MVI A,043H      ; N - PREPARE TO SEND 'C' ERROR
301                DB 001H         ; LXI - HIDES NEXT TWO BYTES
302IERROR:         DB 03EH         ; MVI A, 049H   ; PREPARE TO SEND 'I'
303                DB 049H
304
305; STORE AN ERROR CODE AND THE ADDRESS @ WHICH IT HAPPENED IN
306; THE FIRST 3 BYTES OF RAM, THEN LOOP FOREVER SENDING THE
307; ERROR CODE TO ALL POSSIBLE TERMINAL DEVICES.
308;
309ERREXIT:        STA 00000H
310                SHLD 00001H
311                EI
312FOREVER:        OUT 001H
313                OUT 011H
314                OUT 005H
315                OUT 023H
316                JMP FOREVER
317
318;----------------------------------------------------------
319; MOD TABLE.
320; THIS IS A TABLE OF PORT ADDRESSES, COMMANDS FOR CHECKING
321; THE PORT STATUS (JZ/JNZ), AND STATUS MASKS FOR SEVERAL
322; I/O BOARDS.  THE VALUES ARE STUFFED INTO THE READBYTE
323; ROUTINE AT RUNTIME TO CUSTOMIZE IT FOR THE HARDWARE
324; BEING USED TO LOAD THE TAPE.
325;
326TABLE1:         DB      011H, 0CAH, 001H        ; 2SIO (2 STOP BITS)
327                DB      011H, 0CAH, 001H        ; 2SIO (1 STOP BIT)
328                DB      001H, 0C2H, 001H        ; SIO
329                DB      007H, 0C2H, 001H        ; ACR
330                DB      021H, 0CAH, 080H        ; 4PIO
331                DB      005H, 0CAH, 002H        ; PIO
332                DB      025H, 0CAH, 040H        ; HSR
333
334                END
335