524 lines
19 KiB
ArmAsm
524 lines
19 KiB
ArmAsm
|
; 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
|
||
|
|
||
|
.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
|
||
|
: 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)
|