Skip to content

Commit

Permalink
Makes interrupts interruptable
Browse files Browse the repository at this point in the history
  • Loading branch information
corwinkuiper committed Apr 10, 2024
1 parent 5f37e9f commit bc35b9b
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 29 deletions.
83 changes: 78 additions & 5 deletions agb/src/interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,12 @@ static mut INTERRUPT_TABLE: [InterruptRoot; 14] = [
];

#[no_mangle]
extern "C" fn __RUST_INTERRUPT_HANDLER(interrupt: u16) -> u16 {
extern "C" fn __RUST_INTERRUPT_HANDLER(interrupt: u16) {
for (i, root) in unsafe { INTERRUPT_TABLE.iter().enumerate() } {
if (1 << i) & interrupt != 0 {
root.trigger_interrupts();
}
}

interrupt
}

struct InterruptInner {
Expand Down Expand Up @@ -180,7 +178,7 @@ impl Drop for InterruptInner {
fn inner_drop(this: Pin<&mut InterruptInner>) {
// drop the closure allocation safely
let _closure_box =
unsafe { Box::from_raw(this.closure as *mut dyn Fn(&CriticalSection)) };
unsafe { Box::from_raw(this.closure as *mut dyn Fn(CriticalSection)) };

// perform the rest of the drop sequence
let root = unsafe { &*this.root };
Expand Down Expand Up @@ -299,6 +297,21 @@ unsafe impl critical_section::Impl for MyCriticalSection {
}
}

pub fn interruptable<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let enabled = INTERRUPTS_ENABLED.get();

INTERRUPTS_ENABLED.set(1);

let r = f();

INTERRUPTS_ENABLED.set(enabled);

r
}

static NUM_VBLANKS: AtomicUsize = AtomicUsize::new(0); // overflows after 2.27 years
static HAS_CREATED_INTERRUPT: AtomicBool = AtomicBool::new(false);

Expand Down Expand Up @@ -364,7 +377,7 @@ pub fn profiler(timer: &mut crate::timer::Timer, period: u16) -> InterruptHandle

#[cfg(test)]
mod tests {
use portable_atomic::AtomicU8;
use portable_atomic::{AtomicU32, AtomicU8};

use super::*;

Expand Down Expand Up @@ -393,4 +406,64 @@ mod tests {
assert_eq!(ATOMIC.load(Ordering::SeqCst), i);
}
}

#[test_case]
fn test_nested_interrupts(gba: &mut crate::Gba) {
let mut timers = gba.timers.timers();

let timer_a = &mut timers.timer2;
let timer_b = &mut timers.timer3;

timer_a.set_interrupt(true);
timer_a.set_overflow_amount(10000);

timer_b.set_interrupt(true);
timer_b.set_overflow_amount(15000);

static TIMER: AtomicU32 = AtomicU32::new(0);

let _interrupt_1 = unsafe {
add_interrupt_handler(timer_a.interrupt(), |_| {
interruptable(|| while TIMER.load(Ordering::SeqCst) == 0 {});

TIMER.store(2, Ordering::SeqCst);
})
};

let _interrupt_2 = unsafe {
add_interrupt_handler(timer_b.interrupt(), |_| {
TIMER.store(1, Ordering::SeqCst);
})
};

timer_b.set_enabled(true);
timer_a.set_enabled(true);

while TIMER.load(Ordering::SeqCst) != 2 {}
}

#[test_case]
fn setup_teardown_speed(gba: &mut crate::Gba) {
static TIMER: AtomicU32 = AtomicU32::new(0);
for _ in 0..100 {
TIMER.store(0, Ordering::SeqCst);

let timers = gba.timers.timers();

let mut timer_a = timers.timer2;

timer_a.set_interrupt(true);
timer_a.set_overflow_amount(10000);

timer_a.set_enabled(true);

let _interrupt_1 = unsafe {
add_interrupt_handler(timer_a.interrupt(), |_| {
TIMER.store(1, Ordering::SeqCst);
})
};

while TIMER.load(Ordering::SeqCst) == 0 {}
}
}
}
105 changes: 81 additions & 24 deletions agb/src/interrupt_handler.s
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,103 @@
.section .iwram.interrupt_handler, "ax", %progbits
.align
InterruptHandler:
mov r2, #0x04000000 @ interrupt enable register location
add r2, #0x200

mov r1, #0
strh r1, [r2, #8]
.set IO_MEMORY_MAPPED_REGISTERS, 0x04000000
.set OFFSET_INTERRUPT_ENABLED, 0x200

ldrh r1, [r2] @ load 16 bit interrupt enable to r1
ldrh r3, [r2, #2] @ load 16 bit interrupt request to r3
and r0, r1, r3 @ interrupts both enabled and requested
mov r1, #IO_MEMORY_MAPPED_REGISTERS
ldr r3, [r1, #OFFSET_INTERRUPT_ENABLED]!
and r0, r3, r3, lsr #16 @ interrupts that are enabled AND triggered

ldr r1, [sp, #20]
ldr r3, =agb_rs__program_counter
str r1, [r3]
@ temporarily disable interrupts that were triggered here
bic r2, r3, r0
strh r2, [r1]

@ change to system mode

@ r0: interrupts that are enabled AND triggered
@ r1: #IO_MEMORY_MAPPED_REGISTERS + #OFFSET_INTERRUPT_ENABLED
@ r3: Original contents of enabled interrupts

@ acknowledge interrupts
strh r0, [r1, #2]

.set OFFSET_BIOS_INTERRUPT_ACKNOWLEDGE, -0x8

@ acknowledge bios interrupts
sub r1, r1, #OFFSET_INTERRUPT_ENABLED
ldrh r2, [r1, #OFFSET_BIOS_INTERRUPT_ACKNOWLEDGE]
orr r2, r2, r0
strh r2, [r1, #OFFSET_BIOS_INTERRUPT_ACKNOWLEDGE]

@ r0: interrupts that are enabled AND triggered
@ r1: #IO_MEMORY_MAPPED_REGISTERS
@ r3: Original contents of enabled interrupts

.set OFFSET_INTERRUPT_MASTER_ENABLE, 0x208

@ clear interrupt master enable
add r1, r1, #OFFSET_INTERRUPT_MASTER_ENABLE
mov r2, #0
swp r2, r2, [r1]

@ r0: interrupts that are enabled AND triggered
@ r2: old interrrupt master enable
@ r3: Original contents of enabled interrupts

@ push saved program status, old interrupt master enable, original enabled interrupts, and the link register
mrs r1, spsr
stmfd sp!, {{r1-r3, lr}}

@ r0: interrupts that are enabled AND triggered

.set PSR_MODE_MASK, 0x1F
.set PSR_IRQ_DISABLE_MASK, 0x80
.set PSR_MODE_SYSETM, 0x1F

@ switch to system mode in the current program status register
mrs r1, cpsr
orr r1, r1, #0xD
msr cpsr_c, r1
bic r1, r1, #(PSR_MODE_MASK | PSR_IRQ_DISABLE_MASK)
orr r1, r1, #PSR_MODE_SYSETM
msr cpsr, r1

@ SYSTEM MODE

push {{lr}}

@ r0: interrupts that are enabled AND triggered

@ call the rust interrupt handler with r0 set to the triggered interrupts
ldr r1, =__RUST_INTERRUPT_HANDLER
push {{r2, lr}}
mov lr, pc
bx r1
pop {{r2, lr}}


pop {{lr}}


@ NO MEANING TO ANY REGISTERS


@ Clear the interrupt master enable
mov r0, #IO_MEMORY_MAPPED_REGISTERS
str r0, [r0, #OFFSET_INTERRUPT_MASTER_ENABLE]

.set PSR_MODE_INTERRUPT, 0x12

@ change back to interrupt mode
mrs r1, cpsr
bic r1, r1, #0xD
msr cpsr_c, r1
bic r1, r1, #(PSR_MODE_MASK)
orr r1, r1, #(PSR_MODE_INTERRUPT | PSR_IRQ_DISABLE_MASK)
msr cpsr, r1

mov r1, #1
strh r1, [r2, #8]
@ r0: #IO_MEMORY_MAPPED_REGISTERS

strh r0, [r2, #2] @ store to interrupt request
ldmfd sp!, {{r1-r3, lr}}
msr spsr, r1
str r2, [r0, #OFFSET_INTERRUPT_MASTER_ENABLE]!

ldr r2, =0x03007FF8 @ load bios interrupt request location
ldrh r1, [r2] @ load bios interrupt requests
orr r1, r1, r0 @ or with enabled and requested interrupts
strh r1, [r2] @ acknowlege bios requests
@ r0: #(IO_MEMORY_MAPPED_REGISTERS + OFFSET_INTERRUPT_MASTER_ENABLE)
strh r3, [r0, #(OFFSET_INTERRUPT_ENABLED - OFFSET_INTERRUPT_MASTER_ENABLE)]

bx lr @ return to bios
.pool
Expand Down

0 comments on commit bc35b9b

Please sign in to comment.