====== Apple //c and IIc Plus Firmware Bugs ====== ===== Memory Expansion Firmware ===== The memory expansion firmware has the following code to test the size of the memory expansion card, if installed: numbanks equ $03bb ; screen hole - $c0 sizetemp equ $0478 ; screen hole shared addrl equ $bff8 ; slinky address reg for indirect use addrm equ $bff9 ; real registers are from $c0c0-$c0c3 addrh equ $bffa ; .. data equ $bffb ; slinky data reg ; at entry x is expected to have $c8 (slot * $10 + $88) ; and y is expected to contain $c4 (slot $Cn) testsize equ * lda #0 ; zero address reg l/m sta addrl,x sta addrm,x lda #$10 ; start at 1 meg and go down sec tsloop sbc #1 ; move down a bank sta addrh,x lda data,x ; save existing data pha dec addrl,x ; fix address (undo auto-increment) lda #$a5 ; common apple check byte sta data,x ; store it dec addrl,x ; fix... eor data,x ; 0 if the data is there dec addrl,x ; fix... cmp #1 ; C = 0 if data okay pla sta data,x ; restore data lda addrh,x ; <-- SEE COMMENTS BELOW and #$0f ; only lower nibble valid beq tsnoram ; no RAM somehow! [note: if bank 0, skips checking result of RAM test] bcs tsloop ; loop until we find a bank [carry set = no RAM in bank] adc #1 ; C = 0 from compare tsnoram sta numbanks,y lsr a sta sizetemp ; sizetemp = upper byte of block count rts If you note the code starting at the indicated line, you can see that the code grabs the high byte of the Slinky address register and uses the low nibble directly as a counter value. The problem is that when there is no memory expansion card installed, there is no register, and the value there is floating bus. The only reason the code gets out of the loop is because the floating bus happens to usually have a bunch of bytes streaming by that have 0 in the low nibble. That being said, MAME before [[https://github.com/mamedev/mame/commit/bdcb98307822e79c242782ee67bef966bf4c25de|bdcb983]] did not float the bus for $C0C0-$C0CF. A non-floating bus results in ROM $03 hanging when the card is first accessed, and, due to changes made by Apple, a hang at boot for ROM $04 (and presumably IIc Plus ROM $05). Another minor issue is that the code wouldn't find a theoretical Slinky that had only one 64K bank built into it, as the loop terminates when the bank hits 0 (beq tsnoram) even if the carry flag is clear because RAM was found in bank 0. The code could be fixed by using the stack to save the counter and comes out a few bytes shorter: testsize equ * lda #0 ; zero address reg l/m sta addrl,x sta addrm,x lda #$10 ; start at 1 meg and go down sec tsloop sbc #1 ; move down a bank sta addrh,x pha ; save counter, +1 byte code added lda data,x ; save existing data pha dec addrl,x ; fix address (undo auto-increment) lda #$a5 ; common apple check byte sta data,x ; store it dec addrl,x ; fix... eor data,x ; 0 if the data is there dec addrl,x ; fix... cmp #1 ; C = 0 if data okay pla sta data,x ; restore data pla ; restore counter (net save 4 bytes) beq tsnoram ; no RAM somehow! bcs tsloop ; loop until we find a bank adc #1 ; C = 0 from compare tsnoram sta numbanks,y lsr a sta sizetemp ; sizetemp = upper byte of block count rts We could use a couple of those bytes to fix the bank 0 issue: testsize equ * lda #0 ; zero address reg l/m sta addrl,x sta addrm,x lda #$10 ; start at 1 meg and go down sec tsloop sbc #1 ; move down a bank sta addrh,x pha ; save counter, +1 byte code added lda data,x ; save existing data pha dec addrl,x ; fix address (undo auto-increment) lda #$a5 ; common apple check byte sta data,x ; store it dec addrl,x ; fix... eor data,x ; 0 if the data is there dec addrl,x ; fix... cmp #1 ; C = 0 if data okay pla sta data,x ; restore data pla ; restore counter (net save 4 bytes) bcc tsgotram ; RAM found, extra branch net cost 2 bytes beq tsnoram ; still won't find lonely bank 0 bne tsloop ; loop until we find a bank tsgotram adc #1 ; C = 0 from compare tsnoram sta numbanks,y lsr a sta sizetemp ; sizetemp = upper byte of block count rts Since all %%//%%cs and IIc Pluses shipped with a 65C02, the code could be made even smaller (ca65 format): testsize: stz addrl,x ; net save 2 bytes using stz here stz addrm,x lda #$10 ; start at 1 meg and go down tsloop: dec a ; one byte saved using dec a sta addrh,x pha ; save counter, net cost 1 byte lda data,x ; get exiting data pha ; save it dec addrl,x ; fix address (undo auto-increment) lda #$a5 ; common apple check byte sta data,x ; store it dec addrl,x ; fix... eor data,x ; 0 if the data is there dec addrl,x ; fix... cmp #1 ; C = 0 if data okay pla ; get data back sta data,x ; restore data pla ; get counter back, sets N and Z, net save 4 bytes over existing code bcc tsgotram ; RAM found, extra branch net cost 2 bytes beq tsnoram ; still won't find lonely bank 0 bra tsloop ; loop until we find a bank tsgotram: adc #1 ; C = 0 from compare, could also use inc a tsnoram: sta numbanks,y lsr a sta sizetemp ; sizetemp = upper byte of block count rts If it was undesirable to use the stack, we overwrite sizetemp anyway when the routine exits, so we could replace the pha/pla with sta sizetemp/lda sizetemp at the cost of 4 bytes.