From 2bbd77054ee8af4bc9d81af22a7cd72301f90fcb Mon Sep 17 00:00:00 2001 From: David Guillen Fandos Date: Mon, 10 Apr 2023 12:58:03 +0200 Subject: [PATCH] Fix pending interrupts not raised on IME/IE writes Whenever an interrupt is pending and interrupts are disabled (via IME/IE), an IE/IME write that re-enables IRQs will fail to raise an IRQ. This makes some games hang. Most games seem to use CPSR.IRQ to enable and disable interruts, so they are not affected. However some others use IME/IE (or all of them), causing these deadlocks and some race conditions. This fixes a bunch of games that did not crash but would "hang" in some interesting ways. --- cpu.c | 22 +++++++++++++++------- cpu.h | 1 + gba_memory.c | 10 ++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cpu.c b/cpu.c index 2cef16b..a051d60 100644 --- a/cpu.c +++ b/cpu.c @@ -1573,14 +1573,11 @@ void set_cpu_mode(cpu_mode_type new_mode) reg[CPU_MODE] = new_mode; } -void raise_interrupt(irq_type irq_raised) +cpu_alert_type check_interrupts() { - // The specific IRQ must be enabled in IE, master IRQ enable must be on, - // and it must be on in the flags. - write_ioreg(REG_IF, read_ioreg(REG_IF) | irq_raised); - - if((read_ioreg(REG_IE) & irq_raised) && read_ioreg(REG_IME) && - ((reg[REG_CPSR] & 0x80) == 0)) + // Check any IRQ flag pending, IME and CPSR-IRQ enabled + u16 umirq = read_ioreg(REG_IE) & io_registers[REG_IF]; + if(!(reg[REG_CPSR] & 0x80) && read_ioreg(REG_IME) && umirq) { // Value after the FIQ returns, should be improved reg[REG_BUS_VALUE] = 0xe55ec002; @@ -1594,7 +1591,18 @@ void raise_interrupt(irq_type irq_raised) set_cpu_mode(MODE_IRQ); reg[CPU_HALT_STATE] = CPU_ACTIVE; reg[CHANGED_PC_STATUS] = 1; + return CPU_ALERT_IRQ; } + return CPU_ALERT_NONE; +} + +void raise_interrupt(irq_type irq_raised) +{ + // The specific IRQ must be enabled in IE, master IRQ enable must be on, + // and it must be on in the flags. + write_ioreg(REG_IF, read_ioreg(REG_IF) | irq_raised); + + check_interrupts(); } #ifndef HAVE_DYNAREC diff --git a/cpu.h b/cpu.h index 00aeddf..db2f2d2 100644 --- a/cpu.h +++ b/cpu.h @@ -110,6 +110,7 @@ typedef enum extern u32 instruction_count; void execute_arm(u32 cycles); +cpu_alert_type check_interrupts(void); void raise_interrupt(irq_type irq_raised); void set_cpu_mode(cpu_mode_type new_mode); diff --git a/gba_memory.c b/gba_memory.c index a51eebe..99ca684 100644 --- a/gba_memory.c +++ b/gba_memory.c @@ -1339,6 +1339,11 @@ cpu_alert_type function_cc write_io_register16(u32 address, u32 value) case 0x130: break; + // REG_IE + case 0x200: + write_ioreg(REG_IE, value); + return check_interrupts(); + // Interrupt flag case 0x202: write_ioreg(REG_IF, read_ioreg(REG_IF) & (~value)); @@ -1349,6 +1354,11 @@ cpu_alert_type function_cc write_io_register16(u32 address, u32 value) write_ioreg(REG_WAITCNT, value); break; + // REG_IME + case 0x208: + write_ioreg(REG_IME, value); + return check_interrupts(); + // Halt case 0x300: if(((value >> 8) & 0x01) == 0)