mirror of
https://github.com/GearsProgress/Poke_Transporter_GB.git
synced 2026-03-21 17:34:42 -05:00
211 lines
5.5 KiB
ArmAsm
211 lines
5.5 KiB
ArmAsm
// 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
|