example first version of an interrupt handler implemented with closures

This commit is contained in:
Corwin Kuiper 2021-06-23 18:47:03 +01:00
parent 8cd5055e80
commit 2cc0bee7c3
3 changed files with 179 additions and 1 deletions

View file

@ -6,6 +6,9 @@ fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
loop {} loop {}
} }
#[no_mangle]
pub extern "C" fn __RUST_INTERRUPT_HANDLER(_: u16) {}
// implementation of tonc's "My first GBA demo" // implementation of tonc's "My first GBA demo"
// https://coranac.com/tonc/text/first.htm // https://coranac.com/tonc/text/first.htm

View file

@ -13,6 +13,23 @@ InterruptHandlerSimple:
ldrh r1, [r2] @ load bios interrupt requests ldrh r1, [r2] @ load bios interrupt requests
orr r1, r1, r0 @ or with enabled and requested interrupts orr r1, r1, r0 @ or with enabled and requested interrupts
strh r1, [r2] @ acknowlege bios requests 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 bx lr @ return to bios
.pool .pool

View file

@ -1,3 +1,9 @@
use core::{
cell::Cell,
marker::{PhantomData, PhantomPinned},
pin::Pin,
};
use crate::memory_mapped::MemoryMapped; use crate::memory_mapped::MemoryMapped;
#[allow(dead_code)] #[allow(dead_code)]
@ -55,3 +61,155 @@ pub fn enable_interrupts() {
pub(crate) fn disable_interrupts() { pub(crate) fn disable_interrupts() {
INTERRUPTS_ENABLED.set(0); 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"
);
}