psx_io/dma.rs
1//! DMA controller.
2//!
3//! The PS1 has 7 DMA channels (MDEC in, MDEC out, GPU, CD-ROM, SPU,
4//! expansion, ordering-table clear). Each channel has an address, a size and
5//! a control register at fixed 16-byte strides, plus a global enable and a
6//! global interrupt register. The addresses and bit layouts live in
7//! [`psx_hw::dma`].
8
9use crate::periph::{OrderingTableClearDma, SpuDma};
10use psx_hw::dma as reg;
11
12/// Channel index 0..=6 in the order the DMA controller presents them.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14#[repr(u8)]
15pub enum Channel {
16 /// RAM→MDEC: run-length coefficient data *into* the decoder. This is
17 /// the direction the name refers to, not the direction of transfer to
18 /// RAM -- channel 0 feeds the MDEC, channel 1 drains it.
19 MdecIn = 0,
20 /// MDEC→RAM: decoded macroblocks *out of* the decoder, 256 pixels
21 /// (16x16) per macroblock at 15bpp.
22 MdecOut = 1,
23 /// RAM↔GPU.
24 Gpu = 2,
25 /// CD-ROM drive → RAM.
26 #[doc(alias = "CDROM")]
27 Cd = 3,
28 /// SPU ↔ RAM.
29 Spu = 4,
30 /// Expansion port.
31 #[doc(alias = "PIO")]
32 Expansion = 5,
33 /// Ordering-table clear: writes a reverse-linked empty ordering table.
34 #[doc(alias = "OTC")]
35 OrderingTableClear = 6,
36}
37
38#[allow(non_upper_case_globals)]
39impl Channel {
40 /// Renamed to [`Channel::Cd`].
41 #[deprecated(note = "renamed to `Channel::Cd`")]
42 pub const Cdrom: Channel = Channel::Cd;
43}
44
45impl Channel {
46 /// Address of this channel's register block.
47 #[inline(always)]
48 pub const fn register_base(self) -> u32 {
49 reg::CHANNEL_BASE + reg::CHANNEL_STRIDE * (self as u32)
50 }
51
52 /// Bit position of this channel's enable flag in the global enable
53 /// register.
54 #[doc(alias = "DPCR")]
55 #[inline(always)]
56 pub const fn enable_bit(self) -> u32 {
57 3 + 4 * (self as u32)
58 }
59}
60
61/// Size word for manual or linked-list sync: a 16-bit word count
62/// (linked-list mode ignores it, but silicon wants it written).
63#[doc(alias = "BCR")]
64#[inline(always)]
65pub const fn size_words(words: u16) -> u32 {
66 words as u32
67}
68
69/// Size word for block sync: `block_count` blocks of `block_size` words.
70///
71/// A zero field means 0x1_0000 on silicon, so a zero count is a 65,536-block
72/// transfer, not an empty one.
73#[doc(alias = "BCR")]
74#[inline(always)]
75pub const fn size_blocks(block_size: u16, block_count: u16) -> u32 {
76 (block_size as u32) | ((block_count as u32) << 16)
77}
78
79/// The three per-channel register values that describe one transfer.
80///
81/// [`start`] stores them in the order silicon expects: address, size, then
82/// control (which starts the transfer when it carries `CHCR_START`).
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct Transfer {
85 /// RAM address the channel reads from or writes to.
86 #[doc(alias = "MADR")]
87 pub address: u32,
88 /// Word or block count; see [`size_words`] and [`size_blocks`].
89 #[doc(alias = "BCR")]
90 pub size: u32,
91 /// Control word ([`psx_hw::dma`]'s `CHCR_*` bits): direction, step, sync
92 /// mode, start.
93 #[doc(alias = "CHCR")]
94 pub control: u32,
95}
96
97/// Program channel `ch` with `transfer` and start it.
98///
99/// The same three stores every SDK driver makes: address, size, a
100/// compiler-only barrier that publishes ordinary RAM stores made before the
101/// call (it emits no instruction), then control. The caller still enables
102/// the channel ([`enable_channel`]) and waits for completion ([`wait_done`]).
103///
104/// # Safety
105///
106/// From the control store until the channel goes idle ([`wait_done`]
107/// returns `true`, or [`abort`] stops it), the DMA controller reads or writes
108/// RAM with no regard for Rust's borrow rules. The caller must guarantee that
109/// for that whole window:
110///
111/// - every word the transfer can **write** (a device-to-RAM channel, such
112/// as MDEC-out, CD-ROM, GPU readback or the ordering-table clear) lies in
113/// memory the caller owns exclusively, with no live Rust reference to it;
114/// - every word the transfer can **read** (a RAM-to-device channel, or each
115/// node of a linked list and the nodes its tags link to) is live,
116/// initialised and not written by anyone else;
117/// - the extent is the one `size` and the sync mode describe, remembering
118/// that a zero block count means 65,536 blocks;
119/// - the channel is idle when this is called (silicon ignores a control
120/// write to a busy channel, so the old transfer would keep running).
121#[inline(always)]
122pub unsafe fn start(ch: Channel, transfer: Transfer) {
123 // SAFETY: the caller upholds this function's contract for the transfer
124 // these three stores arm and start.
125 unsafe {
126 raw::set_address(ch, transfer.address);
127 raw::set_size(ch, transfer.size);
128 }
129 compiler_barrier();
130 // SAFETY: as above.
131 unsafe { raw::set_control(ch, transfer.control) };
132}
133
134/// Compiler-only barrier: ordinary RAM accesses are not moved across it in
135/// either direction, so stores before it land before an MMIO store after it
136/// and accesses after it follow an MMIO read before it.
137///
138/// The pinned MIPS-I backend lowers even a single-thread compiler fence to
139/// `SYNC`, which the R3000 lacks, so the target uses an empty `asm!` with its
140/// default memory clobber. Do not add `nomem` or `readonly`: both would drop
141/// the guarantee.
142#[inline(always)]
143pub fn compiler_barrier() {
144 #[cfg(target_arch = "mips")]
145 // SAFETY: an empty asm block; it only constrains compiler ordering.
146 unsafe {
147 core::arch::asm!("", options(nostack, preserves_flags));
148 }
149 #[cfg(not(target_arch = "mips"))]
150 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
151}
152
153/// Single-register writes for silicon probes and drivers that need a store
154/// order other than [`start`]'s.
155pub mod raw {
156 use super::{reg, Channel};
157
158 /// Write the channel's RAM address register.
159 ///
160 /// # Safety
161 ///
162 /// The value arms the next transfer on `ch`: the caller takes on
163 /// [`super::start`]'s contract for whatever transfer later starts with it.
164 ///
165 /// With the `present-queue` feature, a store to the GPU channel first
166 /// runs an armed direct-access guard (`gpu::arm_direct_access_guard`),
167 /// so it never re-arms the channel under a queued walk. Every GPU DMA
168 /// start, [`super::start`] included, goes through here.
169 #[doc(alias = "MADR")]
170 #[inline(always)]
171 pub unsafe fn set_address(ch: Channel, addr: u32) {
172 #[cfg(feature = "present-queue")]
173 if matches!(ch, Channel::Gpu) {
174 crate::gpu::run_direct_access_guard();
175 }
176 // SAFETY: a store to this channel's address register; see the contract above.
177 unsafe { crate::write_u32(ch.register_base() + reg::MADR, addr) }
178 }
179
180 /// Write the channel's size register.
181 ///
182 /// # Safety
183 ///
184 /// As [`set_address`]: the count sets the extent of the next transfer.
185 #[doc(alias = "BCR")]
186 #[inline(always)]
187 pub unsafe fn set_size(ch: Channel, value: u32) {
188 // SAFETY: a store to this channel's size register; see the contract above.
189 unsafe { crate::write_u32(ch.register_base() + reg::BCR, value) }
190 }
191
192 /// Write the channel's control register. With `CHCR_START` set this
193 /// starts a transfer from whatever the address and size registers hold.
194 ///
195 /// # Safety
196 ///
197 /// A value with `CHCR_START` set must satisfy [`super::start`]'s contract
198 /// for the transfer it starts. A value without it (such as 0, an abort)
199 /// starts nothing.
200 #[doc(alias = "CHCR")]
201 #[inline(always)]
202 pub unsafe fn set_control(ch: Channel, value: u32) {
203 // SAFETY: a store to this channel's control register; see the contract above.
204 unsafe { crate::write_u32(ch.register_base() + reg::CHCR, value) }
205 }
206}
207
208/// Write the channel's RAM address register.
209///
210/// # Safety
211///
212/// The value arms or starts a transfer on `ch`; see [`start`]'s contract.
213#[deprecated(
214 note = "a safe address store lets safe code aim DMA anywhere in RAM; use the unsafe `dma::start`, or `dma::raw::set_address` for probes"
215)]
216#[inline(always)]
217pub unsafe fn set_madr(ch: Channel, addr: u32) {
218 // SAFETY: forwarded contract.
219 unsafe { raw::set_address(ch, addr) }
220}
221
222/// Read the channel's RAM address register.
223#[doc(alias = "MADR")]
224#[inline(always)]
225pub fn address(ch: Channel) -> u32 {
226 // SAFETY: a side-effect-free read of this channel's address register.
227 unsafe { crate::read_u32(ch.register_base() + reg::MADR) }
228}
229
230/// Write the size register in manual / linked-list mode: just a 16-bit word
231/// count.
232///
233/// # Safety
234///
235/// The value arms or starts a transfer on `ch`; see [`start`]'s contract.
236#[deprecated(
237 note = "use the unsafe `dma::start` with `dma::size_words`, or `dma::raw::set_size` for probes"
238)]
239#[inline(always)]
240pub unsafe fn set_bcr_manual(ch: Channel, words: u16) {
241 // SAFETY: forwarded contract.
242 unsafe { raw::set_size(ch, size_words(words)) }
243}
244
245/// Write the control register. Starts the transfer if `CHCR_START` is set.
246///
247/// # Safety
248///
249/// The value arms or starts a transfer on `ch`; see [`start`]'s contract.
250#[deprecated(
251 note = "a safe control store starts DMA from safe code; use the unsafe `dma::start`, `dma::abort` to stop a channel, or `dma::raw::set_control` for probes"
252)]
253#[inline(always)]
254pub unsafe fn set_chcr(ch: Channel, value: u32) {
255 // SAFETY: forwarded contract.
256 unsafe { raw::set_control(ch, value) }
257}
258
259/// Read the channel's control register.
260#[doc(alias = "CHCR")]
261#[inline(always)]
262pub fn control(ch: Channel) -> u32 {
263 // SAFETY: a side-effect-free read of this channel's control register.
264 unsafe { crate::read_u32(ch.register_base() + reg::CHCR) }
265}
266
267/// True while the channel is busy with an in-flight transfer.
268#[inline(always)]
269pub fn is_busy(ch: Channel) -> bool {
270 control(ch) & reg::CHCR_START != 0
271}
272
273/// Enable a channel without disturbing the others.
274///
275/// The read-modify-write of the global enable register runs with CPU
276/// interrupts masked: with the `present-queue` feature the VBlank handler
277/// sets the GPU channel's bit in the same register, and a handler that ran
278/// between this read and write would have its bit overwritten.
279pub fn enable_channel(ch: Channel) {
280 crate::irq::without_interrupts(|| {
281 // SAFETY: a side-effect-free read of the global enable register.
282 let enabled = unsafe { crate::read_u32(reg::DPCR) };
283 // SAFETY: sets one channel's enable bit and keeps the rest; enabling a
284 // channel starts no transfer.
285 unsafe { crate::write_u32(reg::DPCR, enabled | (1 << ch.enable_bit())) }
286 });
287}
288
289impl OrderingTableClearDma {
290 /// Ordering-table clear: writes `buf` as a reverse-linked chain the
291 /// GPU-DMA walker consumes. The hardware starts from the last word and
292 /// steps backward, writing a terminator at the first transfer and then
293 /// predecessor pointers.
294 ///
295 /// Convenience wrapper: programs the channel and blocks until done.
296 /// Returns false if the channel wedged, or if `buf` is longer than the
297 /// 16-bit word count can express.
298 #[doc(alias = "ClearOTagR")]
299 pub fn clear_table(&mut self, buf: &mut [u32]) -> bool {
300 clear_table(buf)
301 }
302}
303
304/// [`OrderingTableClearDma::clear_table`] on a token the caller does not hold.
305#[deprecated(note = "use `OrderingTableClearDma::clear_table` with the token")]
306#[inline(always)]
307pub fn clear_ordering_table(buf: &mut [u32]) -> bool {
308 // SAFETY: a token is a logic guard, not a memory-safety one (see
309 // `crate::periph`), and the old function never took one.
310 unsafe { OrderingTableClearDma::steal() }.clear_table(buf)
311}
312
313fn clear_table(buf: &mut [u32]) -> bool {
314 let Ok(words) = u16::try_from(buf.len()) else {
315 // Truncating to 16 bits would clear only the tail of the table
316 // and leave the head pointing into words the DMA never touched.
317 return false;
318 };
319 let Some(last) = buf.last_mut() else {
320 return true;
321 };
322 let last_addr = last as *mut u32 as u32;
323 // Abort whatever the channel was doing before arming it. On silicon a
324 // transfer that never completes leaves START latched, and a write to
325 // the control register while the channel is still busy is ignored: one
326 // stuck kick then poisons every later transfer on that channel, which is
327 // how a single wedge turned into "the DMA moves nothing, forever" across
328 // the CD reader, the ordering-table clear, and the boot uploads.
329 abort(Channel::OrderingTableClear);
330 enable_channel(Channel::OrderingTableClear);
331 // Ordering-table clear: direction backward (step -4), manual sync,
332 // trigger bit.
333 // SAFETY: the channel was just aborted, so it is idle. The transfer
334 // writes `words` words stepping back from `last_addr`, which is exactly
335 // `buf`, borrowed exclusively until this function returns. It cannot
336 // outlive that borrow, and this does not rest on what an abort does:
337 // DMA6 has only CHCR bits 24, 28 and 30 writable, so it always runs in
338 // burst mode (SyncMode 0) without chopping, and psx-spx ("DMA Channels",
339 // "CPU Operation during DMA") documents that any RAM or I/O read stalls
340 // the CPU until such a transfer is finished, allowing only I/O reads
341 // within 3 cycles of the CHCR write. The wait below reads CHCR until it
342 // reads idle or its budget ends, so by the time it returns the transfer
343 // has finished or, wedged with START latched, never ran.
344 unsafe {
345 start(
346 Channel::OrderingTableClear,
347 Transfer {
348 address: last_addr,
349 size: size_words(words),
350 control: reg::CHCR_STEP_BACKWARD
351 | reg::CHCR_SYNC_MANUAL
352 | reg::CHCR_START
353 | reg::CHCR_TRIGGER,
354 },
355 )
356 };
357 // A wedge is aborted, which leaves the controller usable for the next
358 // caller rather than handing back a channel that will swallow its kick.
359 wait_or_abort(Channel::OrderingTableClear, DEFAULT_SPINS)
360}
361
362impl SpuDma {
363 /// Copy `words` to the SPU over DMA channel 4, in blocks of `block_words`
364 /// words, and wait for the channel to finish.
365 ///
366 /// Enables the channel without disturbing the others, starts a block-sync
367 /// transfer, and ends it with [`wait_or_abort`], so the SPU has taken
368 /// every word or the channel has been aborted by the time this returns.
369 /// The caller has already put the SPU in its DMA-write transfer mode.
370 ///
371 /// Returns `false` without starting anything when `words` is empty (a zero
372 /// block count means 65,536 blocks on silicon), when `block_words` is 0 or
373 /// does not divide the length, or when the block count does not fit the
374 /// 16-bit field; and `false` when the channel wedged and was aborted.
375 pub fn write_blocks(&mut self, words: &[u32], block_words: u16) -> bool {
376 let block = usize::from(block_words);
377 if words.is_empty() || block == 0 || !words.len().is_multiple_of(block) {
378 return false;
379 }
380 let Ok(block_count) = u16::try_from(words.len() / block) else {
381 return false;
382 };
383 enable_channel(Channel::Spu);
384 // SAFETY: the transfer reads `block_words * block_count` words from
385 // `words`, which is exactly the slice (a whole number of non-empty
386 // blocks, checked above), borrowed until this function returns. The
387 // wait below, or its abort on a wedge, ends the transfer before then.
388 unsafe {
389 start(
390 Channel::Spu,
391 Transfer {
392 address: words.as_ptr() as u32,
393 size: size_blocks(block_words, block_count),
394 control: reg::CHCR_TO_DEVICE | reg::CHCR_SYNC_BLOCK | reg::CHCR_START,
395 },
396 )
397 };
398 wait_or_abort(Channel::Spu, DEFAULT_SPINS)
399 }
400}
401
402/// Spin budget for one DMA completion wait. Comfortably longer than the
403/// largest legitimate transfer (a full-screen VRAM upload) and short
404/// enough that a wedged channel returns control inside a frame.
405pub const DEFAULT_SPINS: u32 = 500_000;
406
407/// Clear `ch`'s control register, dropping its START bit, so the channel can
408/// be re-armed: on silicon a CHCR write to a channel whose START is still
409/// latched is ignored, which is how one wedged kick used to poison every
410/// later transfer on the channel.
411///
412/// psx-spx documents only that START clears when a transfer completes, not
413/// that clearing it stops one in progress. Code whose memory safety needs a
414/// transfer to have stopped must not rely on this call for it; see
415/// [`OrderingTableClearDma::clear_table`] for a bound that does not.
416pub fn abort(ch: Channel) {
417 // SAFETY: a control word of 0 has no START bit, so it starts nothing.
418 unsafe { raw::set_control(ch, 0) };
419}
420
421/// Bounded completion wait. `false` means the channel was still busy
422/// when the budget ran out, i.e. the transfer is wedged and the caller
423/// must not assume its data landed.
424pub fn wait_done(ch: Channel, spins: u32) -> bool {
425 let mut waited = 0u32;
426 while is_busy(ch) {
427 if waited >= spins {
428 return false;
429 }
430 waited += 1;
431 }
432 true
433}
434
435/// End a transfer started with [`start`]: [`wait_done`]; if the budget runs
436/// out, [`abort`] the channel and wait again, within the same budget, for it
437/// to read idle; then a compiler barrier so the caller's next RAM access to
438/// the buffer is not moved before the completion read (the barrier emits no
439/// instruction). `false` means the data did not all land.
440///
441/// The second wait is there because clearing START is not documented to stop
442/// a running transfer (see [`abort`]). What it leaves assumed: a channel that
443/// still reads busy after both budgets is wedged and moving no data, as the
444/// CD channel's console wedges were (START latched, MADR frozen). Burst-mode
445/// channels without chopping need no assumption; see
446/// [`OrderingTableClearDma::clear_table`].
447pub fn wait_or_abort(ch: Channel, spins: u32) -> bool {
448 let done = wait_done(ch, spins);
449 if !done {
450 abort(ch);
451 let _ = wait_done(ch, spins);
452 }
453 compiler_barrier();
454 done
455}
456
457#[cfg(test)]
458mod tests {
459 extern crate std;
460 use super::*;
461
462 #[test]
463 fn block_size_packs_size_low_and_count_high() {
464 assert_eq!(size_blocks(16, 3), 0x0003_0010);
465 assert_eq!(size_blocks(0xFFFF, 0xFFFF), 0xFFFF_FFFF);
466 assert_eq!(size_words(0x1234), 0x1234);
467 }
468
469 #[test]
470 fn channel_blocks_sit_at_sixteen_byte_strides() {
471 assert_eq!(Channel::MdecIn.register_base(), reg::CHANNEL_BASE);
472 assert_eq!(
473 Channel::Gpu.register_base(),
474 reg::CHANNEL_BASE + 2 * reg::CHANNEL_STRIDE
475 );
476 assert_eq!(
477 Channel::OrderingTableClear.register_base(),
478 reg::CHANNEL_BASE + 6 * reg::CHANNEL_STRIDE
479 );
480 assert_eq!(Channel::OrderingTableClear.enable_bit(), 27);
481 }
482
483 #[test]
484 fn an_spu_write_refuses_a_shape_it_cannot_describe_before_touching_the_channel() {
485 // SAFETY: a test-local token on the host; every call below returns
486 // before any register is touched.
487 let mut dma = unsafe { SpuDma::steal() };
488 let words = std::vec![0u32; 12];
489 // Nothing to send: a zero block count would be 65,536 blocks.
490 assert!(!dma.write_blocks(&[], 4));
491 // No block size, or one that leaves a partial block.
492 assert!(!dma.write_blocks(&words, 0));
493 assert!(!dma.write_blocks(&words, 5));
494 // More blocks than the 16-bit count holds.
495 let long = std::vec![0u32; 65_536];
496 assert!(!dma.write_blocks(&long, 1));
497 }
498
499 #[test]
500 fn the_clear_refuses_a_table_it_cannot_describe_before_touching_the_channel() {
501 // SAFETY: a test-local token on the host; both calls return before any
502 // register is touched.
503 let mut dma = unsafe { OrderingTableClearDma::steal() };
504 // An empty table has nothing to clear.
505 assert!(dma.clear_table(&mut []));
506 // 65,536 words does not fit the 16-bit count: refused whole, not
507 // truncated to its tail.
508 let mut long = std::vec![0u32; 65_536];
509 assert!(!dma.clear_table(&mut long));
510 }
511}