psx_fmv/mdec.rs
1// SPDX-License-Identifier: GPL-2.0-or-later
2//! MDEC driver: table upload, decode command, DMA0 in, DMA1 out.
3//!
4//! [`Mdec`] owns the two DMA channels that feed and drain the decoder; every
5//! step that starts one is a method on it.
6//!
7//! Decode protocol, as commercial players drive it:
8//!
9//! 1. [`Mdec::reset`] once, then [`Mdec::load_tables`] (quantization + IDCT
10//! tables). Neither assumes the MDEC reacts at once: see "Reset latency"
11//! below.
12//! 2. Per frame, [`Mdec::decode_start`] writes the decode command (output depth +
13//! run-length word count) to MDEC0 and kicks DMA0 with the whole
14//! run-length buffer. It does not wait: the MDEC throttles DMA0 as its
15//! output FIFO fills.
16//! 3. [`Mdec::read_column`] pulls one 16-pixel-wide column of decoded
17//! macroblocks over DMA1 (macroblocks come out in the order the
18//! bitstream stores them: top to bottom, then left to right), which the
19//! caller uploads to VRAM.
20//! 4. [`Mdec::decode_finish`] confirms DMA0 drained.
21//!
22//! Every wait is bounded (`psx_io::dma::wait_done`), and every kick aborts
23//! the channel first, per the SDK rule for silicon DMA wedges.
24//!
25//! # Reset latency
26//!
27//! On a PAL SCPH-9002 the MDEC does not finish a reset within the next bus
28//! access: the status read straight after writing the reset still shows the
29//! state from before it (0x6401_0000: busy, data-in full), where the
30//! SuperStation One FPGA and PSoXide already read the documented reset state.
31//! Writing the DMA-request enable right behind the reset, as this driver used
32//! to, left the table upload waiting on DMA0 forever on that console while
33//! both of the others played the movie. So [`Mdec::reset`] waits for the reset to
34//! settle before enabling requests, and [`Mdec::load_tables`] checks that the MDEC
35//! actually raised its data-in request (status bit 28) after each command,
36//! re-writing the enable if it did not, and falls back to CPU writes when
37//! DMA0 still will not take the table.
38
39use psx_hw::mdec::{self as hw, MDEC0, MDEC1};
40use psx_io::dma::{self, Channel};
41use psx_io::periph::MdecDma;
42
43use crate::rle::{self, RleLengthError};
44
45/// DMA block size the MDEC channels use, in words.
46pub const DMA_BLOCK_WORDS: usize = crate::rle::BLOCK_WORDS;
47
48/// Spin budget for one table upload or column transfer.
49pub const DMA_SPINS: u32 = 400_000;
50
51// CHCR: to device / from device, block sync, start.
52const CHCR_IN: u32 =
53 psx_hw::dma::CHCR_TO_DEVICE | psx_hw::dma::CHCR_SYNC_BLOCK | psx_hw::dma::CHCR_START;
54const CHCR_OUT: u32 = psx_hw::dma::CHCR_SYNC_BLOCK | psx_hw::dma::CHCR_START;
55
56/// Standard intra quantization matrix (row-major), DC entry 2.
57const QUANT_ROW_MAJOR: [u8; 64] = [
58 2, 16, 19, 22, 26, 27, 29, 34, //
59 16, 16, 22, 24, 27, 29, 34, 37, //
60 19, 22, 26, 27, 29, 34, 34, 38, //
61 22, 22, 26, 27, 29, 34, 37, 40, //
62 22, 26, 27, 29, 32, 35, 40, 48, //
63 26, 27, 29, 32, 35, 40, 48, 58, //
64 26, 27, 29, 34, 38, 46, 56, 69, //
65 27, 29, 35, 38, 46, 56, 69, 83,
66];
67
68/// Zigzag position `i` to its row-major index.
69const ZIGZAG_TO_ROW_MAJOR: [u8; 64] = [
70 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, //
71 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, //
72 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, //
73 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
74];
75
76/// Luma then chroma table, zigzag order, as the 32 words command 2 takes.
77pub static QUANT_WORDS: [u32; 32] = build_quant();
78
79const fn build_quant() -> [u32; 32] {
80 let mut bytes = [0u8; 128];
81 let mut i = 0;
82 while i < 64 {
83 let q = QUANT_ROW_MAJOR[ZIGZAG_TO_ROW_MAJOR[i] as usize];
84 bytes[i] = q;
85 bytes[64 + i] = q;
86 i += 1;
87 }
88 let mut words = [0u32; 32];
89 let mut w = 0;
90 while w < 32 {
91 words[w] = u32::from_le_bytes([
92 bytes[4 * w],
93 bytes[4 * w + 1],
94 bytes[4 * w + 2],
95 bytes[4 * w + 3],
96 ]);
97 w += 1;
98 }
99 words
100}
101
102/// The IDCT matrix for MDEC command 3, generated from the DCT basis in
103/// [`crate::idct`].
104pub static SCALE_WORDS: [u32; 32] = crate::idct::MATRIX_WORDS;
105
106// Register constants moved to psx-hw; these forward until games repin.
107
108/// Spin budget for one status wait (reset settle, request, FIFO room).
109/// The console settles a reset in tens of cycles; this is a few ms.
110pub const SETTLE_SPINS: u32 = 20_000;
111/// Status reads to spend after a reset looks settled. A reset from an idle
112/// MDEC can read idle before it has finished, so waiting for busy to drop
113/// is not enough on its own. Each read is an MMIO access, several cycles,
114/// so this is a few hundred cycles against the tens a reset takes.
115const RESET_TAIL_READS: u32 = 64;
116/// Enable writes to try before giving up on the data-in request.
117const ENABLE_ATTEMPTS: u8 = 4;
118
119/// Raw MDEC1 status word.
120#[inline(always)]
121pub fn status() -> u32 {
122 // SAFETY: MDEC status register read.
123 unsafe { psx_io::read_u32(MDEC1) }
124}
125
126/// Wait until `status() & mask == want`. `false` on timeout.
127fn wait_status(mask: u32, want: u32, spins: u32) -> bool {
128 let mut n = 0;
129 while status() & mask != want {
130 if n >= spins {
131 return false;
132 }
133 n += 1;
134 }
135 true
136}
137
138/// Send `blocks` 32-word blocks from `words` to the MDEC over DMA0 without
139/// waiting.
140///
141/// # Safety
142/// `blocks` must not be 0 (the controller reads 0 as 65,536 blocks), and
143/// `blocks * 32` words at `words` must stay alive and unmodified until DMA0
144/// completes.
145unsafe fn dma_in(words: *const u32, blocks: u16) {
146 debug_assert!(blocks != 0, "a zero block count is 65,536 blocks");
147 dma::abort(Channel::MdecIn);
148 // SAFETY: the channel was just aborted, so it is idle; the caller keeps
149 // the `blocks * 32` words at `words` alive and unmodified until DMA0
150 // completes, and `blocks` is not 0.
151 unsafe {
152 dma::start(
153 Channel::MdecIn,
154 dma::Transfer {
155 address: words as u32,
156 size: dma::size_blocks(DMA_BLOCK_WORDS as u16, blocks),
157 control: CHCR_IN,
158 },
159 )
160 };
161}
162
163/// How [`Mdec::load_tables`] got the tables in.
164#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
165pub struct Tables {
166 /// Enable writes, summed over both commands, before the MDEC raised its
167 /// data-in request: 2 when every first write held. 0 means it never
168 /// asked for data, so DMA0 decodes will not run either.
169 pub enable_writes: u8,
170 /// Tables that went in over the CPU because the MDEC never asked for
171 /// DMA0 data or DMA0 wedged part way (0, 1 or 2).
172 pub cpu_uploads: u8,
173}
174
175impl Tables {
176 /// The MDEC asked for data on both commands, so DMA0 feeds it.
177 pub fn is_dma_ready(&self) -> bool {
178 self.enable_writes != 0 && self.cpu_uploads == 0
179 }
180}
181
182/// The MDEC's driver: it owns the two DMA channels that feed and drain it, so
183/// every decode step that starts one is a method here and the borrow checker
184/// sees a second decoder.
185///
186/// A decode frame is: [`reset`](Self::reset) and
187/// [`load_tables`](Self::load_tables) once, then per frame
188/// [`decode`](Self::decode) with a closure that pulls the output columns.
189///
190/// The status register stays a free function ([`status`]): reading it
191/// cannot disturb a transfer, so it needs no owner.
192#[derive(Debug)]
193#[repr(transparent)]
194pub struct Mdec(MdecDma);
195
196impl Mdec {
197 /// Take the MDEC's DMA channels. Touches no register: call
198 /// [`reset`](Self::reset) next.
199 pub const fn new(dma: MdecDma) -> Self {
200 Self(dma)
201 }
202
203 /// Give the DMA token back.
204 #[inline(always)]
205 pub fn release(self) -> MdecDma {
206 self.0
207 }
208
209 /// Reset the MDEC, wait for the reset to finish, then enable its DMA
210 /// requests on both channels. `false` if the MDEC stayed busy after the
211 /// reset (the enable is still written).
212 pub fn reset(&mut self) -> bool {
213 dma::abort(Channel::MdecIn);
214 dma::abort(Channel::MdecOut);
215 dma::enable_channel(Channel::MdecIn);
216 dma::enable_channel(Channel::MdecOut);
217 // SAFETY: MDEC control register write.
218 unsafe { psx_io::write_u32(MDEC1, hw::CONTROL_RESET) };
219 let settled = wait_status(hw::STATUS_BUSY, 0, SETTLE_SPINS);
220 let mut n = 0;
221 while n < RESET_TAIL_READS {
222 let _ = status();
223 n += 1;
224 }
225 // SAFETY: MDEC control register write.
226 unsafe { psx_io::write_u32(MDEC1, hw::CONTROL_ENABLE_DMA) };
227 settled
228 }
229
230 /// Write `command`, then feed its 32 parameter words: over DMA0 when the
231 /// MDEC asks for data, else over the CPU. Returns (enable writes before the
232 /// request rose, 0 if never; went over the CPU), or `None` if the MDEC
233 /// would not take the words at all.
234 fn upload(&mut self, command: u32, words: &[u32; 32]) -> Option<(u8, bool)> {
235 if !wait_status(hw::STATUS_BUSY, 0, SETTLE_SPINS) {
236 return None;
237 }
238 // SAFETY: MDEC command write.
239 unsafe { psx_io::write_u32(MDEC0, command) };
240 let mut writes = 0u8;
241 let mut asked = wait_status(hw::STATUS_IN_REQUEST, hw::STATUS_IN_REQUEST, SETTLE_SPINS);
242 while !asked && writes + 1 < ENABLE_ATTEMPTS {
243 // The enable can be lost to a reset that had not finished.
244 // SAFETY: MDEC control register write, no reset bit.
245 unsafe { psx_io::write_u32(MDEC1, hw::CONTROL_ENABLE_DMA) };
246 writes += 1;
247 asked = wait_status(hw::STATUS_IN_REQUEST, hw::STATUS_IN_REQUEST, SETTLE_SPINS);
248 }
249 let enable_writes = if asked { writes + 1 } else { 0 };
250 if asked {
251 // SAFETY: `words` is a borrowed table; the transfer is waited out.
252 unsafe { dma_in(words.as_ptr(), 1) };
253 if dma::wait_done(Channel::MdecIn, DMA_SPINS) {
254 return Some((enable_writes, false));
255 }
256 // Part of the table went in: start the command over.
257 dma::abort(Channel::MdecIn);
258 self.reset();
259 if !wait_status(hw::STATUS_BUSY, 0, SETTLE_SPINS) {
260 return None;
261 }
262 // SAFETY: MDEC command write.
263 unsafe { psx_io::write_u32(MDEC0, command) };
264 }
265 for &word in words {
266 if !wait_status(hw::STATUS_IN_FULL, 0, SETTLE_SPINS) {
267 return None;
268 }
269 // SAFETY: MDEC parameter write.
270 unsafe { psx_io::write_u32(MDEC0, word) };
271 }
272 Some((enable_writes, true))
273 }
274
275 /// Upload the standard quantization tables and the IDCT basis. `None` if
276 /// the MDEC would take them neither over DMA0 nor over the CPU.
277 pub fn load_tables(&mut self) -> Option<Tables> {
278 let (quant_writes, quant_cpu) = self.upload(hw::COMMAND_SET_QUANT, &QUANT_WORDS)?;
279 let (scale_writes, scale_cpu) = self.upload(hw::COMMAND_SET_SCALE, &SCALE_WORDS)?;
280 if !wait_status(hw::STATUS_BUSY, 0, SETTLE_SPINS) {
281 return None;
282 }
283 Some(Tables {
284 enable_writes: if quant_writes == 0 || scale_writes == 0 {
285 0
286 } else {
287 quant_writes + scale_writes
288 },
289 cpu_uploads: quant_cpu as u8 + scale_cpu as u8,
290 })
291 }
292
293 /// Upload both tables over CPU writes to MDEC0 only, after
294 /// [`reset`](Self::reset). No DMA involved: the control path for a console
295 /// whose DMA0 will not feed the MDEC. `false` if the input FIFO never made
296 /// room.
297 pub fn load_tables_cpu(&mut self) -> bool {
298 for (command, words) in [
299 (hw::COMMAND_SET_QUANT, &QUANT_WORDS),
300 (hw::COMMAND_SET_SCALE, &SCALE_WORDS),
301 ] {
302 if !wait_status(hw::STATUS_BUSY, 0, SETTLE_SPINS) {
303 return false;
304 }
305 self.write_command(command);
306 for &word in words {
307 if !wait_status(hw::STATUS_IN_FULL, 0, SETTLE_SPINS) {
308 return false;
309 }
310 self.write_command(word);
311 }
312 }
313 wait_status(hw::STATUS_BUSY, 0, SETTLE_SPINS)
314 }
315
316 /// Write one word to MDEC0: a command, or a parameter the CPU feeds itself.
317 #[inline(always)]
318 pub fn write_command(&mut self, word: u32) {
319 // SAFETY: MDEC command/parameter write.
320 unsafe { psx_io::write_u32(MDEC0, word) }
321 }
322
323 /// Read one word of decoded output from MDEC0 (the CPU path; DMA1 is the
324 /// usual one). Garbage when the output FIFO is empty.
325 #[inline(always)]
326 pub fn read_data(&mut self) -> u32 {
327 // SAFETY: MDEC data read.
328 unsafe { psx_io::read_u32(MDEC0) }
329 }
330
331 /// Start decoding `words` 32-bit words of run-length data (a multiple of 32,
332 /// as [`crate::bitstream::decode_frame`] returns). `mode` is
333 /// [`psx_hw::mdec::DECODE_15BPP`] or [`psx_hw::mdec::DECODE_24BPP`],
334 /// optionally with [`psx_hw::mdec::DECODE_STP`].
335 ///
336 /// [`decode`](Self::decode) is the safe form: it holds the borrow of `rle`
337 /// until DMA0 is done with it.
338 ///
339 /// # Errors
340 /// [`RleLengthError`] when `words` is 0, not a multiple of 32, longer than
341 /// the decode command can announce ([`crate::rle::MAX_WORDS`]) or longer
342 /// than `rle`. Nothing is written to the MDEC then and no DMA starts.
343 ///
344 /// # Safety
345 /// On `Ok`, DMA0 keeps reading `rle` after this returns. The caller must
346 /// keep `rle` alive and unmodified until [`decode_finish`](Self::decode_finish)
347 /// returns, and must call it before the storage is reused or freed.
348 pub unsafe fn decode_start(
349 &mut self,
350 rle: &[u32],
351 words: usize,
352 mode: u32,
353 ) -> Result<(), RleLengthError> {
354 if words > rle.len() {
355 return Err(RleLengthError::PastBuffer);
356 }
357 let blocks = rle::dma_block_count(words)?;
358 // SAFETY: MMIO write to MDEC0, then a DMA0 kick of `blocks` (not 0)
359 // 32-word blocks, exactly the first `words` words of `rle` (checked
360 // above), which the caller keeps alive until `decode_finish`.
361 unsafe {
362 psx_io::write_u32(MDEC0, mode | words as u32);
363 dma_in(rle.as_ptr(), blocks);
364 }
365 Ok(())
366 }
367
368 /// Decode all of `rle` (a multiple of 32 words, as
369 /// [`crate::bitstream::decode_frame`] returns) with DMA0 feeding the MDEC,
370 /// the safe form of [`decode_start`](Self::decode_start).
371 ///
372 /// `columns` runs while DMA0 feeds the MDEC and pulls the output, normally
373 /// with [`read_column`](Self::read_column) once per column; it gets the
374 /// driver back for that. Returns its result and
375 /// [`decode_finish`](Self::decode_finish)'s: `false` there means DMA0 was
376 /// still busy and has been aborted.
377 ///
378 /// # Errors
379 /// [`RleLengthError`] when `rle` is empty, not a whole number of 32-word
380 /// blocks or longer than [`crate::rle::MAX_WORDS`]; `columns` does not run.
381 pub fn decode<R>(
382 &mut self,
383 rle: &[u32],
384 mode: u32,
385 columns: impl FnOnce(&mut Self) -> R,
386 ) -> Result<(R, bool), RleLengthError> {
387 // Finishes the decode on every exit, an unwinding `columns` included.
388 struct Finish<'a> {
389 mdec: &'a mut Mdec,
390 done: bool,
391 }
392 impl Drop for Finish<'_> {
393 fn drop(&mut self) {
394 if !self.done {
395 self.mdec.decode_finish();
396 }
397 }
398 }
399 // SAFETY: `rle` stays borrowed until this function returns. On `Err`
400 // no DMA started; on `Ok` every return path runs decode_finish first
401 // (directly, or via `Finish` when unwinding), which returns only once
402 // DMA0 is done or aborted.
403 unsafe { self.decode_start(rle, rle.len(), mode)? };
404 let mut finish = Finish {
405 mdec: self,
406 done: false,
407 };
408 let out = columns(finish.mdec);
409 finish.done = true;
410 Ok((out, finish.mdec.decode_finish()))
411 }
412
413 /// Start pulling the next `words` words (a multiple of 32) of decoded
414 /// pixels over DMA1 into `dst` and return at once;
415 /// [`column_done`](Self::column_done) says when they are in. The MDEC
416 /// decodes only while DMA1 drains its output, so a player that overlaps
417 /// work with the decode kicks the next column as soon as the last one
418 /// lands.
419 ///
420 /// Only whole 32-word blocks move. Returns `false`, having started
421 /// nothing, when that is no block at all or more than one transfer can
422 /// count: the controller reads a zero block count as 65,536 blocks, which
423 /// would run far past `dst`.
424 ///
425 /// # Safety
426 /// `dst` must point at `words` writable words that stay alive and
427 /// untouched until [`column_done`](Self::column_done) returns `true` or
428 /// DMA1 is aborted ([`abort_decode`](Self::abort_decode)).
429 pub unsafe fn read_column_start(&mut self, dst: *mut u32, words: usize) -> bool {
430 let Ok(blocks) = u16::try_from(words / DMA_BLOCK_WORDS) else {
431 return false;
432 };
433 if blocks == 0 {
434 return false;
435 }
436 dma::abort(Channel::MdecOut);
437 // SAFETY: the channel was just aborted, so it is idle. The transfer
438 // writes `blocks * DMA_BLOCK_WORDS` = `words` words at `dst`, which
439 // the caller keeps writable and untouched until it completes or is
440 // aborted.
441 unsafe {
442 dma::start(
443 Channel::MdecOut,
444 dma::Transfer {
445 address: dst as u32,
446 size: dma::size_blocks(DMA_BLOCK_WORDS as u16, blocks),
447 control: CHCR_OUT,
448 },
449 )
450 };
451 true
452 }
453
454 /// True once the column [`read_column_start`](Self::read_column_start)
455 /// asked for has arrived.
456 #[inline]
457 pub fn column_done(&self) -> bool {
458 !dma::is_busy(Channel::MdecOut)
459 }
460
461 /// Stop both MDEC DMA channels, e.g. when a decode has taken too long. A
462 /// column or run-length buffer a transfer was using is free again once
463 /// this returns.
464 pub fn abort_decode(&mut self) {
465 dma::abort(Channel::MdecOut);
466 dma::abort(Channel::MdecIn);
467 }
468
469 /// Pull the next `dst.len()` words (a multiple of 32) of decoded pixels
470 /// over DMA1 and wait for them. For 15bpp one 16-pixel-wide column of
471 /// height `h` is `8 * h` words. `false` if DMA1 wedged.
472 pub fn read_column(&mut self, dst: &mut [u32]) -> bool {
473 // SAFETY: `dst` is borrowed exclusively until this function returns;
474 // the wait below, or the abort on a wedge, ends the transfer before
475 // then.
476 if !unsafe { self.read_column_start(dst.as_mut_ptr(), dst.len()) } {
477 return false;
478 }
479 dma::wait_or_abort(Channel::MdecOut, DMA_SPINS)
480 }
481
482 /// Confirm DMA0 finished feeding the frame. `false` (after aborting the
483 /// channel) if it is still busy, e.g. the frame held more data than was
484 /// read back.
485 pub fn decode_finish(&mut self) -> bool {
486 dma::wait_or_abort(Channel::MdecIn, DMA_SPINS)
487 }
488}
489
490/// A driver for a deprecated forwarder that never took the token.
491fn steal_mdec() -> Mdec {
492 // SAFETY: a token is a logic guard, not a memory-safety one (see
493 // `psx_io::periph`), and the old free functions never took one.
494 Mdec::new(unsafe { MdecDma::steal() })
495}
496
497/// Reset the MDEC and enable its DMA requests.
498#[deprecated(note = "use `Mdec::reset` with the `MdecDma` token")]
499pub fn reset() -> bool {
500 steal_mdec().reset()
501}
502
503/// Upload the standard quantization tables and the IDCT basis.
504#[deprecated(note = "use `Mdec::load_tables` with the `MdecDma` token")]
505pub fn load_tables() -> Option<Tables> {
506 steal_mdec().load_tables()
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 #[test]
514 fn the_driver_is_its_token_and_gives_it_back() {
515 assert_eq!(core::mem::size_of::<Mdec>(), 0);
516 // SAFETY: a test-local token on the host; `new` touches no register.
517 let mdec = Mdec::new(unsafe { MdecDma::steal() });
518 let _token: MdecDma = mdec.release();
519 }
520
521 #[test]
522 fn a_decode_that_cannot_be_described_never_runs_its_columns() {
523 // SAFETY: a test-local token on the host; both calls are refused
524 // before any register is touched.
525 let mut mdec = Mdec::new(unsafe { MdecDma::steal() });
526 let mut ran = false;
527 // Empty, and one word short of a block: refused before any write.
528 for rle in [&[][..], &[0u32; 31][..]] {
529 let result = mdec.decode(rle, hw::DECODE_15BPP, |_| ran = true);
530 assert!(result.is_err());
531 }
532 assert!(!ran);
533 }
534
535 #[test]
536 fn a_column_slice_the_channel_cannot_describe_is_refused_before_it_starts() {
537 // SAFETY: a test-local token on the host; both are refused first.
538 let mut mdec = Mdec::new(unsafe { MdecDma::steal() });
539 assert!(!mdec.read_column(&mut []));
540 assert!(!mdec.read_column(&mut [0u32; 31]));
541 }
542}