#118293 - Bloodypriest - Sun Feb 11, 2007 10:48 pm
Hi,
for the last few days, I've been trying to write a sleep mode handler. I based my code on those two threads: http://forum.gbadev.org/viewtopic.php?t=12011&highlight=hinge and http://forum.gbadev.org/viewtopic.php?t=6980&highlight=sleep+interrupt. It works but only on ARM7; ARM9 locks-up and the LCD won't power up again. After trying different things, I've determined that it's the CP15_WaitForInterrupt function that I'm calling to put the ARM9 in very-low power mode that never returns.
Can someone help me figure this out? I've tried everything I could think of to make it work.
Here's my code:
Arm7 code:
Code: |
void enterSleepMode(void)
{
u32 old_ie;
old_ie = REG_IE;
REG_IE = IRQ_LID|IRQ_KEYS|IRQ_IPC_SYNC;
REG_KEYCNT=BIT(14)|255;
REG_IME = 1;
setLed(PM_LED_SLEEP);
setPowerSaveMode(1);
IPC_SendSync(1); // Signal ARM9: Power-down
swiSleep();
IPC_SendSync(2); // Signal ARM9: Power-up
setPowerSaveMode(0);
REG_IE = old_ie;
}
|
ARM9 code:
Code: |
static void ipcSyncInterruptHandler(void)
{
int ctl = IPC_GetSync();
if (ctl == 1) {
// iprintf("\x1b[13;0HControl 1 pass\n");
powerOFF(POWER_LCD);
CP15_WaitForInterrupt();
}
else if (ctl == 2) {
sleepMs(100);
powerON(POWER_LCD);
// iprintf("\x1b[14;0HControl 2 pass\n");
}
}
|
CP15_WaitForInterrupt comes from Moonshell's CP15.s and is defined as:
Code: |
.global CP15_WaitForInterrupt
CP15_WaitForInterrupt:
mov r0, #0
mcr p15, 0, r0, c7, c0, 4
bx lr
|
#119322 - nio101 - Wed Feb 21, 2007 10:28 pm
Hello,
I wish somebody could help... I'm looking for the very same low power standby feature too, but haven't been able to find anything working...
#119331 - Mighty Max - Wed Feb 21, 2007 11:01 pm
There is a problem in the ARM9 code:
If you are using libnds, then you are going into the wait-for-irq while IME is disabled. This generates some problems:
- the CP15 wait never returns
- if it would return, the irq handler would not enter the irqHandler again, no IPC_Sync_IRQ would have been flagged in REG_IF, thus not turning on the LCD Power
The easiest workaround for the later two (except chainging the irq dispatcher) would be in the irq routine setting the I flag again in cpsr (preventing nested irqs) and afterthat turning IME/IE up again.
The IME disabled causes some more problems when trying to sync while the other side is in any other irq.
I've sent wintermute a report about this problems some weeks ago when i noticed this on some "missing" irqs but yet he didnt reply to it yet.
_________________
GBAMP Multiboot
#119345 - wintermute - Thu Feb 22, 2007 2:43 am
Big clue for me here might be this
Code: |
static void ipcSyncInterruptHandler(void)
|
Given the name of this function I'd assume it's an interrupt handler.
Waiting for an interrupt during an interrupt handler is quite likely to never succeed.
The libnds interrupt dispatcher does not enable nested interrupts by default for various reasons. You might be able to get away with adding an explicit REG_IME = 1; before the wait.
There's also no need to use custom assembly for the low power wait - that's what swiIntrWait does.
Code: |
/*! \fn swiIntrWait(int waitForSet, uint32 flags)
\brief wait for interrupt(s) to occur
\param waitForSet
0: Return if the interrupt has already occured
1: Wait until the interrupt has been set since the call
\param flags
interrupt mask to wait for
*/
void swiIntrWait(int waitForSet, uint32 flags);
|
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog
#119346 - wintermute - Thu Feb 22, 2007 2:56 am
Mighty Max wrote: |
The easiest workaround for the later two (except chainging the irq dispatcher) would be in the irq routine setting the I flag again in cpsr (preventing nested irqs) and afterthat turning IME/IE up again.
|
Why change the I flag in CPSR again? For this sleep code ideally all interrupts should be disabled other than the one you're interested in.
Quote: |
The IME disabled causes some more problems when trying to sync while the other side is in any other irq.
|
This shouldn't be an issue with the latest dispatcher code. REG_IF is cleared on the way in to the handlers again so irqs really shouldn't be getting lost.
Quote: |
I've sent wintermute a report about this problems some weeks ago when i noticed this on some "missing" irqs but yet he didnt reply to it yet. |
Didn't receive your report, sorry. If you have some code that shows missing irqs with latest dispatcher I'll be glad to take a look.
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog
#119349 - Bloodypriest - Thu Feb 22, 2007 4:28 am
So to make it work, I should use swiWaitForIntr instead of the custom CP15 code and enable nested interrupts is that it?
BTW are setting REG_IME=1 and clearing the I flag sufficient to enable nested interrupts or do I need to do something else?
#119352 - Mighty Max - Thu Feb 22, 2007 7:39 am
wintermute wrote: |
Why change the I flag in CPSR again?
|
I was too lazy yesterday to check whether you had cleared it before calling the handlers, so i wanted to stay "safe" ;)
Quote: |
This shouldn't be an issue with the latest dispatcher code. REG_IF is cleared on the way in to the handlers again so irqs really shouldn't be getting lost.
|
It still is:
Code: |
71 mov r3, #0x4000000 @ REG_BASE
72
73 ldr r1, [r3, #0x208] @ r1 = IME
74 str r3, [r3, #0x208] @ disable IME
(...)
143 ldmfd sp!, {r0-r1,r3} @ {spsr, IME, REG_BASE}
144 str r1, [r3, #0x208] @ restore REG_IME
145 msr spsr, r0 @ restore spsr
146 mov pc,lr
|
This is problematic, because:
Unlike the I Flag, IME prevents an incoming irq request beeing recognized in IF, where you clear IF within the irq handler doesnt matter this way. Neither does the cp15 get any notice from the irq, thus not returning on wait for IRQ while IME=0
The whole time the ARM spends in the dispatcher (and the called IRQ handlers) the cpu will not notice any other irq. This espacially creates problems when other IRQs (like fifo, ipc sync) are to be created short after the vsync. They are most likely to be completely lost.
_________________
GBAMP Multiboot
#119394 - wintermute - Thu Feb 22, 2007 5:26 pm
Mighty Max wrote: |
This is problematic, because:
Unlike the I Flag, IME prevents an incoming irq request beeing recognized in IF, where you clear IF within the irq handler doesnt matter this way. Neither does the cp15 get any notice from the irq, thus not returning on wait for IRQ while IME=0
|
I've written some code to test your supposition.
The first test proves that REG_IF gets bits set even with REG_IME disabled.
The second test proves that REG_IF gets updated during a handler.
Code: |
#include <nds.h>
#include <stdio.h>
void WaitVBL() {
while ( REG_VCOUNT != 192);
while ( REG_VCOUNT == 192);
}
vu32 framecounter = 0;
vu32 vcounter = 0;
void vblankHandler() {
framecounter++;
// wait for the vcount irq to be flagged
while ( !(REG_IF & IRQ_VCOUNT));
int x = REG_VCOUNT;
// wait for the next scanline
while ( REG_VCOUNT == x);
}
void vcountHandler() {
vcounter++;
}
int main(void) {
irqInit();
videoSetMode(0);
consoleDemoInit();
irqEnable(IRQ_VBLANK);
// disable interrupts
REG_IME = 0;
// clear all pending interrupts
REG_IF = IRQ_ALL;
// first test check for REG_IF bits being set with REG_IME = 0
while(1) {
scanKeys();
if (keysDown() & KEY_A ) break;
WaitVBL();
printf("\x1b[2;0HREG_IF = %08X",REG_IF);
REG_IF = IRQ_VBLANK;
}
irqSet(IRQ_VBLANK,vblankHandler);
irqSet(IRQ_VCOUNT,vcountHandler);
irqEnable(IRQ_VCOUNT);
// vcount irq on line 40
SetYtrigger(40);
// second test vblank handler spins on REG_IF IRQ_VCOUNT bit
while(1) {
swiWaitForVBlank();
printf("\x1b[4;0Hvcount %d, frame %d",vcounter, framecounter);
}
return 0;
}
|
There are a couple of potential pitfalls.
1. A handler takes longer than the interrupt period ( i.e. a vblank handler takes more than a single frame to process). In this case the handler will effectively take all your CPU if it's the first handler to be installed.
2. A handler delays for 2 or more periods of another interrupt. In this case only one of the other interrupts will be processed.
The second case can be alleviated by enabling REG_IME during the handler so that other interrupts can take place.
The dispatcher does not enable nested interrupts by default for the simple reason that there is then no way to install an interrupt which cannot be interrupted.
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog
#119395 - wintermute - Thu Feb 22, 2007 5:38 pm
Bloodypriest wrote: |
So to make it work, I should use swiWaitForIntr instead of the custom CP15 code and enable nested interrupts is that it?
BTW are setting REG_IME=1 and clearing the I flag sufficient to enable nested interrupts or do I need to do something else? |
With the libnds irq system setting REG_IME to 1 is sufficient, the dispatcher handles the rest.
swiWaitForIntr just does much the same thing as your custom CP15 code. Personally I prefer to use functions supplied by the system rather than add complex or obscure custom code to a project where it might confuse others.
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog
#119405 - NeX - Thu Feb 22, 2007 7:54 pm
I came up with a solution that doesn't stop CPU processing - something I don't know how to do - but turns off the screens and pauses the game.
Code: |
if((keys & KEY_LID)) {powerOFF(POWER_ALL);while((keys & KEY_LID)){scanKeys();keys = keysHeld();}powerON(POWER_ALL);}
|
#119410 - HyperHacker - Thu Feb 22, 2007 8:22 pm
That's going to be using 100% CPU power though. Throw an swiWaitForVBlank() in that loop.
_________________
I'm a PSP hacker now, but I still <3 DS.
#119415 - NeX - Thu Feb 22, 2007 8:55 pm
That's probably a good idea... why didn't I think of that?
Seriously!
#119551 - Bloodypriest - Sat Feb 24, 2007 6:27 am
Thanks, it works now. In the end, I had to lift the swiWaitForIntr from the interrupt handler.
Wintermute, I tested setting REG_IME to 1 before calling swiWaitForIntr like you said and while it did allow the 2nd IPC Sync IRQ to occur and the power to turn back on, it did not allow swiWaitForIntr to seemingly return. Quite strange since to produce this result, it probably had to return once from swiWaitForIntr. It looks like it did the following :
1. Enter IPC_IRQ handler
2. Enter swiWaitForIntr
3. swiWaitForIntr gets interrupted by nested IPC_IRQ
4. Complete nested IPC_IRQ
5. resume waiting with swiWaitForIntr
I was able to consistently reproduce the results. Might this be worth a look?
#119577 - tepples - Sat Feb 24, 2007 1:18 pm
swiWaitForIntr checks a memory location when waiting for an interrupt. This location should always contain the set of interrupt sources that were recently acknowledged. How is your ISR handling this location?
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#119613 - Bloodypriest - Sat Feb 24, 2007 7:11 pm
Correct me if I'm wrong but I believe libnds' swiIntrWait function already handle that memory location on it's own. Here is how I am calling it :
Code: |
REG_IME = 1;
swiIntrWait(1,IRQ_IPC_SYNC);
|
It works when it's lifted out of my interrupt handler but doesn't return when called from inside my interrupt handler (or rather seems to return, handle the interrupt, then resume waiting).