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

psx_io/
periph.rs

1//! Zero-size ownership tokens for peripherals with shared state.
2//!
3//! One token exists per device whose state two drivers could otherwise
4//! fight over: the GPU's DMA channel (an in-flight linked-list walk and a
5//! GP0 write interleave), the ordering-table clear, MDEC and SPU channels, the CD-ROM
6//! controller (three drivers program it) and the controller port (pads and memory
7//! cards share it). An API that drives one of them takes the token by `&mut`, so
8//! the borrow checker sees the conflict. The tokens are empty, so passing
9//! them costs nothing at run time.
10//!
11//! Get the whole set once with `psx_rt::Peripherals::take()`. Who owns what:
12//!
13//! | Token | Owner | Others borrow it as |
14//! | --- | --- | --- |
15//! | [`GpuDma`] | `psx_gpu::Gpu` | `Gpu::dma_mut`, `Gpu::from_dma_mut` |
16//! | [`Cd`] | the program, or `cd::reader::SectorReader` / `cd::xa::Player` while one exists | `&mut Cd` methods (`command`, `play_track`, ...), `SectorReader::cd_mut` |
17//! | [`ControllerPort`] | the program | `&mut ControllerPort` into `psx_pad` polls and `psx_mc::HardwareCard` |
18//! | [`SpuDma`] | `psx_spu::Spu` | `Spu::upload_adpcm` |
19//! | [`MdecDma`] | `psx_fmv::mdec::Mdec` | its methods |
20//! | [`OrderingTableClearDma`] | the program | `OrderingTableClearDma::clear_table`, `OrderingTable::clear_with_dma` |
21//!
22//! A token is a logic guard, not a memory-safety one: the SDK's DMA APIs prove
23//! buffer lifetimes through references and slices whether or not a token is
24//! involved. That is why [`GpuDma::steal`] and friends are `unsafe` only in
25//! the "you are breaking an ownership invariant" sense. The deprecated free
26//! functions that predate the tokens steal one per call.
27
28macro_rules! token {
29    ($(#[$doc:meta])* $name:ident) => {
30        $(#[$doc])*
31        #[derive(Debug)]
32        pub struct $name {
33            _private: (),
34        }
35
36        impl $name {
37            /// Make a token without going through `psx_rt::Peripherals::take()`.
38            ///
39            /// # Safety
40            ///
41            /// The caller must not use this token while another one of the
42            /// same type is in use: two owners of one device each assume
43            /// they are the only driver programming it.
44            #[inline(always)]
45            pub const unsafe fn steal() -> Self {
46                Self { _private: () }
47            }
48        }
49    };
50}
51
52token!(
53    /// DMA channel 2 (RAM to GP0, and GPUREAD to RAM) and the GP0 command
54    /// stream it feeds.
55    GpuDma
56);
57token!(
58    /// DMA channel 6, the ordering-table clear.
59    #[doc(alias = "OTC")]
60    OrderingTableClearDma
61);
62token!(
63    /// DMA channels 0 and 1, into and out of the MDEC.
64    MdecDma
65);
66token!(
67    /// DMA channel 4 (RAM to SPU RAM and back) and the SPU transfer port.
68    SpuDma
69);
70token!(
71    /// The CD-ROM controller and DMA channel 3.
72    #[doc(alias = "CDROM")]
73    Cd
74);
75token!(
76    /// The controller and memory-card port.
77    #[doc(alias = "SIO0")]
78    ControllerPort
79);
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn tokens_are_zero_sized() {
87        assert_eq!(core::mem::size_of::<GpuDma>(), 0);
88        assert_eq!(core::mem::size_of::<OrderingTableClearDma>(), 0);
89        assert_eq!(core::mem::size_of::<MdecDma>(), 0);
90        assert_eq!(core::mem::size_of::<SpuDma>(), 0);
91        assert_eq!(core::mem::size_of::<Cd>(), 0);
92        assert_eq!(core::mem::size_of::<ControllerPort>(), 0);
93    }
94}