// SPDX-License-Identifier: MIT // // Copyright (c) 2020-2022 Antonio Niño Díaz .section .iwram, "ax", %progbits .code 32 .set MEM_IO_ADDR, 0x04000000 .set OFFSET_IE, 0x200 .set OFFSET_IF, 0x202 .set OFFSET_IME, 0x208 .global IRQ_GlobalInterruptHandler IRQ_GlobalInterruptHandler: // Get the pending interrupts that the user actually cares about. If // something isn't set in IE, ignore it. mov r0, #MEM_IO_ADDR // r0 = MEM_IO_ADDR ldr r1, [r0, #OFFSET_IE] // r1 = REG_IE | (REG_IF << 16) and r1, r1, r1, lsr #16 // r1 = REG_IE & REG_IF // Iterate from BIT(0) to BIT(13) .extern IRQ_VectorTable // Notes on the default priority of interrupts: // // - HBLANK is first because it's very short, so saving a few cycles is // important, specially because it is called every scanline. // - VCOUNT is second because it's similar to HBLANK, but it is triggered // less often. However, it needs higher priority than VBL because they // are both triggered at the same time when VBL starts, and VBL is much // longer. ldr r3, =IRQ_VectorTable + 4 mov r2, #(1 << 1) // HBLANK tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 2) // VCOUNT tst r1, r2 bne interrupt_found sub r3, r3, #8 mov r2, #(1 << 0) // VBLANK tst r1, r2 bne interrupt_found add r3, r3, #12 mov r2, #(1 << 3) // TIMER0 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 4) // TIMER1 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 5) // TIMER2 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 6) // TIMER3 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 7) // SERIAL tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 8) // DMA0 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 9) // DMA1 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 10) // DMA2 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 11) // DMA3 tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 12) // KEYPAD tst r1, r2 bne interrupt_found add r3, r3, #4 mov r2, #(1 << 13) // GAMEPAK tst r1, r2 bne interrupt_found // If no interrupt flag is set, fall to the next section of code. // If no interrupt handlers have to be called, clear all bits in the IF and // BIOS flags register. add r3, r0, #(OFFSET_IF & 0xFF00) orr r3, r3, #(OFFSET_IF & 0xFF) ldrh r1, [r3] strh r1, [r3] ldrh r2, [r0, #-8] // The BIOS register is mirrored at 0x03FFFFF8 orr r2, r2, r1 strh r2, [r0, #-8] bx lr // This point is reached if there is at least one bit set in IF & IE interrupt_found: // r0 = REG_BASE // r2 = IRQ bit of the current vector // r3 = Pointer to vector to jump to // Write bit to IF and the BIOS register to acknowledge this interrupt, but // leave the others alone. add r1, r0, #(OFFSET_IF & 0xFF00) orr r1, r1, #(OFFSET_IF & 0xFF) strh r2, [r1] // The BIOS register (BIOS_INTR_FLAGS) is mirrored at 0x03FFFFF8 ldrh r1, [r0, #-8] orr r1, r1, r2 strh r1, [r0, #-8] // If the interrupt handler is null, exit handler ldr r3, [r3] cmp r3, #0 bxeq lr // If this point is reached, there is a valid interrupt handler // r0 = REG_BASE // r3 = Vector to jump to // Clear IME so that we don't get any nested interrupt during the handler of // the current one. At the same time, the old value is preserved so that it // can be restored after the end of the interrupt handler. Note that it is // safe to access IME in 32-bit accesses. add r2, r0, #(OFFSET_IME & 0xFF00) orr r2, r2, #(OFFSET_IME & 0xFF) mov r1, #0 swp r1, r1, [r2] // Get current spsr mrs r2, spsr // Push old IME, spsr and lr stmfd sp!, {r1-r2, lr} .equ MODE_IRQ, 0x12 .equ MODE_SYSTEM, 0x1F .equ MODE_MASK, 0x1F .equ FLAG_IRQ_DISABLE, 1 << 7 // Set CPU mode to system (like user, but privileged, so we can go back to // mode IRQ later). Re-enable the master IRQ bit in CPSR so that the // interrupt handler can re-enable interrupts by setting IME to 1. mrs r2, cpsr //bic r2, r2, #MODE_MASK // Not needed for MODE_SYSTEM bic r2, r2, #FLAG_IRQ_DISABLE orr r2, r2, #MODE_SYSTEM msr cpsr, r2 // Call interrupt handler push {lr} mov lr, pc bx r3 pop {lr} // Disable interrupts while switching modes mov r0, #MEM_IO_ADDR str r0, [r0, #OFFSET_IME] // Set CPU mode to IRQ. Disable interrupts so that setting IME to 1 // afterwards doesn't let the CPU jump to the interrupt handler. mrs r2, cpsr bic r2, r2, #MODE_MASK orr r2, r2, #(MODE_IRQ | FLAG_IRQ_DISABLE) msr cpsr, r2 // Pop old IME, spsr and lr ldmfd sp!, {r1-r2, lr} // Restore spsr msr spsr, r2 // Restore old IME str r1, [r0, #OFFSET_IME] bx lr .end