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}