-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathNetBoot_LC.s
526 lines (498 loc) · 19.2 KB
/
NetBoot_LC.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
; Apple //e Workstation Card
; replacement for "hard-coded" boot blocks (in ca65 format).
; By Michael Guidero
;
; The Apple //e Card for Macintosh LC is quite capable. It
; emulates all of the functions of the Apple //e Workstation Card
; except for one: When you boot "over the network", instead of getting
; the boot blocks over the network, it loads them from the "IIe Startup"
; resource fork, from BBLK#5120. Because the boot blocks contain both
; ProDOS and the Logon program, as shipped by Apple the LC //e Card is
; "stuck" at ProDOS 1.9 and Logon 1.3 as shipped by Apple. Updating them
; requires using ResEdit to hack the IIe Startup program.
;
; The real Workstation Card finds a boot server on the network and loads
; the boot blocks over the network via ATP. Updating the boot blocks
; is as simple as replacing them on the boot server.
;
; While I figured out how to update the boot blocks in IIe Startup, having
; to hack it with ResEdit just to make it work the same as my regular //es
; with Workstation Cards every time a new ProDOS comes out was an annoying
; prospect.
;
; So as a solution, here's a replacement for BBLK#5120 that makes the LC
; //e Card work like a standard Apple //e with a Workstation Card.
;
; Other features:
; * Puts a letter in the lower left corner indicating what part of the
; boot process is happening, in case something goes wrong.
; * On-screen spinner, spins as each block is received.
; * Displays the current AppleTalk zone.
; * Displays the boot server network, node, and socket.
;
; Revisions since I gave out the Gist link:
; 07/10/2017 - Fix NBPBuf to be at $xx00 instead of $00xx
; - Display boot server address/socket & object name
; - convert output to use monitor routines rather than direct write
; for messages only, spinners and status still direct write
; - If in the boot scan loop, try next slot if we fail.
; 07/11/2017 - Convert all AT calls to macro
; - Adjust retry interval/tries in GetMyZone call.
; - Display zone if possible, when ca/option held.
; - Added missing init type flags byte to AT Init call. No harm no foul.
;
.pc02
.macro ATcall PList
jsr GoCard
.byte $42
.addr PList
.endmacro
NBPBufSz = $0100 ; NBP buffer size
DispTmp = $02 ; temp var for display routines
ch = $24 ; cursor horizontal pos
CardPtr = $fe ; ZP loc of card pointer
IRQvect = $03fe ; ROM calls here on IRQ
BootStart = $0800 ; where we load boot blocks
NotifyLoc = $07d0 ; process notify screen loc
SpinLoc = $428+19 ; spinner screen loc
DeathLoc = $4A8+19
mli = $bf00 ; ProDOS entry pt
init = $fb2f ; init text screen
tabv = $fb5b ; vtab to a-reg
title = $fb60 ; clear screen, display "Apple //e"
bell1 = $fbdd ; beep
wait = $fca8 ; waste time
cout = $fded ; character out
setkbd = $fe89 ; set keyboard as input
setvid = $fe93 ; set text screen as output
clraltchar = $c00e
.org BootStart ; code gets loaded here
; Main routine
.proc NetBootLC
jsr init ; init text screen
jsr setkbd
jsr setvid
jsr HelloMsg ; Greeting message
lda #'F'+$80
sta NotifyLoc ; tell user we are finding the card
jsr FindCard
bcc :+
errend: jsr ErrorMsg ; whoopsie doodles
die: jmp Death ; Try next slot or hang.
: lda #'R'+$80
sta NotifyLoc ; tell user we are moving boot code
jsr ReloBoot ; move $0300 code
lda #'I'+$80
sta NotifyLoc ; tell user we are initing the card
jsr InitCard
bcs errend ; Init failed... sorry
jsr GetInfo
bcs errend
; local zone lookup doesn't always work when we don't have a bridge
; so if we don't have a bridge yet, don't do zone lookup unless ca/option is
; pressed
lda ATbridge
bne :+ ; if we have a bridge
sec ; flag no zone info
lda $c062 ; check closed-apple/option
bpl :++ ; skip if not pressed
: lda #'Z'+$80
sta NotifyLoc ; tell user we are getting our zone
jsr GetZone
: jsr DispInfo ; carry set = do not try to display zone from NBPBuf
lda #'L'+$80
sta NotifyLoc ; tell user we are looking for server
jsr FindSrv
bcc :+ ; found it
jsr NoServer ; didn't find one... sorry
bra die
: jsr DispServ ; give user boot network location
lda #'B'+$80
sta NotifyLoc ; tell user we are gonna boot
ldx #3 ; copy boot server addr to ATP req
: lda NBPBuf,x
sta ATPaddr,x
stz mli,x ; and zero out ProDOS MLI entry point, too,
; because the Logon program might check this and
; avoid initializing the card.
dex
bpl :-
; brk ; DEBUG
jmp ATPBoot ; go to $300 code
.endproc
; Find workstation card, since the //e Card is flexible about its
; placement. Also we can run on a regular //e with a Workstation Card
; for no particular reason.
.proc FindCard
lda #$f9 ; offset to ID bytes
sta CardPtr
lda #$c7 ; start at slot 7
sta CardPtr+1
nextslot: ldy #3 ; check ID bytes
: lda (CardPtr),y
cmp idtbl,y
bne nomatch
dey
bpl :-
ldy #4 ; This is card type byte offset
; 0 = IIgs
; 1 = Workstation Card
; 2 = unseen Server(!) Card
; $F0 = card diags in progress
: lda (CardPtr),y
sta NotifyLoc ; a little visual info
cmp #$f0 ; Shouldn't happen on LC Card, but...
beq :- ; wait for it all the same
cmp #1 ; Because it's proper to make sure it's a working WS Card
beq gotcard ; found it!
nomatch: dec CardPtr+1 ; next slot
lda CardPtr+1 ; get it
cmp #$c0 ; hit slot 0?
bne nextslot ; nope
sec ; no card found
rts
gotcard: clc ; card found
rts
idtbl: .byte "ATLK"
.endproc
; Initialize the WorkStation Card
.proc InitCard
sei ; disable interrupts for now
lda #<CardInt ; set up IRQ vector
sta IRQvect ; for card
lda #>CardInt
sta IRQvect+1
lda CardPtr+1
sta GoCard+2 ; init card MLI call addr
sta CardInt+2 ; init card interrupt addr
ATcall iniparms
done: cli ; re-enable interrupts
rts
; AppleTalk init call parms. Undocumented for the most part.
iniparms: .byte 0,1 ; synchronous init
.word $0000 ; result code, most likely
.byte $00 ; init type. Known types & users:
; $00 - partial init, no MLI global page update
; used by: ETalk (ethernet NetBoot)
; $80 - full init, no MLI global page update
; used by: ETalk (no NetBoot), Fizzy
; Possibly not usable on WS Card.
; $40 - full init, MLI global page update
; used by: ATInit, Logon (boot block version)
.dword $00000000 ; "ProDOS" entry point - zero seems to work
; if not using global page update
.byte $00 ; node num preference, 0 = any node number
.word $0000 ; unknown or reserved
.endproc
; do GetInfo call
.proc GetInfo
ATcall inforeq
bcs done
lda abridge
sta ATbridge
lda thisnet
sta ATnet
lda thisnet+1
sta ATnet+1
lda nodenum
sta ATnode
done: rts
inforeq: .byte 0,2 ; sync GetInfo
.word $0000 ; result code
.dword $00000000 ; completion address
thisnet: .word $0000 ; this network #
abridge: .byte $00 ; local bridge
.byte $00 ; hardware ID
.word $00 ; ROM version
nodenum: .byte $00 ; node number
.endproc
; Display our info. If carry is clear, try to display zone name
; from results of GetMyZone in NBPBuf
; i.e. call GetInfo, then call GetMyZone, then call this.
.proc DispInfo
php
lda #17 ; line 18
jsr tabv
stz ch ; col 0
; display our address
lda ATnet ; network is 16-bit
ldx ATnet+1
jsr PrintU16
lda #'.'+$80 ; standard separator
jsr cout
lda #$00
ldx ATnode ; node number
jsr PrintU16
; display bridge node if present
lda ATbridge
beq :+ ; skip if 0
ldx #msg4-msg1 ; " bridge ."
jsr Disp
lda #$00
ldx ATbridge
jsr PrintU16
: plp ; see if we should display zone
bcs done ; nope
; display zone
ldx #msg5-msg1 ; " zone "
jsr Disp
jsr DispZone ; display it
done: rts
.endproc
; Get our zone - for information
.proc GetZone
ATcall zonereq
bcs done ; error, bail
lda NBPBuf ; check pascal str count
bne done ; if nonzero, done
sec ; otherwise signal error
done: rts
zonereq: .byte 0,$1a ; sync GetMyZone
.word $0000 ; result
.dword $00000000 ; completion
.dword NBPBuf ; we'll use the NBP buffer since zone is just FYI
.byte 4,4 ; 4 times every 1 sec
.word $0000 ; reserved
.endproc
.proc DispZone
; below commented out because we assume we are called from DispInfo
;lda #17 ; line 19
;jsr tabv
;stz ch ; col 0
ldx #$00
: cpx NBPBuf ; did we display all of them?
beq done ; yes, done
lda NBPBuf+1,x ; get char
ora #$80
jsr cout ; display
inx ; next
bra :-
done: rts
.endproc
; Find a boot server in our zone
.proc FindSrv
ATcall lookup
bcs done ; error, bail
lda matches ; check # matches
bne done ; OK if not zero
sec
done: rts
; parameter list for NBPLookup
lookup: .byte 0,16 ; sync NBPLookup
.word $0000 ; result
.dword $00000000 ; completion
.dword srvname ; pointer to name to find
.byte 8,16 ; 16 times, every 2 secs
.word $0000 ; reserved
.word NBPBufSz ; buffer size
.dword NBPBuf ; buffer loc
.byte 1 ; matches wanted
matches: .byte $00 ; matches found
srvname: .byte 1,"=" ; object
.byte 14,"Apple //e Boot" ; type
.byte 1,"*" ; zone
.endproc
; Display found boot server address and object name
.proc DispServ
lda #18 ; line 19
jsr tabv
stz ch ; col 0
; display the server address
lda NBPBuf ; network is 16-bit
ldx NBPBuf+1
jsr PrintU16
lda #'.'+$80 ; standard separator
jsr cout
lda #$00
ldx NBPBuf+2 ; node number
jsr PrintU16
lda #'/'+$80 ; standard separator
jsr cout
lda #$00
ldx NBPBuf+3 ; socket number
jsr PrintU16
lda #' '+$80
; now display server object name
jsr cout
ldx #$00
: cpx NBPBuf+5 ; did we display all of them?
beq done ; yes, done
lda NBPBuf+6,x ; get char
ora #$80
jsr cout ; display
inx ; next
bra :-
done: rts
.endproc
; Print unsigned 16-bit integer
; adapted from
; https://groups.google.com/forum/#!topic/comp.sys.apple2/_y27d_TxDHA
.proc PrintU16
stx DispTmp
sta DispTmp+1
lda #$00
: pha
lda #$00
clv
ldy #$10
: cmp #$05
bcc :+
sbc #$85
sec
: rol DispTmp
rol DispTmp+1
rol a
dey
bne :--
ora #$b0
bvs :---
: jsr cout
pla
bne :-
rts
.endproc
; we are dead and can't even start downloading boot blocks
; so go to next slot if we are booting, or hang otherwise
; can't be used after we go to $300 code
.proc Death
lda $00 ; $00 must be 0
bne :+ ; or we are not booting
lda $01
and #$f0
cmp #$c0 ; $01 must be $Cx
bne :+
lda #$ff
jsr wait ; wait a bit so user can see message
jmp $faba
: sta clraltchar ; make sure alt char set is off
lda #$58 ; flashing (red) X
sta DeathLoc ; on screen
hang: bra hang ; hang
.endproc
; display the "no boot server" message
.proc NoServer
ldx #msg3-msg1
bra Disp
.endproc
; display the "something went wrong" message
.proc ErrorMsg
ldx #msg2-msg1
bra Disp
.endproc
; display the greeting
.proc HelloMsg
jsr title ; apple ii title screen (card boot clears screen)
ldx #msg1-msg1 ; better be zero!
; fall-through
.endproc
; Display one of the messages, can't be used after we go to $300 code
.proc Disp
lda msg1,x
bne :+
rts
: inx ; set up for next message byte
cmp #$18 ; last line + 1
bcc repos
eor #$80
jsr cout ; not supposed to change anything
bra Disp
repos: jsr tabv ; destroys a and y, but not x
lda msg1,x ; get horizontal
sta ch ; and write
inx ; next message byte
bra Disp
.endproc
msg1: .byte 05,08,"NetBoot LC v1.0 by M.G."
.byte 06,05,"Starting up over the network...",$00
msg2: .byte 08,09,"Something went wrong!",$00
msg3: .byte 08,12,"No boot server!",$00
msg4: .byte ", bridge .",$00
msg5: .byte ", zone ", $00
; move $300 code into position
.proc ReloBoot
ldx #BootOSize+1
: lda BootRStrt-1,x
sta $0300-1,x
dex
bne :-
rts
.endproc
; Code to be moved to $300 follows
BootRStrt = *
.org $0300
BootOBgn = *
; Call the card's MLI
.proc GoCard
jmp $C714 ; For slot 7, modify in InitCard
.endproc
; Provide an interrupt handler for the card
.proc CardInt
jsr $C719 ; For slot 7, modify in InitCard
rti
.endproc
; Boot using ATP requests to retrieve boot blocks.
.proc ATPBoot
fetch: lda #1
sta ATPbmap ; want only block 0 (ATP-wise)
ATcall ATPparms
bcs error ; oops
lda Status ; is EOF?
beq :+ ; keep reading if not EOF
sei ; otherwise, no more interrupts
jmp BootStart ; and execute next boot stage
: inc BlkNum ; implicitly limited, below
lda BlkNum ; get it for spinner
and #$03 ; mask in low bits
tay
lda spinner,y ; get spinner char
sta SpinLoc ; put on middle of screen
lda BlkPtr+1 ; block pointer (load addr)
clc
adc #$02 ; $200 bytes
sta BlkPtr+1 ; inc address
cmp #$c0 ; Reading too far?
bcc fetch ; read next block if not
error: jsr bell1 ; beep speaker
lda #$58 ; flashing (red) X
sta SpinLoc ; on screen
hang: bra hang
spinner: .byte '|'+$80
.byte '/'+$80
.byte '-'+$80
.byte '\'+$80
.endproc
ATbridge: .byte $00 ; local bridge
ATnet: .word $0000 ; local net
ATnode: .byte $00 ; our node number
ATPparms: .byte 0,18 ; sync SendATPReq
.word $0000 ; result
.dword $00000000 ; compl. addr
.byte $00 ; socket #
ATPaddr: .dword $00000000 ; destination address
.word $0000 ; TID
.word $0000 ; req buffer size
.dword $00000000 ; req buffer addr
.byte $02 ; boot type
; $01 = IIgs stage 1
; $02 = //e
; $03 = IIgs boot image
BlkNum: .word $0000 ; block number to req
.byte $00 ; unused
.byte $01 ; one response buffer
.dword BDS ; pointer to response BDS
.byte $00 ; ATP flags
.byte 8,32 ; try 32 times every 2 seconds
ATPbmap: .byte $00 ; bitmap of blocks to recieve
.byte $00 ; number of responses
.res 6 ; 6 bytes reserved
BDS: .word $0200 ; length of buffer
BlkPtr: .dword BootStart ; block pointer
Status: .dword $00000000 ; returned user bytes, first byte = 1 if EOF
.word $0000 ; actual length
BootOEnd = *
.assert BootOEnd < $3d0, warning, "Page 3 code too big"
BootOSize = BootOEnd - BootOBgn
; end of $300 code, fix up origin
.org BootRStrt + BootOSize
NBPBuf = (* >> 8 + 1)*$100 ; put this on next page boundary
.out .sprintf("NBP Buffer at $%x", NBPBuf)