#177793 - WriteASM - Fri Mar 08, 2013 12:18 am
First a little background: I have been working on a BASIC and ASM IDE for the GBA for two months now (called bASMic), and have been having good success. It is coded in 100% ASM, both ARM and THUMB.
Admittedly, I'm having a hard time describing my problem, but here goes: When "BREAKing" a running program via the Keypad interrupt (a la GBBasic), the CPU can be running either ARM or THUMB, and be in System, Supervisor (=BIOS), or possibly even IRQ mode. After confirming the "BREAK" with the user, it currently overwrites the return address on the IRQ stack, pointing it to a short routine that ensures ARM mode. That routine exits out to "Main_FixAll", which basically resets the GBA without clearing EXTRAM. (Reloads font and palette, also calls "isrInit", among other things.) However, if the keypad interrupt occurs in a BIOS call (=Supervisor mode), something goes wrong with the IRQ stack, which causes the auto-sleep routine to put the GBA to sleep. Although I can wake the GBA via joypad interrupt, the BIOS Sleep function never returns, rendering the GBA uselessly locked up in IRQ mode.
If I write a bASMic program that doesn't use BIOS calls (Div and ArcTan2), it does not lock up when "BREAKing".
I also noticed that "gba_crt0.s" in DevKitPRO has the CPU in System mode when jumping to user code; from reading the ARM7TDMI Reference Manual, I'd have expected it to exit in User mode. Any reasons why, besides more privileges?
Below is the code in "irq.s"; TABS=4 on my system. (Source code for the entire project totals about 519KB.)
Admittedly, I'm having a hard time describing my problem, but here goes: When "BREAKing" a running program via the Keypad interrupt (a la GBBasic), the CPU can be running either ARM or THUMB, and be in System, Supervisor (=BIOS), or possibly even IRQ mode. After confirming the "BREAK" with the user, it currently overwrites the return address on the IRQ stack, pointing it to a short routine that ensures ARM mode. That routine exits out to "Main_FixAll", which basically resets the GBA without clearing EXTRAM. (Reloads font and palette, also calls "isrInit", among other things.) However, if the keypad interrupt occurs in a BIOS call (=Supervisor mode), something goes wrong with the IRQ stack, which causes the auto-sleep routine to put the GBA to sleep. Although I can wake the GBA via joypad interrupt, the BIOS Sleep function never returns, rendering the GBA uselessly locked up in IRQ mode.
If I write a bASMic program that doesn't use BIOS calls (Div and ArcTan2), it does not lock up when "BREAKing".
I also noticed that "gba_crt0.s" in DevKitPRO has the CPU in System mode when jumping to user code; from reading the ARM7TDMI Reference Manual, I'd have expected it to exit in User mode. Any reasons why, besides more privileges?
Below is the code in "irq.s"; TABS=4 on my system. (Source code for the entire project totals about 519KB.)
Code: |
.arm @ the ARM subset is used for interrupts. .text .global isrInit, isrKbdLP, isrKbdBK, isrKbdWK @ Low-Power, BreaK, WaKe. .include "source/defines.h" .equ BIOS_IF, 0x03007FF8 .equ ISR_MAIN, 0x03007FFC @ For ISR mode the stack is by default initialized to 03007FA0h. @NOTE: Only R0-R3, R12 manually PUSHED by the BIOS. If using others, PUSH them, too. @NOTE: Code break (Start+Select) uses a MANUALLY CALCULATED VALUE for the stack offset. @ Interrupt notes: @ Unfortunately, the SWI 2 (Halt) instruction requires that both enable bits be set for an interrupt. Otherwise, it will do nothing. @ That means that we cannot exit the "low-power-mode" without the interrupt handler firing. @ The "OR" mask for the keyboard register is for ANY key--it does not "adapt" to the keystate before the SWI call. @ You cannot use the "unused bits in IF" for special messages, unfortunately. @ @NOTE: Clearing bits in IE does not prevent IF bits from getting set! If you would like to disable an interrupt, clear the @bit in the respective control register, NOT IE! Note that there is no secondary interrupt enable for the cartridge interrupt. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ isrInit: Interrupt Service Routine Initializer. Enable interrupts needed for@ @bASMic's operation. This includes TMRs, VBlank, Keys (for Break), Serial, etc.@ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ isrInit: ldr r0, =ISR_MAIN @ISR_MAIN < The hardcode address the BIOS looks to for the ISR handler routine ldr r1, =0x03003700 @ Get the address to where our interrupt handler will be. str r1, [r0] @ Set the IRQ handler address. @ Copy our ARM interrupt handler to IWRAM for maximum speed. The shorter the ISR takes, the better for all! adr r2, irqHndlr @ Copy Start Location. R1 already has the destination mov r3, #((irqH_End - irqHndlr) / 4) @ copying one long at a time. No LTORGs required @ Copy loop. isrInitLp: ldr r0, [r2], #4 @ Get a long str r0, [r1], #4 @ Write it back subs r3, r3, #1 bne isrInitLp mov r4, #0x03000000 @ Address to IWRAM for setting the ISR handler routines. @================================ @ Enable all interrupts we plan to support. ldr r0, =IE ldr r1, =0b0001000011111001 @13:cartridge, 12:keypad, 7:port, 6-3:timers, 0:VBlank @ ldr r1, =0b0001000000000001 strh r1, [r0] @ Set the interrupt enable bits. @ Set the ISR for VBlank interrupts. adr r0, ihVBlank @ Address to the VBlank handler str r0, [r4, #0xC0] @ ISR routine offset. @ Set the ISR for keypad interrupts adr r0, ihKeypad str r0, [r4, #0xF0] @ Keypad handler offset. @ Turn on the master interrupt enable. ldr r0, =IME mov r1, #0x1 str r1, [r0] @ Master Enable bit. ($4000208 = IME) bx lr @ Return @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ isrKbdLP: Set the keyboard interrupt for the lower-power keyboard mode. @ @ isrKbdBK: Set the keyboard interrupt for the "break" AND mask mode, ISR. @ @ isrKbdWK: Set the interrupt for the "wake" mode, ISR disabled. @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ Initialize the keyboard interrupt for the BREAK combination of START+SELECT. isrKbdLP: @ldr r1, =0b0100001111111111 @ All keys, OR mask (15) mvn r1, #0b1011110000000000 @ All keys, OR mask (15)--remember than an MVN inverts everything! mov r2, #0 @ No ISR b iK_WR @-------------------------------- isrKbdBK: mov r2, #59 @ Keypad ISR "Break" Enabled. Security code to try to prevent lockups. b iK_AndMsk @-------------------------------- isrKbdWK: mov r2, #0 @ No "Break" iK_AndMsk: mov r1, #0xC000 @ Use the Logical AND method (15) and interrupt enable (14) for wakeup. add r1, #0x000C @ Give the mask as START+SELECT. @-------------------------------- @ Write to the actual registers. iK_WR: mov r0, #0x3000000 @ Hardcode address to IWRAM. strb r2, [r0, #255] @ 0x030000FF. "KeypadISR Flags Register" (our doing, not the GBA's) ldr r0, =0x4000132 @ pointer to KeyControl register strh r1, [r0] @ Set the keyboard interrupt's flags bx lr @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ isrHndlr: Interrupt Service Routine Handler. An interrupt occured; determine@ @what it is, and take the appropriate action. @ @ @ @ THE SHORTER THIS ROUTINE, THE BETTER FOR EVERYONE. @ @ Note: The BIOS interrupt handler PUSHes R0-R3, R12 @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ irqHndlr: @push {lr} @ldr r0, =BIOS_IF @BIOS_IF < If you would like to use BIOS VBlank wait SWI, this code must be here. @ldr r1, [r0] @ Otherwise, just comment it out! @orr r1, #0x1 @str r1, [r0] @ "Check loop" to see what caused the interrupt ihInters: mov r3, #0x04000000 @ Going for IF, 0x04000202 add r3, r3, #0x200 @ MOV/ADD/ADD much faster than an LDR @ No SWPH, unfortunately--and a SWP (32-bit) on $4000202 wouldn't work very well. ldrh r12, [r3, #2] @ What interrupt flags are set? @ DON'T reset IF here--as easy as that is to do. That can result in nested interrupts, and those can cause unexplained actions. mov r2, #0x03000000 @ Root address to IWRAM add r2, r2, #0xC0 @ Refine to interrupt handler address. This is much faster than an LDR, and also requires no .ltorg mov r1, #14 @ #of interrupt handlers. @ Loop to find out what caused the interrupt ihLoop: lsrs r12, r12, #1 @ Check the next IE bit. bcs ihJump beq ihClear @ If no set interrupt bits, reset IF ourselves, and exit. If no handler for an interrupt, it is skipped. @ Point to the next interrupt handler address with each iteration. add r2, r2, #4 b ihLoop @ An interrupt bit was set. ihJump: ldr r0, [r2] movs r0, r0 @ Is a handler specified? beq ihLoop @ No handler. bx r0 @ There you go. @---------------------------- ihClear: mvn r1, #0 strh r1, [r3, #2] @ Reset all the interrupt flags. bx lr irqH_End: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ ihVBlank: Interrupt Service Routine for the VBlank Interrupt. @ @Make Sprite #0 blink on and off at ~2Hz, also handle a 3-minute timeout. @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ihVBlank: ldr r0, [sp, #-32] @ Get the "static" value from the IRQ Stack, containing a "VBlank Count" add r0, r0, #1 @ Increment it str r0, [sp, #-32] @ Save it again. @ Provide a 3-minute timeout. "Keyboard" will reset this if a key pressed. cmp r0, #10816 @ barely fits in the CMP, but it does fit. @DEBUG: This a weak Band-Aid patch to fix an infinite hang after a BREAK. (Should be a BGT.) @In other words, a BEQ doesn't solve the problem, but merely reduces the chances you'll hit it. @The problem seems to be when we try to BREAK from a SWI. Perhaps we need to reset the Supervisor stack? beq ihVB_PSave @ Power save...DEBUG: Only if an EXACT match. @ Make the cursor sprite blink. MODding by binary powers is relatively easy. @-------------------------------- tst r0, #0x1F @ 32 counts, toggle on-and-off once per second. bne ihVB_Done @ Reset the VBlank Interrupt Flag. mov r1, #0x7000000 @ start address for OAM fits nicely in a MOV...! ldrh r2, [r1] @ INT(0) has the Y position and the "Visible" bit eoreq r2, #0b1000000000 @ 9th bit is "Disable" strh r2, [r1] @ Write the bit back. @-------------------------------- ihVB_Done: mov r1, #1 @ Reset the VBlank interrupt flag strh r1, [r3, #2] bx lr @ Done. @================================ @ Timeout! The keyboard mode should be set to interrupt on any key press, so just sleep. ihVB_PSave: mov r0, #0 @ Reset the timeout str r0, [sp, #-32] @ There are some bugs with BREAKing that cause catastrophic "sleep hangs" here. You can wake it just fine, but it's locked up. @VisualBoyAdvance points to the SWI below as being the problem. However, VBA also has some bugs with interrupt emulation... mvn r1, #0 @ We have to reset flags so the SWI can go to sleep! strh r1, [r3, #2] @ Sleeping in IRQ mode /shouldn't/ be a problem. swi #0x30000 @ Full Stop. Code execution halts here until the interrupt. As the display is off, there is no VBL interrupt. b ihVB_Done @ Reset the VBlank interrupt flag, although that won't be what woke it. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ datKeyBrk: .ascii "USER BREAK.\x80","Exit Interpreter?\x00" .align @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ ihKeypad: Interrupt Service Routine for the Keypad Interrupt. @ @If the Interpreter is running, we have to BREAK it. Unfortunately, the CPU can@ @be in ARM -or- THUMB mode, as well as in System, IRQ and Supervisor modes! @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ihKeypad: mov r0, #0x03000000 ldrb r0, [r0, #255] @ 0x030000FF cmp r0, #59 @ Security code bne ihKeyRet @ If NE, clear the interrupt flag and exit. @-------------------------------- @ Keypad Break interrupt enabled. That means we're in the THUMB Interpreter. (But could be running ARM routines) push {r3, lr} @ One more stack level--and save the ~address to the IF register (R3=0x04000200) adr r0, datKeyBrk mov r3, #s_Text @ on the "run" text screen. bl RMsgBox @ Red MsgBox... pop {r3, lr} @ Get the stack back to just what the BIOS did. @- - - - - - - - - - - - - - @ If OK, we have to quit the Interpreter, and set the return address of our own call stack to Main_FixAll. cmp r0, #msgCancel beq ihKeyRet @ Cancel selected, so continue running the program. @ OK selected. Disable interrupts, and return to Main_FixAll. R3 = 0x04000200 @NOTE: The BIOS return is by "SUB PC, LR+4": the subset will return to what was last set there by a BX. adr r0, ihThumbRet +4 @ Counteract the BIOS's 4-byte offset. str r0, [sp, #4*5] @ The BIOS PUSHed R0,R1,R2,R3,R12,--LR--. R0 is first on the stack, LR is last @ Clear the keypad interrupt flag, return. ihKeyRet: mov r1, #4096 @ 1<<12 = Keypad flag. strh r1, [r3, #2] @ Reset the keypad interrupt flag. bx lr @ Return...or BREAK, depending on whether we messed with the return address. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .thumb @ The ARM specification indicates that upon the return from IRQ, FIQ, UNDEF, ABORT, SWI modes, @the destination language will be THUMB if that was what it was before the event. Consequently, we must have @our own BX mode-changer. BX is the ONLY instruction that can switch between ARM and THUMB @ @ But sometimes, the interrupted routine will have been in ARM mode (MSGBOX, INPUTBOX, PSET). @The next three lines assemble in ARM to "andge r4, r1, r0, ASR #13", "andeq r4, r0, r0, LSL #14"...quite harmless. ihThumbRet: nop @ for ARM "runover" upon return. Without this, it's "STRMI R10, [r0, -r0]" adr r0, ihTR_Ret bx r0 @ Switch to ARM mode. Or, if we're already there, do some nonsense ANDs, and fall through to the Branch. @//////////////////////////// .arm .align @DEBUG: Make sure we're in System mode. A hacked return to IRQ or Supervisor mode wouldn't work too well. @Seems this code doesn't exist--it doesn't work /with/ it, and it doesn't work /without/ it! ihTR_Ret: mov r0, #0x1F @DEBUG: Switch to System Mode, all flags cleared. msr cpsr, r0 @ When changing system mode, it technically should be a R-M-W. (ARM Reference Manual.) b Main_FixAll @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |