Site Tools


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 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.