mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-24 03:11:29 +11:00
Merge pull request #69 from Ruin0x11/add_irq_handling
Add interrupt request handling mechanism
This commit is contained in:
commit
723711695e
56
crt0.s
56
crt0.s
|
@ -6,6 +6,11 @@ __start:
|
||||||
.fill 188, 1, 0
|
.fill 188, 1, 0
|
||||||
|
|
||||||
.Linit:
|
.Linit:
|
||||||
|
@ Set address of user IRQ handler
|
||||||
|
ldr r0, =MainIrqHandler
|
||||||
|
ldr r1, =0x03FFFFFC
|
||||||
|
str r0, [r1]
|
||||||
|
|
||||||
@ set IRQ stack pointer
|
@ set IRQ stack pointer
|
||||||
mov r0, #0x12
|
mov r0, #0x12
|
||||||
msr CPSR_cf, r0
|
msr CPSR_cf, r0
|
||||||
|
@ -31,4 +36,55 @@ __start:
|
||||||
@ jump to user code
|
@ jump to user code
|
||||||
ldr r0, =main
|
ldr r0, =main
|
||||||
bx r0
|
bx r0
|
||||||
|
|
||||||
|
.arm
|
||||||
|
.global MainIrqHandler
|
||||||
|
.align 4, 0
|
||||||
|
MainIrqHandler:
|
||||||
|
@ Load base I/O register address
|
||||||
|
mov r2, #0x04000000
|
||||||
|
add r2, r2, #0x200
|
||||||
|
|
||||||
|
@ Save IRQ stack pointer and IME
|
||||||
|
mrs r0, spsr
|
||||||
|
ldrh r1, [r2, #8]
|
||||||
|
stmdb sp!, {r0-r2,lr}
|
||||||
|
|
||||||
|
@ Disable all interrupts by writing to IME
|
||||||
|
mov r0, #0
|
||||||
|
strh r0, [r2, #8]
|
||||||
|
|
||||||
|
@ Acknowledge all received interrupts that were enabled in IE
|
||||||
|
ldr r3, [r2, #0]
|
||||||
|
and r0, r3, r3, lsr #16
|
||||||
|
strh r0, [r2, #2]
|
||||||
|
|
||||||
|
@ Switch to system mode
|
||||||
|
mrs r2, cpsr
|
||||||
|
bic r2, r2, #0x1F
|
||||||
|
orr r2, r2, #0x1F
|
||||||
|
msr cpsr_cf, r2
|
||||||
|
|
||||||
|
@ Jump to user specified IRQ handler
|
||||||
|
ldr r2, =__IRQ_HANDLER
|
||||||
|
ldr r1, [r2]
|
||||||
|
stmdb sp!, {lr}
|
||||||
|
adr lr, MainIrqHandler_Return
|
||||||
|
bx r1
|
||||||
|
MainIrqHandler_Return:
|
||||||
|
ldmia sp!, {lr}
|
||||||
|
|
||||||
|
@ Switch to IRQ mode
|
||||||
|
mrs r2, cpsr
|
||||||
|
bic r2, r2, #0x1F
|
||||||
|
orr r2, r2, #0x92
|
||||||
|
msr cpsr_cf, r2
|
||||||
|
|
||||||
|
@ Restore IRQ stack pointer and IME
|
||||||
|
ldmia sp!, {r0-r2,lr}
|
||||||
|
strh r1, [r2, #8]
|
||||||
|
msr spsr_cf, r0
|
||||||
|
|
||||||
|
@ Return to BIOS IRQ handler
|
||||||
|
bx lr
|
||||||
.pool
|
.pool
|
||||||
|
|
|
@ -16,3 +16,8 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
static __IRQ_HANDLER: extern "C" fn() = irq_handler;
|
||||||
|
|
||||||
|
extern "C" fn irq_handler() {}
|
||||||
|
|
146
examples/irq.rs
Normal file
146
examples/irq.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
|
use gba::{
|
||||||
|
io::{
|
||||||
|
display::{DisplayControlSetting, DisplayMode, DisplayStatusSetting, DISPCNT, DISPSTAT},
|
||||||
|
irq::{self, IrqEnableSetting, IrqFlags, BIOS_IF, IE, IME},
|
||||||
|
keypad::read_key_input,
|
||||||
|
timers::{TimerControlSetting, TimerTickRate, TM0CNT_H, TM0CNT_L, TM1CNT_H, TM1CNT_L},
|
||||||
|
},
|
||||||
|
vram::bitmap::Mode3,
|
||||||
|
Color,
|
||||||
|
};
|
||||||
|
|
||||||
|
const BLACK: Color = Color::from_rgb(0, 0, 0);
|
||||||
|
const RED: Color = Color::from_rgb(31, 0, 0);
|
||||||
|
const GREEN: Color = Color::from_rgb(0, 31, 0);
|
||||||
|
const BLUE: Color = Color::from_rgb(0, 0, 31);
|
||||||
|
const YELLOW: Color = Color::from_rgb(31, 31, 0);
|
||||||
|
const PINK: Color = Color::from_rgb(31, 0, 31);
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_timers() {
|
||||||
|
let init_val: u16 = u32::wrapping_sub(0x1_0000, 64) as u16;
|
||||||
|
const TIMER_SETTINGS: TimerControlSetting = TimerControlSetting::new().with_overflow_irq(true).with_enabled(true);
|
||||||
|
|
||||||
|
TM0CNT_L.write(init_val);
|
||||||
|
TM0CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU1024));
|
||||||
|
TM1CNT_L.write(init_val);
|
||||||
|
TM1CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU64));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[start]
|
||||||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
DISPCNT.write(DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true));
|
||||||
|
Mode3::clear_to(BLACK);
|
||||||
|
|
||||||
|
// Set the IRQ handler to use.
|
||||||
|
irq::set_irq_handler(irq_handler);
|
||||||
|
|
||||||
|
// Enable all interrupts that are set in the IE register.
|
||||||
|
IME.write(IrqEnableSetting::UseIE);
|
||||||
|
|
||||||
|
// Request that VBlank, HBlank and VCount will generate IRQs.
|
||||||
|
const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new()
|
||||||
|
.with_vblank_irq_enable(true)
|
||||||
|
.with_hblank_irq_enable(true)
|
||||||
|
.with_vcounter_irq_enable(true);
|
||||||
|
DISPSTAT.write(DISPLAY_SETTINGS);
|
||||||
|
|
||||||
|
// Start two timers with overflow IRQ generation.
|
||||||
|
start_timers();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let this_frame_keys = read_key_input();
|
||||||
|
|
||||||
|
// The VBlank IRQ must be enabled at minimum, or else the CPU will halt
|
||||||
|
// at the call to vblank_interrupt_wait() as the VBlank IRQ will never
|
||||||
|
// be triggered.
|
||||||
|
let mut flags = IrqFlags::new().with_vblank(true);
|
||||||
|
|
||||||
|
// Enable interrupts based on key input.
|
||||||
|
if this_frame_keys.a() {
|
||||||
|
flags = flags.with_hblank(true);
|
||||||
|
}
|
||||||
|
if this_frame_keys.b() {
|
||||||
|
flags = flags.with_vcounter(true);
|
||||||
|
}
|
||||||
|
if this_frame_keys.l() {
|
||||||
|
flags = flags.with_timer0(true);
|
||||||
|
}
|
||||||
|
if this_frame_keys.r() {
|
||||||
|
flags = flags.with_timer1(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
IE.write(flags);
|
||||||
|
|
||||||
|
// Puts the CPU into low power mode until a VBlank IRQ is received. This
|
||||||
|
// will yield considerably better power efficiency as opposed to spin
|
||||||
|
// waiting.
|
||||||
|
gba::bios::vblank_interrupt_wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut PIXEL: usize = 0;
|
||||||
|
|
||||||
|
fn write_pixel(color: Color) {
|
||||||
|
unsafe {
|
||||||
|
Mode3::write_pixel(PIXEL, 0, color);
|
||||||
|
PIXEL = (PIXEL + 1) % Mode3::SCREEN_PIXEL_COUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn irq_handler(flags: IrqFlags) {
|
||||||
|
if flags.vblank() {
|
||||||
|
vblank_handler();
|
||||||
|
}
|
||||||
|
if flags.hblank() {
|
||||||
|
hblank_handler();
|
||||||
|
}
|
||||||
|
if flags.vcounter() {
|
||||||
|
vcounter_handler();
|
||||||
|
}
|
||||||
|
if flags.timer0() {
|
||||||
|
timer0_handler();
|
||||||
|
}
|
||||||
|
if flags.timer1() {
|
||||||
|
timer1_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vblank_handler() {
|
||||||
|
write_pixel(BLUE);
|
||||||
|
|
||||||
|
// When using `interrupt_wait()` or `vblank_interrupt_wait()`, IRQ handlers must acknowledge
|
||||||
|
// the IRQ on the BIOS Interrupt Flags register.
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_vblank(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hblank_handler() {
|
||||||
|
write_pixel(GREEN);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_hblank(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vcounter_handler() {
|
||||||
|
write_pixel(RED);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_vcounter(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timer0_handler() {
|
||||||
|
write_pixel(YELLOW);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_timer0(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timer1_handler() {
|
||||||
|
write_pixel(PINK);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_timer1(true));
|
||||||
|
}
|
19
src/bios.rs
19
src/bios.rs
|
@ -11,6 +11,7 @@
|
||||||
#![cfg_attr(not(all(target_vendor = "nintendo", target_env = "agb")), allow(unused_variables))]
|
#![cfg_attr(not(all(target_vendor = "nintendo", target_env = "agb")), allow(unused_variables))]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use io::irq::IrqFlags;
|
||||||
|
|
||||||
//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The
|
//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The
|
||||||
//functions that never return must panic, the functions that return nothing
|
//functions that never return must panic, the functions that return nothing
|
||||||
|
@ -184,16 +185,16 @@ pub fn stop() {
|
||||||
/// * The first argument controls if you want to ignore all current flags and
|
/// * The first argument controls if you want to ignore all current flags and
|
||||||
/// wait until a new flag is set.
|
/// wait until a new flag is set.
|
||||||
/// * The second argument is what flags you're waiting on (same format as the
|
/// * The second argument is what flags you're waiting on (same format as the
|
||||||
/// IE/IF registers).
|
/// [`IE`](io::irq::IE)/[`IF`](io::irq::IF) registers).
|
||||||
///
|
///
|
||||||
/// If you're trying to handle more than one interrupt at once this has less
|
/// If you're trying to handle more than one interrupt at once this has less
|
||||||
/// overhead than calling `halt` over and over.
|
/// overhead than calling `halt` over and over.
|
||||||
///
|
///
|
||||||
/// When using this routing your interrupt handler MUST update the BIOS
|
/// When using this routing your interrupt handler MUST update the BIOS
|
||||||
/// Interrupt Flags `0x300_7FF8` in addition to the usual interrupt
|
/// Interrupt Flags at [`BIOS_IF`](io::irq::BIOS_IF) in addition to
|
||||||
/// acknowledgement.
|
/// the usual interrupt acknowledgement.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
|
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: IrqFlags) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -203,19 +204,19 @@ pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x04"
|
asm!(/* ASM */ "swi 0x04"
|
||||||
:/* OUT */ // none
|
:/* OUT */ // none
|
||||||
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags)
|
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags.0)
|
||||||
:/* CLO */ // none
|
:/* CLO */ // none
|
||||||
:/* OPT */ "volatile"
|
:/* OPT */ "volatile"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO(lokathor): newtype this flag business.
|
|
||||||
|
|
||||||
/// (`swi 0x05`) "VBlankIntrWait", VBlank Interrupt Wait.
|
/// (`swi 0x05`) "VBlankIntrWait", VBlank Interrupt Wait.
|
||||||
///
|
///
|
||||||
/// This is as per `interrupt_wait(true, 1)` (aka "wait for a new vblank"). You
|
/// This is as per `interrupt_wait(true, IrqFlags::new().with_vblank(true))`
|
||||||
/// must follow the same guidelines that `interrupt_wait` outlines.
|
/// (aka "wait for a new vblank"). You must follow the same guidelines that
|
||||||
|
/// [`interrupt_wait`](interrupt_wait) outlines.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn vblank_interrupt_wait() {
|
pub fn vblank_interrupt_wait() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
||||||
|
@ -225,7 +226,7 @@ pub fn vblank_interrupt_wait() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x04"
|
asm!(/* ASM */ "swi 0x05"
|
||||||
:/* OUT */ // none
|
:/* OUT */ // none
|
||||||
:/* INP */ // none
|
:/* INP */ // none
|
||||||
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod background;
|
||||||
pub mod color_blend;
|
pub mod color_blend;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod dma;
|
pub mod dma;
|
||||||
|
pub mod irq;
|
||||||
pub mod keypad;
|
pub mod keypad;
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
pub mod timers;
|
pub mod timers;
|
||||||
|
|
|
@ -133,14 +133,28 @@ pub fn vcount() -> u16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a busy loop until VBlank starts.
|
/// Performs a busy loop until VBlank starts.
|
||||||
|
///
|
||||||
|
/// NOTE: This method isn't very power efficient, since it is equivalent to
|
||||||
|
/// calling "halt" repeatedly. The recommended way to wait for a VBlank or VDraw
|
||||||
|
/// is to set an IRQ handler with
|
||||||
|
/// [`io::irq::set_irq_handler`](`io::irq::set_irq_handler`) and using
|
||||||
|
/// [`bios::vblank_intr_wait`](bios::vblank_interrupt_wait) to sleep the CPU
|
||||||
|
/// until a VBlank IRQ is generated. See the [`io::irq`](io::irq) module for
|
||||||
|
/// more details.
|
||||||
pub fn spin_until_vblank() {
|
pub fn spin_until_vblank() {
|
||||||
// TODO: make this the better version with BIOS and interrupts and such.
|
|
||||||
while vcount() < VBLANK_SCANLINE {}
|
while vcount() < VBLANK_SCANLINE {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a busy loop until VDraw starts.
|
/// Performs a busy loop until VDraw starts.
|
||||||
|
///
|
||||||
|
/// NOTE: This method isn't very power efficient, since it is equivalent to
|
||||||
|
/// calling "halt" repeatedly. The recommended way to wait for a VBlank or VDraw
|
||||||
|
/// is to set an IRQ handler with
|
||||||
|
/// [`io::irq::set_irq_handler`](`io::irq::set_irq_handler`) and using
|
||||||
|
/// [`bios::vblank_intr_wait`](bios::vblank_interrupt_wait) to sleep the CPU
|
||||||
|
/// until a VBlank IRQ is generated. See the [`io::irq`](io::irq) module for
|
||||||
|
/// more details.
|
||||||
pub fn spin_until_vdraw() {
|
pub fn spin_until_vdraw() {
|
||||||
// TODO: make this the better version with BIOS and interrupts and such.
|
|
||||||
while vcount() >= VBLANK_SCANLINE {}
|
while vcount() >= VBLANK_SCANLINE {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
180
src/io/irq.rs
Normal file
180
src/io/irq.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
//! Module containing a wrapper for interrupt request (IRQ) handling.
|
||||||
|
//!
|
||||||
|
//! When an interrupt is executed, the CPU will be set to IRQ mode and code
|
||||||
|
//! execution will jump to the physical interrupt vector, located in BIOS. The
|
||||||
|
//! BIOS interrupt handler will then save several registers to the IRQ stack
|
||||||
|
//! pointer and execution will jump to the user interrupt handler starting at
|
||||||
|
//! `0x0300_7FFC`, in ARM mode.
|
||||||
|
//!
|
||||||
|
//! Currently, the user interrupt handler is defined in `crt0.s`. It is set up
|
||||||
|
//! to execute a user-specified interrupt handler after saving some registers.
|
||||||
|
//! This handler is declared as a static function pointer on the Rust side, and
|
||||||
|
//! can be set by using [`set_irq_handler`](irq::set_irq_handler).
|
||||||
|
//!
|
||||||
|
//! ## Notes
|
||||||
|
//! * The interrupt will only be triggered if [`IME`](irq::IME) is enabled, the
|
||||||
|
//! flag corresponding to the interrupt is enabled on the [`IE`](irq::IE)
|
||||||
|
//! register, and the "IRQ Enable" flag is set on the register related to the
|
||||||
|
//! interrupt, which varies. For example, to enable interrupts on VBlank you
|
||||||
|
//! would set the
|
||||||
|
//! [`vblank_irq_enable`](io::display::DisplayStatusSetting::vblank_irq_enable)
|
||||||
|
//! flag on the [`DISPSTAT`](io::display::DISPCNT) register.
|
||||||
|
//! * If you intend to use [`interrupt_wait`](bios::interrupt_wait) or
|
||||||
|
//! [`vblank_interrupt_wait`](bios::vblank_interrupt_wait) to wait for an
|
||||||
|
//! interrupt, your interrupt handler MUST update the BIOS Interrupt Flags at
|
||||||
|
//! [`BIOS_IF`](irq::BIOS_IF) in addition to the usual interrupt
|
||||||
|
//! acknowledgement (which is handled for you by the user interrupt handler).
|
||||||
|
//! This is done by setting the corresponding IRQ flag on
|
||||||
|
//! [`BIOS_IF`](irq::BIOS_IF) at the end of the interrupt handler.
|
||||||
|
//! * You can change the low-level details of the interrupt handler by editing
|
||||||
|
//! the `MainIrqHandler` routine in `crt0.s`. For example, you could declare
|
||||||
|
//! an external static variable in Rust holding a table of interrupt function
|
||||||
|
//! pointers and jump directly into one of them in assembly, without the need
|
||||||
|
//! to write the branching logic in Rust. However, note that the main
|
||||||
|
//! interrupt handler MUST acknowledge all interrupts received by setting
|
||||||
|
//! their corresponding bits to `1` in the [`IF`](irq::IF) register.
|
||||||
|
//! * If you wait on one or more interrupts, be sure at least one of them is
|
||||||
|
//! able to be triggered or the call to wait will never return.
|
||||||
|
//! * If you wait on multiple interrupts and those interrupts fire too quickly,
|
||||||
|
//! it is possible that the call to wait will never return as interrupts will
|
||||||
|
//! be constantly received before control is returned to the caller. This
|
||||||
|
//! usually only happens when waiting on multiple timer interrupts with very
|
||||||
|
//! fast overflow rates.
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! extern "C" fn irq_handler(flags: IrqFlags) {
|
||||||
|
//! if flags.vblank() {
|
||||||
|
//! // Run drawing logic here.
|
||||||
|
//!
|
||||||
|
//! // Acknowledge the IRQ on the BIOS Interrupt Flags register.
|
||||||
|
//! BIOS_IF.write(BIOS_IF.read().with_vblank(true));
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main_loop() {
|
||||||
|
//! // Set the IRQ handler to use.
|
||||||
|
//! irq::set_irq_handler(irq_handler);
|
||||||
|
//!
|
||||||
|
//! // Handle only the VBlank interrupt.
|
||||||
|
//! const FLAGS: IrqFlags = IrqFlags::new().with_vblank(true);
|
||||||
|
//! IE.write(flags);
|
||||||
|
//!
|
||||||
|
//! // Enable all interrupts that are set in the IE register.
|
||||||
|
//! IME.write(IrqEnableSetting::UseIE);
|
||||||
|
//!
|
||||||
|
//! // Enable IRQ generation during VBlank.
|
||||||
|
//! const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new()
|
||||||
|
//! .with_vblank_irq_enable(true);
|
||||||
|
//! DISPSTAT.write(DISPLAY_SETTINGS);
|
||||||
|
//!
|
||||||
|
//! loop {
|
||||||
|
//! // Sleep the CPU until a VBlank IRQ is generated.
|
||||||
|
//! bios::vblank_interrupt_wait();
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Implementation Details
|
||||||
|
//!
|
||||||
|
//! This is the setup the provided user interrupt handler in `crt0.s` will do
|
||||||
|
//! when an interrupt is received, in order. It is based on the _Recommended
|
||||||
|
//! User Interrupt Handling_ portion of the GBATEK reference.
|
||||||
|
//!
|
||||||
|
//! 1. Save the status of [`IME`](irq::IME).
|
||||||
|
//! 2. Save the IRQ stack pointer and change to system mode to use the user
|
||||||
|
//! stack instead of the IRQ stack (to prevent stack overflow).
|
||||||
|
//! 3. Disable interrupts by setting [`IME`](irq::IME) to 0, so other interrupts
|
||||||
|
//! will not preempt the main interrupt handler.
|
||||||
|
//! 4. Acknowledge all IRQs that occurred and were enabled in the
|
||||||
|
//! [`IE`](irq::IE) register by writing the bits to the [`IF`](irq::IF)
|
||||||
|
//! register.
|
||||||
|
//! 5. Save the user stack pointer, switch to Thumb mode and jump to the
|
||||||
|
//! user-specified interrupt handler. The IRQ flags that were set are passed
|
||||||
|
//! as an argument in `r0`.
|
||||||
|
//! 6. When the handler returns, restore the user stack pointer and switch back
|
||||||
|
//! to IRQ mode.
|
||||||
|
//! 7. Restore the IRQ stack pointer and the status of [`IME`](irq::IME).
|
||||||
|
//! 8. Return to the BIOS interrupt handler.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
newtype!(
|
||||||
|
/// A newtype over all interrupt flags.
|
||||||
|
IrqFlags, pub u16
|
||||||
|
);
|
||||||
|
|
||||||
|
impl IrqFlags {
|
||||||
|
phantom_fields! {
|
||||||
|
self.0: u16,
|
||||||
|
vblank: 0,
|
||||||
|
hblank: 1,
|
||||||
|
vcounter: 2,
|
||||||
|
timer0: 3,
|
||||||
|
timer1: 4,
|
||||||
|
timer2: 5,
|
||||||
|
timer3: 6,
|
||||||
|
serial: 7,
|
||||||
|
dma0: 8,
|
||||||
|
dma1: 9,
|
||||||
|
dma2: 10,
|
||||||
|
dma3: 11,
|
||||||
|
keypad: 12,
|
||||||
|
game_pak: 13,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt Enable Register. Read/Write.
|
||||||
|
///
|
||||||
|
/// After setting up interrupt handlers, set the flags on this register type corresponding to the
|
||||||
|
/// IRQs you want to handle.
|
||||||
|
pub const IE: VolAddress<IrqFlags> = unsafe { VolAddress::new(0x400_0200) };
|
||||||
|
|
||||||
|
/// Interrupt Request Flags / IRQ Acknowledge. Read/Write.
|
||||||
|
///
|
||||||
|
/// The main user interrupt handler will acknowledge the interrupt that was set
|
||||||
|
/// by writing to this register, so there is usually no need to modify it.
|
||||||
|
/// However, if the main interrupt handler in `crt0.s` is changed, then the
|
||||||
|
/// handler must write a `1` bit to all bits that are enabled on this register
|
||||||
|
/// when it is called.
|
||||||
|
pub const IF: VolAddress<IrqFlags> = unsafe { VolAddress::new(0x400_0200) };
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// Setting to control whether interrupts are enabled.
|
||||||
|
IrqEnableSetting = u32,
|
||||||
|
/// Disable all interrupts.
|
||||||
|
DisableAll = 0,
|
||||||
|
/// Enable interrupts according to the flags set in the [`IE`](irq::IE) register.
|
||||||
|
UseIE = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt Master Enable Register. Read/Write.
|
||||||
|
pub const IME: VolAddress<IrqEnableSetting> = unsafe { VolAddress::new(0x400_0208) };
|
||||||
|
|
||||||
|
/// BIOS Interrupt Flags. Read/Write.
|
||||||
|
///
|
||||||
|
/// When using either [`interrupt_wait`](bios::interrupt_wait) or
|
||||||
|
/// [`vblank_interrupt_wait`](bios::vblank_interrupt_wait), the corresponding
|
||||||
|
/// interrupt handler MUST set the flag of the interrupt it has handled on this
|
||||||
|
/// register in addition to the usual interrupt acknowledgement.
|
||||||
|
pub const BIOS_IF: VolAddress<IrqFlags> = unsafe { VolAddress::new(0x0300_7FF8) };
|
||||||
|
|
||||||
|
/// A function pointer for use as an interrupt handler.
|
||||||
|
pub type IrqHandler = extern "C" fn(IrqFlags);
|
||||||
|
|
||||||
|
/// Sets the function to run when an interrupt is executed. The function will
|
||||||
|
/// receive the interrupts that were acknowledged by the main interrupt handler
|
||||||
|
/// as an argument.
|
||||||
|
pub fn set_irq_handler(handler: IrqHandler) {
|
||||||
|
unsafe {
|
||||||
|
__IRQ_HANDLER = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn default_handler(_flags: IrqFlags) {}
|
||||||
|
|
||||||
|
// Inner definition of the interrupt handler. It is referenced in `crt0.s`.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[no_mangle]
|
||||||
|
static mut __IRQ_HANDLER: IrqHandler = default_handler;
|
|
@ -101,8 +101,9 @@ newtype! {
|
||||||
/// of the interrupt firing.
|
/// of the interrupt firing.
|
||||||
///
|
///
|
||||||
/// NOTE: This _only_ configures the operation of when keypad interrupts can
|
/// NOTE: This _only_ configures the operation of when keypad interrupts can
|
||||||
/// fire. You must still set the `IME` to have interrupts at all, and you must
|
/// fire. You must still set the [`IME`](irq::IME) to have interrupts at all,
|
||||||
/// further set `IE` for keypad interrupts to be possible.
|
/// and you must further set [`IE`](irq::IE) for keypad interrupts to be
|
||||||
|
/// possible.
|
||||||
KeyInterruptSetting, u16
|
KeyInterruptSetting, u16
|
||||||
}
|
}
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
|
Loading…
Reference in a new issue