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.
This commit is contained in:
David Guillen Fandos 2023-04-10 12:58:03 +02:00
parent f9d7f51ef1
commit 2bbd77054e
3 changed files with 26 additions and 7 deletions

22
cpu.c
View File

@ -1573,14 +1573,11 @@ void set_cpu_mode(cpu_mode_type new_mode)
reg[CPU_MODE] = 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, // Check any IRQ flag pending, IME and CPSR-IRQ enabled
// and it must be on in the flags. u16 umirq = read_ioreg(REG_IE) & io_registers[REG_IF];
write_ioreg(REG_IF, read_ioreg(REG_IF) | irq_raised); if(!(reg[REG_CPSR] & 0x80) && read_ioreg(REG_IME) && umirq)
if((read_ioreg(REG_IE) & irq_raised) && read_ioreg(REG_IME) &&
((reg[REG_CPSR] & 0x80) == 0))
{ {
// Value after the FIQ returns, should be improved // Value after the FIQ returns, should be improved
reg[REG_BUS_VALUE] = 0xe55ec002; reg[REG_BUS_VALUE] = 0xe55ec002;
@ -1594,7 +1591,18 @@ void raise_interrupt(irq_type irq_raised)
set_cpu_mode(MODE_IRQ); set_cpu_mode(MODE_IRQ);
reg[CPU_HALT_STATE] = CPU_ACTIVE; reg[CPU_HALT_STATE] = CPU_ACTIVE;
reg[CHANGED_PC_STATUS] = 1; 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 #ifndef HAVE_DYNAREC

1
cpu.h
View File

@ -110,6 +110,7 @@ typedef enum
extern u32 instruction_count; extern u32 instruction_count;
void execute_arm(u32 cycles); void execute_arm(u32 cycles);
cpu_alert_type check_interrupts(void);
void raise_interrupt(irq_type irq_raised); void raise_interrupt(irq_type irq_raised);
void set_cpu_mode(cpu_mode_type new_mode); void set_cpu_mode(cpu_mode_type new_mode);

View File

@ -1339,6 +1339,11 @@ cpu_alert_type function_cc write_io_register16(u32 address, u32 value)
case 0x130: case 0x130:
break; break;
// REG_IE
case 0x200:
write_ioreg(REG_IE, value);
return check_interrupts();
// Interrupt flag // Interrupt flag
case 0x202: case 0x202:
write_ioreg(REG_IF, read_ioreg(REG_IF) & (~value)); 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); write_ioreg(REG_WAITCNT, value);
break; break;
// REG_IME
case 0x208:
write_ioreg(REG_IME, value);
return check_interrupts();
// Halt // Halt
case 0x300: case 0x300:
if(((value >> 8) & 0x01) == 0) if(((value >> 8) & 0x01) == 0)