Skip to main content

gb/
interrupt.rs

1//! Interrupt control: the `IME` master switch and the `IE` / `IF` masks.
2//!
3//! Two gates stand between the hardware and a handler. `IME` is the CPU's master
4//! switch, flipped by [`enable`] and [`disable`]; `IE` picks which of the five
5//! sources may fire, and [`set_enabled`] writes it. Both must be open. The
6//! runtime enters `main` with `IME` off, so a program sees no interrupt until it
7//! opens them.
8//!
9//! Handlers are not installed here: write one with
10//! [`#[gb::rt::interrupt]`](macro@crate::rt::interrupt), which binds it straight
11//! to a vector.
12//!
13//! Turning interrupts on is `unsafe` throughout, since it introduces preemption
14//! that the surrounding code may have been written to rule out. With the
15//! `critical-section-impl` feature these functions are also the only sanctioned
16//! way to change `IME`, which the implementation mirrors in HRAM.
17
18pub use critical_section::CriticalSection;
19
20use crate::mmio::{IE, IF, Interrupts};
21
22/// Interrupt entry: the CPU clears `IME` when it dispatches.
23#[doc(hidden)]
24#[inline(always)]
25pub fn __isr_enter() {
26    mirror::set(false);
27}
28
29/// Interrupt exit: `reti` sets `IME` whatever the handler did to it.
30#[doc(hidden)]
31#[inline(always)]
32pub fn __isr_exit() {
33    mirror::set(true);
34}
35
36#[cfg(feature = "critical-section-impl")]
37mod mirror {
38    use gb_hram::{hram, prelude::*};
39
40    // The runtime clears HRAM and enters `main` with interrupts off, so the
41    // zero this starts at is already the right answer.
42    hram! {
43        static IME_ON: HramAtomicCell<bool>;
44    }
45
46    #[inline(always)]
47    pub fn set(on: bool) {
48        IME_ON.set(on);
49    }
50
51    struct SingleCore;
52    critical_section::set_impl!(SingleCore);
53
54    unsafe impl critical_section::Impl for SingleCore {
55        unsafe fn acquire() -> critical_section::RawRestoreState {
56            let was_on = IME_ON.get();
57            super::disable();
58            was_on
59        }
60
61        unsafe fn release(was_on: critical_section::RawRestoreState) {
62            if was_on {
63                unsafe { super::enable() };
64            }
65        }
66    }
67}
68
69#[cfg(not(feature = "critical-section-impl"))]
70mod mirror {
71    #[inline(always)]
72    pub fn set(_on: bool) {}
73}
74
75/// Clear IME with `di`: interrupts stop being serviced.
76#[inline]
77pub fn disable() {
78    unsafe { core::arch::asm!("di") }
79    mirror::set(false);
80}
81
82/// Set IME with `ei`, effective after the next instruction.
83///
84/// # Safety
85///
86/// Introduces preemption, breaking code that assumes interrupts stay off.
87#[inline]
88pub unsafe fn enable() {
89    mirror::set(true);
90    unsafe { core::arch::asm!("ei") }
91}
92
93/// Sleep until an interrupt arrives, then service it.
94///
95/// Emits `ei` and `halt` as one pair: `ei` takes effect only after the next
96/// instruction, so an interrupt cannot be serviced in between and leave the `halt`
97/// waiting for the following one.
98///
99/// # Safety
100///
101/// Introduces preemption, breaking code that assumes interrupts stay off.
102#[inline]
103pub unsafe fn enable_and_halt() {
104    mirror::set(true);
105    unsafe { core::arch::asm!("ei", "halt") }
106}
107
108/// Sleep until an interrupt arrives. Never returns if no enabled interrupt can fire.
109///
110/// Emits `halt` followed by a `nop`, which covers the halt bug: with IME clear and
111/// an interrupt already pending, the CPU skips the sleep and reads the byte after
112/// `halt` twice. See <https://gbdev.io/pandocs/halt.html>.
113#[inline]
114pub fn halt() {
115    unsafe { core::arch::asm!("halt", "nop") }
116}
117
118/// Run `f` with interrupts disabled, handing it proof of that.
119///
120/// # Safety
121///
122/// Enables interrupts on the way out however they were set on the way in: SM83
123/// cannot read `IME` back, so there is nothing to restore. Calling this with
124/// interrupts already off ends the enclosing critical section early.
125#[inline]
126pub unsafe fn free<R>(f: impl FnOnce(CriticalSection<'_>) -> R) -> R {
127    disable();
128    let r = f(unsafe { CriticalSection::new() });
129    unsafe { enable() };
130    r
131}
132
133/// Read `IE`: the interrupts that may fire.
134#[inline]
135pub fn enabled() -> Interrupts {
136    IE.read()
137}
138
139/// Read `IF`: the interrupts that have been requested.
140#[inline]
141pub fn pending() -> Interrupts {
142    IF.read()
143}
144
145/// Replace `IE` with `mask`.
146///
147/// # Safety
148///
149/// Introduces preemption, breaking code that assumes interrupts stay off.
150#[inline]
151pub unsafe fn set_enabled(mask: Interrupts) {
152    unsafe { IE.write(mask) }
153}