Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_io/
irq.rs

1//! Interrupt controller: pending and enabled sources.
2//!
3//! The register addresses and source bit positions live in
4//! [`psx_hw::irq`].
5
6use psx_hw::irq as reg;
7
8/// Pending interrupt sources, one bit per [`psx_hw::irq::source`] position.
9#[doc(alias = "I_STAT")]
10#[inline(always)]
11pub fn pending() -> u32 {
12    // SAFETY: I_STAT (`psx_hw::irq::I_STAT`) is the interrupt controller's aligned 32-bit pending register on
13    // every PS1; reading it has no side effects.
14    unsafe { crate::read_u32(reg::I_STAT) }
15}
16
17/// Enabled interrupt sources (the mask register).
18#[doc(alias = "I_MASK")]
19#[inline(always)]
20pub fn mask() -> u32 {
21    // SAFETY: I_MASK (`psx_hw::irq::I_MASK`) is the interrupt controller's aligned 32-bit mask register on
22    // every PS1; reading it has no side effects.
23    unsafe { crate::read_u32(reg::I_MASK) }
24}
25
26/// Acknowledge pending bits by writing `!(bits)` -- the hardware
27/// AND-accumulates, so any bit left 1 in the written value is
28/// preserved, any bit that was 0 is cleared.
29#[inline(always)]
30pub fn acknowledge(bits: u32) {
31    // SAFETY: an aligned 32-bit write to I_STAT (`psx_hw::irq::I_STAT`). The hardware ANDs the value in, so
32    // it can only clear pending bits.
33    unsafe { crate::write_u32(reg::I_STAT, !bits) }
34}
35
36/// Set the mask register (who can interrupt the CPU).
37#[inline(always)]
38pub fn set_mask(bits: u32) {
39    // SAFETY: an aligned 32-bit write to I_MASK (`psx_hw::irq::I_MASK`). It only selects which sources raise
40    // the CPU interrupt line and touches no memory.
41    unsafe { crate::write_u32(reg::I_MASK, bits) }
42}
43
44/// Clear the CPU's interrupt-enable bit (COP0 `SR.IEc`) and report whether it
45/// was set, so [`restore_cpu_interrupts`] can put it back. Sections nest.
46///
47/// This gates every interrupt at the CPU, whatever [`mask`] says. Use
48/// [`without_interrupts`] unless a guard cannot be a closure.
49///
50/// # Safety
51///
52/// Pair every call with one [`restore_cpu_interrupts`] of its result, in
53/// reverse order. Interrupts enabled early would let a handler run inside a
54/// section whose code assumes it cannot.
55#[inline(always)]
56pub unsafe fn disable_cpu_interrupts() -> bool {
57    #[cfg(target_arch = "mips")]
58    {
59        let sr: u32;
60        // Read SR, clear IEc, write it back. The nop after MFC0 covers its
61        // load delay. The two nops after MTC0 let the write settle before the
62        // section's first instruction. An interrupt taken between the read and
63        // the write returns through RFE with IEc as it was, and psx-rt's
64        // handler leaves the rest of SR alone, so the write-back loses nothing.
65        // SAFETY: COP0 SR read-modify-write of the interrupt-enable bit only.
66        unsafe {
67            core::arch::asm!(
68                "mfc0 $8, $12",
69                "nop",
70                // Clear bit 0 (IEc) with a shift pair: no mask register.
71                "srl $9, $8, 1",
72                "sll $9, $9, 1",
73                "mtc0 $9, $12",
74                "nop",
75                "nop",
76                out("$8") sr,
77                out("$9") _,
78                options(nostack),
79            );
80        }
81        sr & 1 != 0
82    }
83    // The host has no interrupts to mask.
84    #[cfg(not(target_arch = "mips"))]
85    false
86}
87
88/// Put back the interrupt-enable bit [`disable_cpu_interrupts`] found.
89///
90/// # Safety
91///
92/// `was_enabled` must be the result of the matching
93/// [`disable_cpu_interrupts`], and every section entered after it must
94/// already have ended.
95#[inline(always)]
96pub unsafe fn restore_cpu_interrupts(was_enabled: bool) {
97    #[cfg(target_arch = "mips")]
98    if was_enabled {
99        // SAFETY: re-enables interrupts only when the matching disable found
100        // them enabled.
101        unsafe {
102            core::arch::asm!(
103                "mfc0 $8, $12",
104                "nop",
105                "ori $8, $8, 1",
106                "mtc0 $8, $12",
107                out("$8") _,
108                options(nostack),
109            );
110        }
111    }
112    #[cfg(not(target_arch = "mips"))]
113    let _ = was_enabled;
114}
115
116/// Run `f` with CPU interrupts masked, then restore the previous state.
117///
118/// For a read-modify-write of a register an interrupt handler also writes
119/// (the DMA enable register, `DPCR`). Keep `f` short: VBlank waits until it
120/// returns.
121#[inline(always)]
122pub fn without_interrupts<R>(f: impl FnOnce() -> R) -> R {
123    // SAFETY: restored below, after `f` returns. A panic in `f` halts the
124    // console, so the section is never left half-open with code running.
125    let was_enabled = unsafe { disable_cpu_interrupts() };
126    let result = f();
127    // SAFETY: pairs the disable above; nested sections inside `f` have ended.
128    unsafe { restore_cpu_interrupts(was_enabled) };
129    result
130}
131
132/// Renamed to [`acknowledge`].
133#[deprecated(note = "renamed to `acknowledge`")]
134#[inline(always)]
135pub fn ack(bits: u32) {
136    acknowledge(bits)
137}
138
139/// Moved to [`psx_hw::irq::source`].
140pub mod source {
141    use psx_hw::irq::source as bit;
142
143    /// Moved to [`psx_hw::irq::source::CDROM`].
144    #[deprecated(note = "moved to `psx_hw::irq::source::CDROM`")]
145    pub const CDROM: u32 = bit::CDROM;
146    /// Moved to [`psx_hw::irq::source::DMA`].
147    #[deprecated(note = "moved to `psx_hw::irq::source::DMA`")]
148    pub const DMA: u32 = bit::DMA;
149    /// Moved to [`psx_hw::irq::source::SPU`].
150    #[deprecated(note = "moved to `psx_hw::irq::source::SPU`")]
151    pub const SPU: u32 = bit::SPU;
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn a_section_runs_its_closure_once_and_returns_its_value() {
160        let mut runs = 0;
161        let value = without_interrupts(|| {
162            runs += 1;
163            without_interrupts(|| 7)
164        });
165        assert_eq!((runs, value), (1, 7));
166    }
167}