Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Makes interrupts interruptable #627

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
96 changes: 64 additions & 32 deletions agb/src/interrupt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use core::{cell::Cell, marker::PhantomPinned, pin::Pin};
use core::{
cell::{Cell, UnsafeCell},
marker::PhantomPinned,
pin::Pin,
};

use alloc::boxed::Box;
use critical_section::{CriticalSection, RawRestoreState};
Expand Down Expand Up @@ -123,7 +127,11 @@ impl InterruptRoot {
}
}

static mut INTERRUPT_TABLE: [InterruptRoot; 14] = [
struct InterruptCell(UnsafeCell<[InterruptRoot; 14]>);

unsafe impl Sync for InterruptCell {}

static INTERRUPT_TABLE: InterruptCell = InterruptCell(UnsafeCell::new([
InterruptRoot::new(Interrupt::VBlank),
InterruptRoot::new(Interrupt::HBlank),
InterruptRoot::new(Interrupt::VCounter),
Expand All @@ -138,17 +146,16 @@ static mut INTERRUPT_TABLE: [InterruptRoot; 14] = [
InterruptRoot::new(Interrupt::Dma3),
InterruptRoot::new(Interrupt::Keypad),
InterruptRoot::new(Interrupt::Gamepak),
];
]));

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

interrupt
}

struct InterruptInner {
Expand Down Expand Up @@ -180,7 +187,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 @@ -220,7 +227,8 @@ impl InterruptRoot {
}

fn interrupt_to_root(interrupt: Interrupt) -> &'static InterruptRoot {
unsafe { &INTERRUPT_TABLE[interrupt as usize] }
let table = unsafe { &*(INTERRUPT_TABLE.0.get()) };
&table[interrupt as usize]
}

#[must_use]
Expand Down Expand Up @@ -299,6 +307,26 @@ unsafe impl critical_section::Impl for MyCriticalSection {
}
}

/// This makes the interrupt itself interruptable. Other interrupts are not
/// guaranteed to fire, but are allowed to.
///
/// # Safety
/// * You must not use a critical section acquired outside this inside it.
pub(crate) unsafe 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 @@ -341,37 +369,16 @@ impl VBlank {
}
}

#[must_use]
/// The behaviour of this function is undefined in the sense that it will output
/// some information in some way that can be interpreted in a way to give some
/// profiling information. What it outputs, how it outputs it, and how to
/// interpret it are all subject to change at any time.
///
/// With that out of the way, the current version will, in mgba, output the
/// program counter at regular intervals. This can be used to see hot functions
/// using, for example, addr2line.
pub fn profiler(timer: &mut crate::timer::Timer, period: u16) -> InterruptHandler {
timer.set_interrupt(true);
timer.set_overflow_amount(period);
timer.set_enabled(true);

unsafe {
add_interrupt_handler(timer.interrupt(), |_key: CriticalSection| {
crate::println!("{:#010x}", crate::program_counter_before_interrupt());
})
}
}

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

use super::*;

#[test_case]
fn test_interrupt_table_length(_gba: &mut crate::Gba) {
assert_eq!(
unsafe { INTERRUPT_TABLE.len() },
unsafe { (*INTERRUPT_TABLE.0.get()).len() },
Interrupt::Gamepak as usize + 1,
"interrupt table should be able to store gamepak interrupt"
);
Expand All @@ -393,4 +400,29 @@ mod tests {
assert_eq!(ATOMIC.load(Ordering::SeqCst), i);
}
}

#[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 {}
}
}
}
107 changes: 81 additions & 26 deletions agb/src/interrupt_handler.s
Original file line number Diff line number Diff line change
@@ -1,50 +1,105 @@

@ An interrupt handler that simply acknowledges all interrupts
.arm
.global InterruptHandler
.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

mov r1, #IO_MEMORY_MAPPED_REGISTERS
ldr r3, [r1, #OFFSET_INTERRUPT_ENABLED]!
and r0, r3, r3, lsr #16 @ interrupts that are enabled AND triggered

@ temporarily disable interrupts that were triggered here
bic r2, r3, r0
strh r2, [r1]


@ 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

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
@ push saved program status, old interrupt master enable, original enabled interrupts, and the link register
mrs r1, spsr
push {{r1-r3, lr}}

ldr r1, [sp, #20]
ldr r3, =agb_rs__program_counter
str r1, [r3]
@ r0: interrupts that are enabled AND triggered

@ change to system mode
.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
pop {{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
8 changes: 0 additions & 8 deletions agb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,14 +423,6 @@ pub mod test_runner {
}
}

#[inline(never)]
pub(crate) fn program_counter_before_interrupt() -> u32 {
extern "C" {
static mut agb_rs__program_counter: u32;
}
unsafe { agb_rs__program_counter }
}

#[cfg(test)]
mod test {
use core::ptr::addr_of_mut;
Expand Down
Loading