diff --git a/agb/examples/just_build.rs b/agb/examples/just_build.rs index 2287d816..b19262db 100644 --- a/agb/examples/just_build.rs +++ b/agb/examples/just_build.rs @@ -6,6 +6,9 @@ fn panic_handler(_info: &core::panic::PanicInfo) -> ! { loop {} } +#[no_mangle] +pub extern "C" fn __RUST_INTERRUPT_HANDLER(_: u16) {} + // implementation of tonc's "My first GBA demo" // https://coranac.com/tonc/text/first.htm diff --git a/agb/interrupt_simple.s b/agb/interrupt_simple.s index de64d181..817fd0b1 100644 --- a/agb/interrupt_simple.s +++ b/agb/interrupt_simple.s @@ -13,6 +13,23 @@ InterruptHandlerSimple: ldrh r1, [r2] @ load bios interrupt requests orr r1, r1, r0 @ or with enabled and requested interrupts strh r1, [r2] @ acknowlege bios requests - + + mrs r2, cpsr + orr r2, r2, #0xD + msr cpsr_c, r2 + + + ldr r1, =__RUST_INTERRUPT_HANDLER + push {lr} + adr lr, .IReturn + bx r1 +.IReturn: + pop {lr} + + mrs r2, cpsr + bic r2, r2, #0xD + orr r2, r2, #0x92 + msr cpsr_c, r2 + bx lr @ return to bios .pool diff --git a/agb/src/interrupt.rs b/agb/src/interrupt.rs index 772e8a08..7db37619 100644 --- a/agb/src/interrupt.rs +++ b/agb/src/interrupt.rs @@ -1,3 +1,9 @@ +use core::{ + cell::Cell, + marker::{PhantomData, PhantomPinned}, + pin::Pin, +}; + use crate::memory_mapped::MemoryMapped; #[allow(dead_code)] @@ -55,3 +61,155 @@ pub fn enable_interrupts() { pub(crate) fn disable_interrupts() { INTERRUPTS_ENABLED.set(0); } + +pub struct InterruptRoot { + next: Cell<*const InterruptClosure>, +} + +impl InterruptRoot { + const fn new() -> Self { + InterruptRoot { + next: Cell::new(core::ptr::null()), + } + } +} + +static mut INTERRUPT_TABLE: Interrupts = Interrupts { + vblank: InterruptRoot::new(), + hblank: InterruptRoot::new(), +}; + +#[no_mangle] +pub extern "C" fn __RUST_INTERRUPT_HANDLER(interrupt: u16) { + if interrupt & 1 != 0 { + unsafe { INTERRUPT_TABLE.vblank.trigger_interrupts() }; + }; +} + +struct Interrupts { + vblank: InterruptRoot, + hblank: InterruptRoot, +} + +pub struct InterruptClosureBounded<'a> { + c: InterruptClosure, + _phantom: PhantomData<&'a ()>, + _unpin: PhantomPinned, +} + +pub struct InterruptClosure { + closure: *mut (dyn FnMut()), + next: Cell<*const InterruptClosure>, + root: *const InterruptRoot, +} + +impl InterruptRoot { + fn trigger_interrupts(&self) { + let mut count = 0; + let mut c = self.next.get(); + while !c.is_null() { + count += 1; + let closure_ptr = unsafe { &*c }.closure; + let closure_ref = unsafe { &mut *closure_ptr }; + closure_ref(); + c = unsafe { &*c }.next.get(); + } + } +} + +impl Drop for InterruptClosure { + fn drop(&mut self) { + let mut c = unsafe { &*self.root }.next.get(); + let own_pointer = self as *const _; + if c == own_pointer { + unsafe { &*self.root }.next.set(self.next.get()); + return; + } + loop { + let p = unsafe { &*c }.next.get(); + if p == own_pointer { + unsafe { &*c }.next.set(self.next.get()); + return; + } + c = p; + } + } +} + +fn get_interrupt_handle_root<'a>( + f: &'a mut dyn FnMut(), + root: &InterruptRoot, +) -> InterruptClosureBounded<'a> { + InterruptClosureBounded { + c: InterruptClosure { + closure: unsafe { core::mem::transmute(f as *mut _) }, + next: Cell::new(core::ptr::null()), + root: root as *const _, + }, + _phantom: PhantomData, + _unpin: PhantomPinned, + } +} + +pub fn get_interrupt_handle<'a>( + f: &'a mut dyn FnMut(), + interrupt: Interrupt, +) -> InterruptClosureBounded<'a> { + let root = match interrupt { + Interrupt::VBlank => unsafe { &INTERRUPT_TABLE.vblank }, + _ => unimplemented!( + "sorry, I haven't yet added this interrupt. Please request it if you need it" + ), + }; + + get_interrupt_handle_root(f, root) +} + +pub fn add_interrupt<'a>(interrupt: Pin<&'a InterruptClosureBounded<'a>>) { + let root = unsafe { &*interrupt.c.root }; + let mut c = root.next.get(); + if c.is_null() { + root.next.set((&interrupt.c) as *const _); + return; + } + loop { + let p = unsafe { &*c }.next.get(); + if p.is_null() { + unsafe { &*c }.next.set((&interrupt.c) as *const _); + return; + } + + c = p; + } +} + +#[test_case] +fn test_vblank_interrupt_handler(gba: &mut crate::Gba) { + { + let mut counter = 0; + let mut counter_2 = 0; + + let mut vblank_interrupt = || counter += 1; + let mut vblank_interrupt_2 = || counter_2 += 1; + + let interrupt_closure = get_interrupt_handle(&mut vblank_interrupt, Interrupt::VBlank); + let interrupt_closure = unsafe { Pin::new_unchecked(&interrupt_closure) }; + add_interrupt(interrupt_closure); + + let interrupt_closure_2 = get_interrupt_handle(&mut vblank_interrupt_2, Interrupt::VBlank); + let interrupt_closure_2 = unsafe { Pin::new_unchecked(&interrupt_closure_2) }; + add_interrupt(interrupt_closure_2); + + let vblank = gba.display.vblank.get(); + + while counter < 100 || counter_2 < 100 { + vblank.wait_for_VBlank(); + } + } + + assert_eq!( + unsafe { INTERRUPT_TABLE.vblank.next.get() }, + core::ptr::null(), + "expected the interrupt table for vblank to be empty" + ); +}