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}