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}