diff --git a/include/arm11/drivers/lgy11.h b/include/arm11/drivers/lgy11.h index b8d3d18..55985c0 100644 --- a/include/arm11/drivers/lgy11.h +++ b/include/arm11/drivers/lgy11.h @@ -57,6 +57,17 @@ ALWAYS_INLINE Lgy11* getLgy11Regs(void) void LGY11_switchMode(void); +void LGY11_setGbaKeypadWakeBridge(bool enabled); + +static inline bool LGY11_isGbaSleeping(void) +{ + return (getLgy11Regs()->sleep & BIT(1)) != 0u; +} + +static inline void LGY11_wakeGba(void) +{ + getLgy11Regs()->sleep |= (u16)(BIT(0) | BIT(1)); +} static inline void LGY11_setInputState(const u16 pressed) { diff --git a/include/arm11/power.h b/include/arm11/power.h index 713e5c2..3376e61 100644 --- a/include/arm11/power.h +++ b/include/arm11/power.h @@ -18,6 +18,8 @@ * along with this program. If not, see . */ +#include "types.h" + #ifdef __cplusplus @@ -25,9 +27,10 @@ extern "C" { #endif +bool power_sleep(void); [[noreturn]] void power_off(void); [[noreturn]] void power_reboot(void); #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/include/arm9/lgy7_code.h b/include/arm9/lgy7_code.h index 6eb3a9d..239f9e2 100644 --- a/include/arm9/lgy7_code.h +++ b/include/arm9/lgy7_code.h @@ -33,7 +33,6 @@ extern const u32 _gba_vector_overlay_size[]; // ARM7 boot code for GBA mode. [[noreturn]] void _gba_boot(void); -extern u8 _gba_boot_swi_a9_addr[]; // "swi 0x01" location in ARM9 address space. extern const u32 _gba_boot_size[]; #ifdef __cplusplus diff --git a/include/drivers/gfx.h b/include/drivers/gfx.h index 308c04b..59b6357 100644 --- a/include/drivers/gfx.h +++ b/include/drivers/gfx.h @@ -278,6 +278,14 @@ void GFX_sleep(void); */ void GFX_sleepAwake(void); +/** + * @brief Reinitializes graphics hardware after whole-system PDN sleep. + * + * Unlike GFX_sleepAwake(), this performs a full hardware reset. + * VRAM contents are lost and must be reconstructed by the caller. + */ +void GFX_sleepAwakeCold(void); + bool GFX_setupExceptionFrameBuffer(void); #endif // #ifdef __ARM11__ diff --git a/include/drivers/lgy_common.h b/include/drivers/lgy_common.h index fae506e..f550539 100644 --- a/include/drivers/lgy_common.h +++ b/include/drivers/lgy_common.h @@ -89,6 +89,7 @@ typedef struct Result LGY_prepareGbaMode(bool directBoot, const u32 saveType, const char *const savePath); +Result LGY_setGbaStopVector(bool enabled); Result LGY_setGbaRtc(const GbaRtc rtc); Result LGY_getGbaRtc(GbaRtc *const out); Result LGY_backupGbaSave(void); diff --git a/include/ipc_handler.h b/include/ipc_handler.h index 30a5135..4206737 100644 --- a/include/ipc_handler.h +++ b/include/ipc_handler.h @@ -76,7 +76,8 @@ typedef enum // Miscellaneous API. //IPC_CMD9_TEST = MAKE_CMD9(0, 0, 0), - IPC_CMD9_PREPARE_POWER = MAKE_CMD9(0, 0, 0) // Also used for panic() and guruMeditation(). + IPC_CMD9_PREPARE_POWER = MAKE_CMD9(0, 0, 0), // Also used for panic() and guruMeditation(). + IPC_CMD9_SET_GBA_STOP_VECTOR = MAKE_CMD9(0, 0, 1) } IpcCmd9; enum {_CMD11_C_BASE = __COUNTER__ + 1}; // Start at 0. diff --git a/include/system.h b/include/system.h index 0ff2b7e..5e4b0ca 100644 --- a/include/system.h +++ b/include/system.h @@ -30,6 +30,7 @@ extern "C" //void WEAK __systemInit(void); #ifdef __ARM11__ +void __systemWaitCore1Standby(void); void __systemBootCore1(void (*entry)(void)); #endif diff --git a/source/arm11/drivers/gfx.c b/source/arm11/drivers/gfx.c index cd9ee15..81ef3e5 100644 --- a/source/arm11/drivers/gfx.c +++ b/source/arm11/drivers/gfx.c @@ -302,35 +302,8 @@ static void oldBootloaderWorkaround(void) (void)MCU_getIrqs(MCU_LCD_IRQ_MASK); } -void GFX_init(const GfxFmt fmtTop, const GfxFmt fmtBot, const GfxTopMode mode) +static void initVramAfterReset(void) { - // Initialize GFX state. - GfxState *const state = &g_gfxState; - const u8 mcuLcdState = MCU_LCD_PWR_TOP_BL_OFF | MCU_LCD_PWR_BOT_BL_OFF | MCU_LCD_PWR_OFF; - const u32 lcdLum = 1; // TODO: Better default. - state->swap = 0; - state->swapMask = 3; // Double buffering enabled for both. - state->mcuLcdState = mcuLcdState; - state->lcdLum = lcdLum; - - // If the previous FIRM does not wait for LCD MCU events - // we will get them unexpectedly and this will most likely trigger a panic(). - // TODO: If Luma3DS/fastboot3DS eventually fix this remove this workaround. - oldBootloaderWorkaround(); - - // Do a full hardware reset. - hardwareReset(); - - // Create IRQ events. - // PSC0, PSC1, PDC0, PDC1, PPF, P3D. - static_assert(IRQ_P3D - IRQ_PSC0 == 5); - for(unsigned i = 0; i < 6; i++) - { - KHandle kevent = createEvent(false); - bindInterruptToEvent(kevent, IRQ_PSC0 + i, 14); - state->events[i] = kevent; - } - // Not in gsp. Clear entire VRAM. // Note: Benchmarks show that // - 1 fill for the entire VRAM is the slowest. @@ -349,17 +322,19 @@ void GFX_init(const GfxFmt fmtTop, const GfxFmt fmtBot, const GfxTopMode mode) //GX_displayTransfer((u32*)VRAM_BASE, PPF_DIM(128, 128), (u32*)(VRAM_BASE + 0x8000), PPF_DIM(128, 128), // PPF_O_FMT(GX_ABGR4) | PPF_I_FMT(GX_ABGR4) | PPF_NO_TILED_2_LINEAR); GX_textureCopy((u32*)VRAM_BASE, 0, (u32*)(VRAM_BASE + 16), 0, 16); +} - // Allocate our frame buffers. - allocateFramebufs(fmtTop, fmtBot, mode); +static void initDisplayAfterReset(void) +{ + const GfxState *const state = &g_gfxState; // Get the display controllers up and running. // This needs to happen before LCD init (LCDs need some clock pulses to reset?). - displayControllerInit(mode); + displayControllerInit(state->mode); // Initialize/power on the LCDs. // Note: This will also enable forced black output. - LCD_init(mcuLcdState<<1, lcdLum); + LCD_init(state->mcuLcdState<<1, state->lcdLum); // Not in gsp. Ensure VRAM is cleared. // Also wait for the dummy display transfer/texture copy. @@ -371,6 +346,43 @@ void GFX_init(const GfxFmt fmtTop, const GfxFmt fmtBot, const GfxTopMode mode) GFX_setForceBlack(false, false); } +void GFX_init(const GfxFmt fmtTop, const GfxFmt fmtBot, const GfxTopMode mode) +{ + // Initialize GFX state. + GfxState *const state = &g_gfxState; + const u8 mcuLcdState = MCU_LCD_PWR_TOP_BL_OFF | MCU_LCD_PWR_BOT_BL_OFF | MCU_LCD_PWR_OFF; + const u32 lcdLum = 1; // TODO: Better default. + state->swap = 0; + state->swapMask = 3; // Double buffering enabled for both. + state->mcuLcdState = mcuLcdState; + state->lcdLum = lcdLum; + + // If the previous FIRM does not wait for LCD MCU events + // we will get them unexpectedly and this will most likely trigger a panic(). + // TODO: If Luma3DS/fastboot3DS eventually fix this remove this workaround. + oldBootloaderWorkaround(); + + // Do a full hardware reset. + hardwareReset(); + + // Create IRQ events. + // PSC0, PSC1, PDC0, PDC1, PPF, P3D. + static_assert(IRQ_P3D - IRQ_PSC0 == 5); + for(unsigned i = 0; i < 6; i++) + { + KHandle kevent = createEvent(false); + bindInterruptToEvent(kevent, IRQ_PSC0 + i, 14); + state->events[i] = kevent; + } + + initVramAfterReset(); + + // Allocate our frame buffers. + allocateFramebufs(fmtTop, fmtBot, mode); + + initDisplayAfterReset(); +} + void GFX_deinit(void) { // Power off backlights and LCDs if on. @@ -665,6 +677,14 @@ void GFX_sleepAwake(void) GFX_setForceBlack(false, false); } +void GFX_sleepAwakeCold(void) +{ + // Whole-system PDN sleep resets the graphics blocks and loses VRAM. + hardwareReset(); + initVramAfterReset(); + initDisplayAfterReset(); +} + bool GFX_setupExceptionFrameBuffer(void) { // Check if we can access GX regs. diff --git a/source/arm11/drivers/lgy11.c b/source/arm11/drivers/lgy11.c index 82fb871..aa33e1d 100644 --- a/source/arm11/drivers/lgy11.c +++ b/source/arm11/drivers/lgy11.c @@ -32,15 +32,25 @@ +static volatile bool g_gbaKeypadWakeEnabled = true; + static void lgySleepIsr(u32 intSource) { Lgy11 *const lgy11 = getLgy11Regs(); if(intSource == IRQ_LGY_SLEEP) { - // Workaround for The Legend of Zelda - A Link to the Past. - // This game doesn't set the IRQ enable bit so we force it - // on the 3DS side. Unknown if other games have this bug. - REG_HID_PADCNT = lgy11->padcnt | BIT(14); + if(g_gbaKeypadWakeEnabled) + { + // Workaround for The Legend of Zelda - A Link to the Past. + // This game doesn't set the IRQ enable bit so we force it + // on the 3DS side. Unknown if other games have this bug. + REG_HID_PADCNT = lgy11->padcnt | BIT(14); + } + else + { + // Host-controlled sleep is woken explicitly through LGY_SLEEP_CNT. + REG_HID_PADCNT = 0; + } } else // IRQ_HID_PADCNT { @@ -120,6 +130,7 @@ Result LGY_prepareGbaMode(bool directBoot, const u32 saveType, const char *const setFcramModeForLegacy(LGY_MODE_AGB); // Setup IRQ handlers and sleep mode handling. + g_gbaKeypadWakeEnabled = true; getLgy11Regs()->sleep = BIT(15); IRQ_registerIsr(IRQ_LGY_SLEEP, 14, 0, lgySleepIsr); IRQ_registerIsr(IRQ_HID_PADCNT, 14, 0, lgySleepIsr); @@ -127,6 +138,18 @@ Result LGY_prepareGbaMode(bool directBoot, const u32 saveType, const char *const return RES_OK; } +Result LGY_setGbaStopVector(const bool enabled) +{ + const u32 cmdBuf[1] = {enabled}; + return PXI_sendCmd(IPC_CMD9_SET_GBA_STOP_VECTOR, cmdBuf, 1); +} + +void LGY11_setGbaKeypadWakeBridge(const bool enabled) +{ + g_gbaKeypadWakeEnabled = enabled; + if(!enabled) REG_HID_PADCNT = 0; +} + Result LGY_setGbaRtc(const GbaRtc rtc) { return PXI_sendCmd(IPC_CMD9_SET_GBA_RTC, (u32*)&rtc, sizeof(GbaRtc) / 4); diff --git a/source/arm11/power.c b/source/arm11/power.c index 17f4ac7..7f439f9 100644 --- a/source/arm11/power.c +++ b/source/arm11/power.c @@ -18,8 +18,12 @@ #include "types.h" #include "util.h" +#include "system.h" #include "ipc_handler.h" +#include "arm11/drivers/interrupt.h" #include "arm11/drivers/mcu.h" +#include "arm11/drivers/pdn.h" +#include "arm11/drivers/scu.h" #include "arm11/drivers/timer.h" #include "drivers/cache.h" #include "drivers/pxi.h" @@ -27,6 +31,47 @@ +static vu32 g_powerSleepCore1Ready = 0u; +static vu32 g_powerSleepCore1Release = 0u; +static vu32 g_powerSleepCore1Resumed = 0u; + + + +static void power_sleep_core1(void) +{ + Scu *const scu = getScuRegs(); + + /* core1Standby() unregisters IPI1 before dispatching this function. */ + IRQ_registerIsr(IRQ_IPI1, 14, 0, (IrqIsr)NULL); + + g_powerSleepCore1Ready = 1u; + __dsb(); + + u32 cpuStat = scu->cpu_stat; + cpuStat &= ~SCU_STAT_MASK(1u); + cpuStat |= SCU_STAT_DORMANT(1u); + scu->cpu_stat = cpuStat; + __dsb(); + + /* PDN wake resumes this execution context. */ + __wfi(); + + cpuStat = scu->cpu_stat; + cpuStat &= ~SCU_STAT_MASK(1u); + cpuStat |= SCU_STAT_NORMAL(1u); + scu->cpu_stat = cpuStat; + __dsb(); + + g_powerSleepCore1Resumed = 1u; + __dsb(); + + while(g_powerSleepCore1Release == 0u) + __asm__ volatile("nop"); + + IRQ_unregisterIsr(IRQ_IPI1); + /* Return to libn3ds core1Standby(). */ +} + static void power_safe_halt(void) { PXI_sendCmd(IPC_CMD9_PREPARE_POWER, NULL, 0); @@ -34,6 +79,77 @@ static void power_safe_halt(void) cleanDCache(); } +bool power_sleep(void) +{ + Pdn *const pdn = getPdnRegs(); + Scu *const scu = getScuRegs(); + + /* GFX_sleep() must have powered VRAM down before whole-system sleep. */ + if((pdn->cnt & PDN_CNT_VRAM_OFF) == 0u) + return false; + + const u32 wakeEnableBefore = pdn->wake_enable; + + /* Clear deferred MCU/PDN events before arming the system wake path. */ + (void)MCU_getIrqs(0xFFFFFFFFu); + const u32 staleReason = pdn->wake_reason; + if(staleReason != 0u) + pdn->wake_reason = staleReason; + pdn->wake_enable = PDN_WAKE_SHELL_OPENED | PDN_WAKE_MCU; + + /* Tell the MCU that the application processor is entering sleep. */ + if(!MCU_writeReg(MCU_REG_SYS_PWR, BIT(4))) + { + pdn->wake_enable = wakeEnableBefore; + return false; + } + + g_powerSleepCore1Ready = 0u; + g_powerSleepCore1Release = 0u; + g_powerSleepCore1Resumed = 0u; + __dsb(); + + /* Core 1 is idle here and can enter dormant state in parallel with Core 0. */ + __systemBootCore1(power_sleep_core1); + while(g_powerSleepCore1Ready == 0u) + __asm__ volatile("nop"); + while((scu->cpu_stat & SCU_STAT_MASK(1u)) != SCU_STAT_DORMANT(1u)) + __asm__ volatile("nop"); + + u32 cpuStat = scu->cpu_stat; + cpuStat &= ~SCU_STAT_MASK(0u); + cpuStat |= SCU_STAT_DORMANT(0u); + scu->cpu_stat = cpuStat; + __dsb(); + + /* Preserve the hardware VRAM-off status while asserting only SLEEP. */ + pdn->cnt = (u16)((pdn->cnt & PDN_CNT_VRAM_OFF) | PDN_CNT_SLEEP); + __dsb(); + __isb(); + __wfi(); + + /* Core 0 resumed in the same context. Rejoin coherency first. */ + cpuStat = scu->cpu_stat; + cpuStat &= ~SCU_STAT_MASK(0u); + cpuStat |= SCU_STAT_NORMAL(0u); + scu->cpu_stat = cpuStat; + __dsb(); + + /* Release Core 1 whether PDN already woke it or it is still in WFI. */ + g_powerSleepCore1Release = 1u; + __dsb(); + IRQ_softInterrupt(IRQ_IPI1, BIT(1)); + while(g_powerSleepCore1Resumed == 0u) + __asm__ volatile("nop"); + + const u32 wakeReason = pdn->wake_reason; + if(wakeReason != 0u) + pdn->wake_reason = wakeReason; + pdn->wake_enable = wakeEnableBefore; + + return true; +} + [[noreturn]] void power_off(void) { power_safe_halt(); diff --git a/source/arm11/system.c b/source/arm11/system.c index 9c7b170..d3e904a 100644 --- a/source/arm11/system.c +++ b/source/arm11/system.c @@ -91,6 +91,14 @@ void WEAK __systemInit(void) } } +void __systemWaitCore1Standby(void) +{ + /* Wait until the current Core 1 job has returned to the standby loop. */ + while(getScuRegs()->mn3 != 0u) + __asm__ volatile("nop"); + __dsb(); +} + void __systemBootCore1(void (*entry)(void)) { // Set core 1 entrypoint. diff --git a/source/arm9/drivers/lgy9.c b/source/arm9/drivers/lgy9.c index 8ace58a..db4622b 100644 --- a/source/arm9/drivers/lgy9.c +++ b/source/arm9/drivers/lgy9.c @@ -37,16 +37,27 @@ static char g_savePath[512] = {0}; +#define GBA_IRQ_VECTOR_BIOS (0xEA000042u) // b 0x00000128 from vector 0x18. +#define GBA_IRQ_VECTOR_STOP (0xEAFFFFFBu) // b 0x0000000C from vector 0x18. + static void setupBiosOverlay(bool directBoot) { - copy32((u32*)getLgy9Regs()->a7_vector, (u32*)_gba_vector_overlay, (u32)_gba_vector_overlay_size); - //static const u32 biosVectors[8] = {0xEA000018, 0xEA000004, 0xEA00004C, 0xEA000002, - // 0xEA000001, 0xEA000000, 0xEA000042, 0xE59FD1A0}; - //copy32(getLgy9Regs()->a7_vector, biosVectors, 32); - - copy32((u32*)LGY9_ARM7_STUB_LOC9, (u32*)_gba_boot, (u32)_gba_boot_size); - if(!directBoot) *_gba_boot_swi_a9_addr = 0x26; // Patch swi 0x01 (RegisterRamReset) to swi 0x26 (HardReset). - flushDCacheRange((void*)LGY9_ARM7_STUB_LOC9, (u32)_gba_boot_size); + Lgy9 *const lgy9 = getLgy9Regs(); + copy32((u32*)lgy9->a7_vector, (u32*)_gba_vector_overlay, (u32)_gba_vector_overlay_size); + + if(directBoot) + { + /* + * Reuse the existing ARM7 direct-boot stub while keeping the vector + * overlay enabled for runtime GBA STOP redirection. The reset vector loads + * the stub address from the otherwise unused FIQ slot. + */ + lgy9->a7_vector[0] = 0xE59FF014u; // ldr pc, [pc, #0x14] -> slot 0x1C. + lgy9->a7_vector[7] = LGY9_ARM7_STUB_LOC; + + copy32((u32*)LGY9_ARM7_STUB_LOC9, (u32*)_gba_boot, (u32)_gba_boot_size); + flushDCacheRange((void*)LGY9_ARM7_STUB_LOC9, (u32)_gba_boot_size); + } } static u32 setupSaveType(const u32 saveType) @@ -105,6 +116,12 @@ Result LGY_prepareGbaMode(bool directBoot, const u32 saveType, const char *const return res; } +Result LGY_setGbaStopVector(const bool enabled) +{ + getLgy9Regs()->a7_vector[6] = (enabled ? GBA_IRQ_VECTOR_STOP : GBA_IRQ_VECTOR_BIOS); + return RES_OK; +} + Result LGY_setGbaRtc(const GbaRtc rtc) { // Set base time and date. diff --git a/source/arm9/ipc_handler.c b/source/arm9/ipc_handler.c index c06e14d..c742c5c 100644 --- a/source/arm9/ipc_handler.c +++ b/source/arm9/ipc_handler.c @@ -121,6 +121,9 @@ u32 IPC_handleCmd(u8 cmdId, u32 sendBufs, u32 recvBufs, const u32 *const buf) case IPC_CMD_ID_MASK(IPC_CMD9_BACKUP_GBA_SAVE): result = LGY_backupGbaSave(); break; + case IPC_CMD_ID_MASK(IPC_CMD9_SET_GBA_STOP_VECTOR): + result = LGY_setGbaStopVector(buf[0] != 0u); + break; #endif // Miscellaneous API. diff --git a/source/arm9/lgy7_code.s b/source/arm9/lgy7_code.s index 8027367..9502060 100644 --- a/source/arm9/lgy7_code.s +++ b/source/arm9/lgy7_code.s @@ -26,12 +26,22 @@ BEGIN_ASM_FUNC _gba_vector_overlay - mov r0, #1 - mov r1, #0x4000000 - strb r0, [r1, #0x300] @ "POSTFLG" - ldr pc, =0x3007E00 + /* + * Persistent GBA exception-vector overlay. + * + * The abort/reserved slots at 0x0C-0x14 host a minimal STOP trampoline. + * The IRQ vector at 0x18 normally continues through the GBA BIOS and is + * redirected to 0x0C only for a host-controlled GBA STOP transition. + */ + .word 0xEA000018 @ 0x00 Reset -> BIOS reset path. + .word 0xEA000004 @ 0x04 Undefined instruction. + .word 0xEA00004C @ 0x08 SWI/SVC -> BIOS SVC handler. + .word 0xE92D1004 @ 0x0C stmdb sp!, {r2, r12}. + .word 0xEF030000 @ 0x10 SWI 03h / STOP. + .word 0xE8BD1004 @ 0x14 ldmia sp!, {r2, r12}. + .word 0xEA000042 @ 0x18 IRQ -> BIOS IRQ handler at 0x128. + .word 0xE59FD1A0 @ 0x1C FIQ. -.pool .global _gba_vector_overlay_size _gba_vector_overlay_size = . - _gba_vector_overlay END_ASM_FUNC @@ -43,13 +53,15 @@ BEGIN_ASM_FUNC _gba_boot add sp, r1, #0x6B @ 0x3007E88 msr CPSR_fsxc, #PSR_INT_OFF | PSR_SYS_MODE add sp, r1, #0x5B @ 0x3007E78 - mov r3, #0x4700000 + mov r3, #0x4000000 bx r1 .thumb _gba_boot_thumb: movs r0, #1 - str r0, [r3] @ Disable BIOS overlay. + movs r1, #3 + lsls r1, r1, #8 + strb r0, [r3, r1] @ POSTFLG = 1. Keep the vector overlay enabled. @ The original ARM7 stub waits 256 cycles here (for the BIOS overlay disable?). @ The original ARM7 stub waits 1677800 cycles (100 ms) here for LCD/LgyFb sync. @ The original ARM7 stub waits for REG_VCOUNT = 160 here. @@ -58,8 +70,6 @@ _gba_boot_thumb: movs r0, #0xFF @ Clear WRAM, iWRAM, palette RAM, VRAM, OAM @ + reset SIO, sound and all other registers. -.global _gba_boot_swi_a9_addr -_gba_boot_swi_a9_addr = . - _gba_boot + 0x80BFE00 @ location in ARM9 address space. swi 0x01 @ RegisterRamReset @ After BIOS intro REG_TM0CNT_L is set to 0xFF8C instead of 0. @ No other differences between direct boot and BIOS.