Site Tools


Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
articles:iicplus_merlin [2017/12/14 03:03]
M.G. created
articles:iicplus_merlin [2017/12/14 19:07] (current)
M.G. [A Wild Bug Appears]
Line 1: Line 1:
-====== Apple IIc Plus and Merlin ======+====== Apple IIc Plus, ROM 5X, and Merlin ======
  
 +Written on 2017/12/14.
 +
 +===== A Wild Bug Appears =====
 +
 +Recently, "Apple II Enthusiasts"​ Facebook group members Skander Ben Abdelkrim, Eric Leung, and Wyatt Wong reported a bug, seemingly in ROM 5X for the Apple IIc Plus:  The Merlin assembler, specifically the 128K versions 2.58 and 2.59, would crash at the main menu when ROM 5X was installed, but worked fine with the stock Apple ROM.
 +
 +This had me a bit perplexed, as I ran through possibilities in my head.  After booting a copy of [[http://​mirrors.apple2.org.za/​ftp.apple.asimov.net/​images/​programming/​assembler/​merlin/​Merlin-8%20v2.58%20%28ProDOS%29%20Disk%201-2.dsk|Merlin 2.58]] in an emulator and looking at its normal behavior, I had a place to start - the crash was happening immediately after the prompt was displayed at the main menu.
 +
 +===== Using the Source =====
 +
 +The source code for Merlin 2.59 is [[http://​mirrors.apple2.org.za/​ftp.apple.asimov.net/​images/​programming/​assembler/​merlin/​MER8.Src.259.SHK|available]]. ​ So naturally this would provide a good place to start. ​ In the Merlin code in the file ''​Macexec.S''​ we find the menu display/​prompt/​input code:
 +
 +<​code>​
 +         ​JSR ​  MENU
 +
 +WNDUP    PRINT 81;"​%"​
 +         ​JSR ​  ​JCLOSE
 +:BL      JSR   BELL
 +         ​JSR ​  RDKEY
 +</​code>​
 +
 +Now it is a fact that the % prompt appears before the crash, so it has to be after the % is printed on the screen. ​ The main thing that sticks out here is the ''​JSR BELL''​. ​ That's suspicious because in the affected versions of ROM 5X, the main hook into the ROM 5X code is in the BELL1 routine that actually sounds the system bell.
 +
 +Now, there'​s nothing special about the ROM 5X BELL1 code, it looks like this (ca65-format):​
 +
 +<​code>​
 +        .org    $FBDD
 +BELL1: ​ lda     #$40
 +        sta     ​$C028 ​  ; original: ​ jsr     WAIT
 +        ldy     #​$C0 ​   ; "​BELL1.2"​ in the IIc Technical ref 2nd ed.
 +:       ​lda ​    #$0C
 +        jsr     WAIT
 +        lda     ​SPEAKER
 +        dey
 +        bne     :-
 +        rts
 +</​code>​
 +
 +The first two instructions starting at BELL1 produce a short delay before the bell is sounded, so that multiple bells are distinct. ​ The remaining code starting at ''​ldy #​$C0''​ makes the actual beep sound.
 +
 +The ''​sta $c028''​ switches to the aux firmware bank to the ROM 5X dispatcher, which does one of a few things depending on the contents of the A register. ​ Two of them, if the A register contains $A9 or $EA, are to dispatch the reset or boot hooks that provide for the main ROM 5X features. ​ It would be obvious if we were accidentally hitting these. ​ One is to sound the classic "air raid beep" if the A register contains $40, like it would if someone were calling BELL1 to sound the speaker. ​ Finally, if the A register is not one of those three values, the dispatcher calls the WAIT routine and exits back to the main firmware at the ''​ldy #​$C0''​ and falls through to the original BELL sounder. ​ We'd know if we were hitting that if we heard the "IIc Plus beep"​. ​ But we don't actually hear the beep that Merlin is attempting to make near where it crashes. ​ We crash into the monitor and that is what causes the beep we actually hear.
 +
 +I hadn't really tested this fall-through much and Occam'​s Razor suggested that Merlin was using its own delay value and calling BELL1 at the ''​jsr WAIT''/''​sta $C028''​ instruction,​ and that I was doing something like trashing the stack or returning an unexpected value.
 +
 +At that point I spent an hour looking at my code, testing all of the possibilities,​ and could not replicate the issue. ​ I did confirm that the BELL1 patch was causing the Merlin crash, though.
 +
 +So I was back to square one.
 +
 +===== Disassembling the ROM, redux =====
 +
 +When I implemented ROM 5X, I patched the beep code out of convenience. ​ I hadn't originally set out to make the IIc Plus beep sound like the other Apple IIs.  Its different sound wasn't a bug to my ears, any more than the distinctive "​bonk"​ of the Apple IIgs.
 +
 +However, the popularity of [[http://​quinndunki.com/​OGOL/​Home.html|Quinn Dunki]]'​s [[http://​quinndunki.com/​blondihacks/?​p=2471|beep patch]] was brought to my attention via the requests of several people interested in my ROM projects. ​ The fact that I had already hijacked the BELL1 routine was mighty convenient.
 +
 +At any rate, I had to see what the Apple IIc Plus BELL1 routine was doing in full, because whatever it was, it wasn't triggering the Merlin bug.
 +
 +Quinn'​s excellent work already showed why the the beep was different based on the IIc Plus's aux-ROM WAIT implementation,​ and there is no need to do that work again here.
 +
 +However, there'​s one little tidbit that wasn't important to the sound of the beep, but was important to the behavior of the system. ​ The IIc Plus main-ROM WAIT routine is implemented thusly:
 +
 +<​code>​
 +        .org    $FCA8
 +WAIT:   nop
 +        jmp     ​$C2EE ​   ; what's this?
 +LFCAC: ​ sta     ​$C028 ​   ; bank switch
 +        nop
 +        nop
 +        nop
 +        nop
 +        rts
 +</​code>​
 +
 +The label LFCAC is important, because it's right after we jumped away from the original routine. ​ We eventually come back here, which we will get to momentarily. ​ Importantly,​ as Quinn covered, the ''​sta $C028''​ goes to the aux ROM bank to the new IIc Plus speed-compensated WAIT routine.
 +
 +So what does the code at $C2EE do?
 +
 +<​code>​
 +        .org    $C2EE
 +        phx
 +        jsr     $CFE5
 +        jsr     LFCAC
 +        inc     ​$C000,​x
 +        plx
 +        lda     #$00
 +        rts
 +</​code>​
 +
 +Okay, we save the X register, call whatever'​s at $CFE5, and then call the WAIT routine in the aux bank, back at $FCAC. ​ Finally, we access something in the I/O page, indexed by the X register which must be set by the $CFE5 routine, because it's certainly not set here.  We restore X and zero the accumulator,​ and we are done.
 +
 +The accumulator is zeroed because WAIT always returns with it zeroed according to Apple documentation. ​ So that's an easy one.  What happens at $CFE5 and why do we hit the I/O page?
 +
 +Well here is what's at $CFE5:
 +
 +<​code>​
 +        .org    $CFE5
 +        ldx     #​$81 ​   ; $C081 = ROMIN
 +        bit     ​$C012 ​  ; RDLCRAM - state of LC RAM
 +        bpl     ​LCFF8 ​  ; if off
 +        ldx     #​$8B ​   ; $C08B = LCBANK1
 +        bit     ​$C011 ​  ; RDLCBNK2 - state of LC RAM bank 1/2
 +        bpl     ​LCFF5 ​  ; if off
 +        ldx     #​$83 ​   ; $C083 = LCBANK2
 +LCFF5: ​ sta     ​$C081 ​  ; ROMIN
 +LCFF8: ​ rts
 +</​code>​
 +
 +Aha!  This code saves the language card state in the X register, and enables the ROM.  $C000+X is the soft switch needed to restore the language card state to what it was while we are accessing this routine.
 +
 +There'​s just one problem.
 +
 +===== This Code *is* in the ROM! =====
 +
 +This code saves the language card state and switches to the ROM.  But we just found this code in the ROM.  What gives?
 +
 +Initially I had assumed the IIc Plus had some weird thing happening that would put it at odds with the other Apple IIs with regard to the memory select soft switches.
 +
 +But, then I had another idea.
 +
 +===== Or is it? =====
 +
 +What would happen if this code wasn't in the ROM.  Say, perhaps, executing out of the language card RAM?
 +
 +Pause for a moment and think about that.
 +
 +Well, the original code works fine because it makes sure the ROM is present and then restores whatever language card/ROM state was when it was called. ​ Save language card state, put the ROM in, switch to aux firmware, do WAIT, switch to main firmware, restore language card state, voila! ​ So that carries on to the BELL routine, nice and easy.
 +
 +So what if the ROM 5X BELL code is running out of the language card RAM?
 +
 +Well, we switch to the aux ROM, but nothing happens to our code because the ROM bank toggle doesn'​t affect the RAM.  The code falls through to the "​BELL1.2"​ code which should sound a standard Apple IIc Plus beep.  The beep code calls WAIT, which as we see at the $CFE5 routine saves the language card state and turns on the ROM... ​
 +
 +===== Crash! =====
 +
 +What happened?  ​
 +
 +Well... ... ... We had already toggled the firmware bank once.  So the aux ROM was already switched in when the $CFE5 code enabled the ROM.
 +
 +The next instruction was not what it was supposed to be.
 +
 +Could that be our problem?
 +
 +It sure can!
 +
 +Going back to the Merlin code, this time in ''​Interpreter.S''​ which is the source to MERLIN.SYSTEM,​ we find this little tidbit.
 +
 +<​code>​
 +:NOX     ​BIT ​  ​CONTROL!BANK1!ROMREAD!ENABLE
 +         ​BIT ​  ​CONTROL!BANK1!ROMREAD!ENABLE
 +         ​LDA ​  #$FF
 +         ​STA ​  ​ENDMOVE
 +         ​STA ​  ​ENDMOVE+1
 +         ​STY ​  FROM
 +         ​STY ​  DEST
 +         ​LDA ​  #$F8
 +         ​STA ​  ​FROM+1
 +         ​STA ​  ​DEST+1
 +         ​JSR ​  ​MOVE ​      ;Move monitor
 +</​code>​
 +
 +One comment says it all: "Move monitor"​. ​ That code copies everything from $F800 to $FFFF right back on top of $F800 to $FFFF... reading from ROM and writing the aux language card space.
 +
 +So it turns out that ROM 5X was running from the RAM after all.
 +
 +===== Fixing ROM 5X =====
 +
 +The fix is easy... either don't mess with the BELL routine, or make sure we get back to the right place if we are running out of RAM, and the latter is exactly what happens in the 12/10/2017 update of ROM 5X.
 +
 +While there are other miscellaneous changes and code reorganizations around fixing this problem (they can be seen in the [[https://​github.com/​mgcaret/​rom4x/​commit/​d5969104cdca6fb3746e47080364c6deb2874f7b|commit]]),​ they are not centrally important to the problem we are fixing.
 +
 +The main parts of the fix are as follows: ​ (Important note: The ROM in $Cxxx is //always visible// no matter what, there'​s no way to switch it out for RAM.)
 +
 +Move the dispatcher bank switch out of the BELL1 routine to a few free bytes at $C7FC in the main bank and $C7FF of the aux bank:
 +
 +<​code>​
 +; Main bank:
 + .org  $c7fc
 + sta   $c028
 +; Aux bank:
 +        .org  $c7ff
 +        jmp   ​$fb3c ​    ; to 5X dispatch
 +</​code>​
 +
 +Now, to do the beep fix, we patch BELL1:
 +
 +<​code>​
 +        .org  $fbe6
 +        jmp   ​$c2fc ​       ; to 5X beep with merlin fix
 +</​code> ​  
 +
 +This replaces the ''​jsr WAIT''​ in the "​BELL1.2"​ portion to trigger the "air raid beep" sound via the ROM 5X dispatcher, using the preceding ''​lda #​$0c''​ as the function code.
 +
 +Now, we know that we have to jump through the language card hoops in order to run out of RAM, so that's what happens at $C2FC:
 +
 +<​code>​
 +        .org  $c2fc
 +        phx
 +        jmp   ​$c9a1 ​    ; next step
 +</​code>​
 +
 +There'​s not a ton of space, so we have just enough room to save the X register and jump to some more code:
 +
 +<​code>​
 +        .org  $c9a1
 +        jsr   ​$cfe5 ​  ; get memory config we need to fix
 +        jsr   ​$c7fc ​  ; call ROM 5X dispatch
 +        jmp   ​$c2f5 ​  ; fix memory, restore x, a=$00
 +</​code>​
 +
 +We make use of the IIc Plus's existing routines to save and restore the language card state, and in between we call the ROM 5X dispatch, which executes the classic "air raid beep" that the Apple II line is known for.  In fact, this can be used to call any of the ROM 5X functions, now or in the future, and preserve the RAM state, provided we don't care about the contents of the A register.
 +
 +
 +===== Conclusion =====
 +
 +Looking into the ROM further, the only time that the stock IIc Plus monitor ($F800-$FFFF) switches into the aux ROM is for the WAIT routine.
 +
 +There are plenty of programs that copy the ROM into RAM in order to modify it or make use of the routines in a non-standard memory configuration. ​ That's what's going on here.
 +
 +Apple'​s engineers probably hit this problem early on in testing the IIc Plus, perhaps noting an odd crash in a popular assembler.