From a27f84ad90210df220a9397dff8fc77ba85a8a76 Mon Sep 17 00:00:00 2001 From: Corwin Date: Tue, 13 Feb 2024 22:09:47 +0000 Subject: [PATCH] Makes interrupts interruptable --- agb/src/interrupt.rs | 88 ++++++++++++++++++++++++++++-- agb/src/interrupt_handler.s | 105 +++++++++++++++++++++++++++--------- agb/src/sync/statics.rs | 31 ++++++++++- 3 files changed, 193 insertions(+), 31 deletions(-) diff --git a/agb/src/interrupt.rs b/agb/src/interrupt.rs index c30c4b459..922459587 100644 --- a/agb/src/interrupt.rs +++ b/agb/src/interrupt.rs @@ -140,14 +140,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 { @@ -223,8 +221,7 @@ fn interrupt_to_root(interrupt: Interrupt) -> &'static InterruptRoot { } #[must_use] -/// Adds an interrupt handler as long as the returned value is alive. The -/// closure takes a [`CriticalSection`] which can be used for mutexes. +/// Adds an interrupt handler as long as the returned value is alive. /// /// # Safety /// * You *must not* allocate in an interrupt. @@ -307,6 +304,27 @@ where r } +pub fn interruptable(mut f: F) -> R +where + F: FnOnce() -> R, +{ + let enabled = INTERRUPTS_ENABLED.get(); + + INTERRUPTS_ENABLED.set(1); + + // prevents the contents of the function from being reordered before IME is disabled. + crate::sync::memory_write_hint(&mut f); + + let mut r = f(); + + // prevents the contents of the function from being reordered after IME is re-enabled. + crate::sync::memory_write_hint(&mut r); + + INTERRUPTS_ENABLED.set(enabled); + + r +} + static NUM_VBLANKS: Static = Static::new(0); // overflows after 2.27 years static HAS_CREATED_INTERRUPT: Static = Static::new(false); @@ -383,4 +401,64 @@ mod tests { "interrupt table should be able to store gamepak interrupt" ); } + + #[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: Static = Static::new(0); + + let _interrupt_1 = unsafe { + add_interrupt_handler(timer_a.interrupt(), |_| { + interruptable(|| while TIMER.read() == 0 {}); + + TIMER.write(2); + }) + }; + + let _interrupt_2 = unsafe { + add_interrupt_handler(timer_b.interrupt(), |_| { + TIMER.write(1); + }) + }; + + timer_b.set_enabled(true); + timer_a.set_enabled(true); + + while TIMER.read() != 2 {} + } + + #[test_case] + fn setup_teardown_speed(gba: &mut crate::Gba) { + static TIMER: Static = Static::new(0); + for _ in 0..100 { + TIMER.write(0); + + 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.write(1); + }) + }; + + while TIMER.read() == 0 {} + } + } } diff --git a/agb/src/interrupt_handler.s b/agb/src/interrupt_handler.s index 124be1e72..cf9734bbf 100644 --- a/agb/src/interrupt_handler.s +++ b/agb/src/interrupt_handler.s @@ -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 diff --git a/agb/src/sync/statics.rs b/agb/src/sync/statics.rs index e7828c6cc..2fb7f67ef 100644 --- a/agb/src/sync/statics.rs +++ b/agb/src/sync/statics.rs @@ -277,7 +277,7 @@ mod test { let mut timer = gba.timers.timers().timer2; timer.set_cascade(false); timer.set_divider(Divider::Divider1); - timer.set_overflow_amount(1049); + timer.set_overflow_amount(5049); timer.set_interrupt(true); timer.set_enabled(true); @@ -322,16 +322,43 @@ mod test { } #[test_case] - fn write_read_concurrency_test(gba: &mut Gba) { + fn write_read_concurrency_test_1(gba: &mut Gba) { generate_concurrency_test!(1, gba); + } + #[test_case] + fn write_read_concurrency_test_2(gba: &mut Gba) { generate_concurrency_test!(2, gba); + } + #[test_case] + fn write_read_concurrency_test_3(gba: &mut Gba) { generate_concurrency_test!(3, gba); + } + #[test_case] + fn write_read_concurrency_test_4(gba: &mut Gba) { generate_concurrency_test!(4, gba); + } + #[test_case] + fn write_read_concurrency_test_5(gba: &mut Gba) { generate_concurrency_test!(5, gba); + } + #[test_case] + fn write_read_concurrency_test_6(gba: &mut Gba) { generate_concurrency_test!(6, gba); + } + #[test_case] + fn write_read_concurrency_test_7(gba: &mut Gba) { generate_concurrency_test!(7, gba); + } + #[test_case] + fn write_read_concurrency_test_8(gba: &mut Gba) { generate_concurrency_test!(8, gba); + } + #[test_case] + fn write_read_concurrency_test_9(gba: &mut Gba) { generate_concurrency_test!(9, gba); + } + #[test_case] + fn write_read_concurrency_test_10(gba: &mut Gba) { generate_concurrency_test!(10, gba); } }