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

psx_mc/
hardware.rs

1// SPDX-License-Identifier: GPL-2.0-or-later
2//! On-device SIO0 transport for a physical memory card (feature `hw`).
3//!
4//! Talks the raw card protocol byte by byte: assert the port, clock `0x81` to
5//! address the card, then a Read (`0x52`) or Write (`0x57`) command frame. The
6//! serial link is `psx-io`'s shared [`Transport`], the one `psx-pad` polls
7//! controllers with: the same select, the same ACK-paced byte exchange that
8//! watches the live `/ACK` level, the same release. A card's own timing is a
9//! longer `/ACK` budget, because its write commit stalls between bytes while
10//! flash settles.
11//!
12//! A [`HardwareCard`] holds the controller port while it exists: either the
13//! [`ControllerPort`] token itself ([`HardwareCard::on_port`] with the token,
14//! and [`release`](HardwareCard::release) gives it back) or a `&mut` borrow of
15//! one the program keeps for polling pads between saves.
16//!
17//! Timing is exposed as a runtime knob ([`HardwareCard::on_port_with_timing`])
18//! because real silicon varies: the emulator ACKs instantly, an official card
19//! needs the setup delay, and a write commit may need a longer ACK wait than a
20//! pad byte.
21//!
22//! [`HardwareCard::last_trace`] exposes bounded transport diagnostics. In
23//! particular, an `/ACK` timeout is never silently treated as a successful
24//! exchange: the rest of that transaction is aborted and the first failing byte
25//! remains visible to an on-console diagnostic.
26
27use crate::{Block, Error, Result, FRAME_COUNT, FRAME_SIZE};
28use core::borrow::BorrowMut;
29use psx_io::controller_port::{ExchangeError, Port, Transport};
30use psx_io::periph::ControllerPort;
31
32// Protocol bytes.
33const CARD_SELECT: u8 = 0x81;
34const CMD_READ: u8 = 0x52;
35const CMD_WRITE: u8 = 0x57;
36const ID1: u8 = 0x5A;
37const ACK1: u8 = 0x5C;
38const END_GOOD: u8 = 0x47;
39/// A card needs a slightly longer recovery interval after acknowledging the
40/// initial `0x81` device-select byte than it does between ordinary frame bytes.
41const FIRST_COMMAND_SETTLE_SPINS: u32 = 1_024;
42/// Conservative volatile-MMIO delay after a 128-byte sector write. Reference
43/// drivers leave at least two video periods before accessing the card again.
44/// This intentionally covers the slower 50 Hz case as well as 60 Hz consoles.
45const POST_WRITE_SETTLE_SPINS: u32 = 400_000;
46
47/// Which controller/card port to use.
48pub type Slot = Port;
49
50/// The first low-level failure observed in the latest frame transaction.
51#[derive(Copy, Clone, Debug, PartialEq, Eq)]
52pub enum TransportFault {
53    /// No low-level timeout was observed.
54    None,
55    /// SIO0 never became ready to accept the next byte.
56    TxTimeout,
57    /// SIO0 never returned a byte after transmission.
58    RxTimeout,
59    /// The card did not pulse `/ACK` after a non-final byte.
60    AckTimeout,
61    /// `/ACK` was observed, but the live line did not return high.
62    AckReleaseTimeout,
63}
64
65/// Compact evidence from the latest raw read/write transaction.
66#[derive(Copy, Clone, Debug, PartialEq, Eq)]
67pub struct TransportTrace {
68    /// Total byte exchanges attempted before the transaction was aborted.
69    pub exchanges: u16,
70    /// Number of `/ACK` pulses observed.
71    pub acknowledgements: u16,
72    /// First low-level fault.
73    pub fault: TransportFault,
74    /// Zero-based exchange at which `fault` occurred, or `0xffff` for none.
75    pub fault_exchange: u16,
76    /// First ten response bytes (flags, IDs, echoes and acknowledgements).
77    pub response_prefix: [u8; 10],
78}
79
80impl TransportTrace {
81    const fn new() -> Self {
82        Self {
83            exchanges: 0,
84            acknowledgements: 0,
85            fault: TransportFault::None,
86            fault_exchange: u16::MAX,
87            response_prefix: [0xff; 10],
88        }
89    }
90}
91
92/// Tunable transfer timing (bounded MMIO spin counts). Defaults mirror the pad.
93#[derive(Copy, Clone, Debug)]
94pub struct Timing {
95    /// Delay after asserting select, before the first byte (strict-pad setup).
96    pub setup_spins: u32,
97    /// Bound on waiting for TX-ready / RX-filled per byte.
98    pub byte_spins: u32,
99    /// Bound on waiting for the `/ACK` pulse (raise for slow write commits).
100    pub ack_spins: u32,
101}
102
103impl Default for Timing {
104    fn default() -> Self {
105        let card = psx_io::controller_port::Timing::CARD;
106        Timing {
107            setup_spins: card.setup_spins,
108            byte_spins: card.byte_spins,
109            ack_spins: card.ack_spins,
110        }
111    }
112}
113
114impl Timing {
115    fn link(self) -> psx_io::controller_port::Timing {
116        psx_io::controller_port::Timing {
117            setup_spins: self.setup_spins,
118            byte_spins: self.byte_spins,
119            ack_spins: self.ack_spins,
120        }
121    }
122}
123
124/// A physical memory card reached over SIO0.
125///
126/// `P` is how the card holds the port: the [`ControllerPort`] token (the
127/// default) or a `&mut ControllerPort` borrowed from the program for the
128/// length of the card's use. While a card holds the port, a pad poll cannot
129/// borrow it:
130///
131/// ```compile_fail,E0499
132/// use psx_mc::{HardwareCard, Slot};
133/// use psx_io::periph::ControllerPort;
134///
135/// // SAFETY: the doctest never touches the port; it only has to compile.
136/// let mut port = unsafe { ControllerPort::steal() };
137/// let mut card = HardwareCard::on_port(&mut port, Slot::One);
138/// let other_driver = &mut port; // the card still holds the borrow
139/// let _ = card.last_trace();
140/// let _ = other_driver;
141/// ```
142pub struct HardwareCard<P = ControllerPort> {
143    port: P,
144    slot: Slot,
145    timing: Timing,
146    trace: TransportTrace,
147}
148
149impl HardwareCard<ControllerPort> {
150    /// A card on the given `slot` with default timing.
151    #[deprecated(note = "use `HardwareCard::on_port` with the `ControllerPort` token")]
152    pub fn new(slot: Slot) -> Self {
153        Self::on_port(steal_port(), slot)
154    }
155}
156
157/// The token for a deprecated constructor that never took one.
158fn steal_port() -> ControllerPort {
159    // SAFETY: a token is a logic guard, not a memory-safety one (see
160    // `psx_io::periph`), and the old constructors never took one.
161    unsafe { ControllerPort::steal() }
162}
163
164impl<P: BorrowMut<ControllerPort>> HardwareCard<P> {
165    /// A card on `slot`, reached through `port` (the token, or a borrow of
166    /// it), with the memory card's default [`Timing`].
167    pub fn on_port(port: P, slot: Slot) -> Self {
168        Self::on_port_with_timing(port, slot, Timing::default())
169    }
170
171    /// [`on_port`](Self::on_port) with custom [`Timing`], for tuning against
172    /// real silicon.
173    pub fn on_port_with_timing(port: P, slot: Slot, timing: Timing) -> Self {
174        HardwareCard {
175            port,
176            slot,
177            timing,
178            trace: TransportTrace::new(),
179        }
180    }
181
182    /// Give the port back: the token when the card owned it, the borrow
183    /// otherwise.
184    pub fn release(self) -> P {
185        self.port
186    }
187
188    /// Diagnostic evidence from the most recent frame read or write.
189    pub fn last_trace(&self) -> TransportTrace {
190        self.trace
191    }
192
193    /// The port, between transactions, for another driver that shares it (a
194    /// pad poll). Every frame read or write finishes inside its own call, so
195    /// the card is never mid-transaction when this is reachable.
196    pub fn controller_port(&mut self) -> &mut ControllerPort {
197        self.port.borrow_mut()
198    }
199}
200
201/// One frame transaction's view of the port: the shared transport plus the
202/// card's trace of what happened on it.
203struct Link<'a, T> {
204    bus: &'a mut T,
205    slot: Slot,
206    timing: Timing,
207    trace: TransportTrace,
208}
209
210impl<T: Transport> Link<'_, T> {
211    fn select(&mut self) {
212        if !self.bus.begin(self.slot, self.timing.link()) {
213            // The previous transaction left `/ACK` asserted and it never
214            // released. Every exchange fails from here.
215            self.record_fault(TransportFault::AckReleaseTimeout, 0);
216        }
217    }
218
219    fn record_fault(&mut self, fault: TransportFault, exchange: u16) {
220        if self.trace.fault == TransportFault::None {
221            self.trace.fault = fault;
222            self.trace.fault_exchange = exchange;
223        }
224    }
225
226    fn note_reply(&mut self, exchange: u16, reply: u8) {
227        if (exchange as usize) < self.trace.response_prefix.len() {
228            self.trace.response_prefix[exchange as usize] = reply;
229        }
230        self.trace.exchanges = exchange + 1;
231    }
232
233    /// Clock one byte and, when `wait_ack`, wait for the card's `/ACK` pulse so
234    /// the next byte is not clocked early. Returns the received byte, or
235    /// `0xff` once the transaction has failed.
236    fn xfer(&mut self, tx: u8, wait_ack: bool) -> u8 {
237        if self.trace.fault != TransportFault::None {
238            return 0xff;
239        }
240        let exchange = self.trace.exchanges;
241        match self.bus.exchange(tx, !wait_ack, self.timing.link()) {
242            Ok(reply) => {
243                self.note_reply(exchange, reply);
244                if wait_ack {
245                    self.trace.acknowledgements += 1;
246                }
247                reply
248            }
249            Err(ExchangeError::TxTimeout) => {
250                self.record_fault(TransportFault::TxTimeout, exchange);
251                0xff
252            }
253            Err(ExchangeError::RxTimeout) => {
254                self.record_fault(TransportFault::RxTimeout, exchange);
255                0xff
256            }
257            Err(ExchangeError::AckTimeout { reply }) => {
258                self.note_reply(exchange, reply);
259                self.record_fault(TransportFault::AckTimeout, exchange);
260                reply
261            }
262            Err(ExchangeError::AckStuck { reply }) => {
263                self.note_reply(exchange, reply);
264                self.trace.acknowledgements += 1;
265                self.record_fault(TransportFault::AckReleaseTimeout, exchange);
266                reply
267            }
268        }
269    }
270
271    fn end(&mut self) {
272        let clean = self.trace.fault == TransportFault::None;
273        self.bus.finish(clean);
274    }
275
276    fn read_frame(&mut self, frame: u16, out: &mut [u8; FRAME_SIZE]) -> Result<()> {
277        let msb = (frame >> 8) as u8;
278        let lsb = frame as u8;
279
280        self.select();
281        self.xfer(CARD_SELECT, true); // open bus
282        self.bus.delay(FIRST_COMMAND_SETTLE_SPINS);
283        let flags = self.xfer(CMD_READ, true);
284        let id1 = self.xfer(0x00, true); // 0x5A
285        let _id2 = self.xfer(0x00, true); // 0x5D
286        let _ = self.xfer(msb, true); // 0x00
287        let _ = self.xfer(lsb, true); // MSB echo
288        let ack1 = self.xfer(0x00, true); // 0x5C
289        let _ack2 = self.xfer(0x00, true); // 0x5D
290        let emsb = self.xfer(0x00, true); // MSB
291        let elsb = self.xfer(0x00, true); // LSB
292        for b in out.iter_mut() {
293            *b = self.xfer(0x00, true);
294        }
295        let chk = self.xfer(0x00, true);
296        let end = self.xfer(0x00, false); // terminator, no ACK
297        self.end();
298
299        if id1 == 0xFF {
300            return Err(Error::NoCard);
301        }
302        // FLAG bit 2 reports an error from the previous write transaction.
303        if flags & 0x04 != 0
304            || id1 != ID1
305            || ack1 != ACK1
306            || emsb != msb
307            || elsb != lsb
308            || end != END_GOOD
309        {
310            return Err(Error::Protocol);
311        }
312        let mut want = msb ^ lsb;
313        for &b in out.iter() {
314            want ^= b;
315        }
316        if want != chk {
317            return Err(Error::BadChecksum);
318        }
319        Ok(())
320    }
321
322    fn write_frame(&mut self, frame: u16, data: &[u8; FRAME_SIZE]) -> Result<()> {
323        let msb = (frame >> 8) as u8;
324        let lsb = frame as u8;
325        let mut chk = msb ^ lsb;
326        for &b in data.iter() {
327            chk ^= b;
328        }
329
330        self.select();
331        self.xfer(CARD_SELECT, true); // flag
332        self.bus.delay(FIRST_COMMAND_SETTLE_SPINS);
333        self.xfer(CMD_WRITE, true);
334        let id1 = self.xfer(0x00, true); // 0x5A
335        let _id2 = self.xfer(0x00, true); // 0x5D
336        let _ = self.xfer(msb, true); // 0x00
337        let _ = self.xfer(lsb, true); // MSB echo
338        for &b in data.iter() {
339            self.xfer(b, true);
340        }
341        self.xfer(chk, true); // commit happens here (flash write)
342        let ack1 = self.xfer(0x00, true); // 0x5C
343        let _ack2 = self.xfer(0x00, true); // 0x5D
344        let end = self.xfer(0x00, false); // terminator
345        self.end();
346
347        // Leave the card idle while its non-volatile write finishes. Starting
348        // another transaction too early can produce a protocol-successful
349        // readback while still losing directory updates across a power cycle.
350        self.bus.delay(POST_WRITE_SETTLE_SPINS);
351
352        if id1 == 0xFF {
353            return Err(Error::NoCard);
354        }
355        if id1 != ID1 || ack1 != ACK1 {
356            return Err(Error::Protocol);
357        }
358        if end != END_GOOD {
359            // 0x4E = bad checksum / not written.
360            return Err(Error::Protocol);
361        }
362        Ok(())
363    }
364}
365
366/// Run one frame transaction on `bus`, leaving its trace in `trace`.
367fn transact<T: Transport, R>(
368    bus: &mut T,
369    slot: Slot,
370    timing: Timing,
371    trace: &mut TransportTrace,
372    run: impl FnOnce(&mut Link<'_, T>) -> R,
373) -> R {
374    let mut link = Link {
375        bus,
376        slot,
377        timing,
378        trace: TransportTrace::new(),
379    };
380    let result = run(&mut link);
381    *trace = link.trace;
382    result
383}
384
385fn check_range(frame: u16) -> Result<()> {
386    if (frame as usize) < FRAME_COUNT {
387        Ok(())
388    } else {
389        Err(Error::OutOfRange)
390    }
391}
392
393impl<P: BorrowMut<ControllerPort>> Block for HardwareCard<P> {
394    fn read_frame(&mut self, frame: u16, out: &mut [u8; FRAME_SIZE]) -> Result<()> {
395        check_range(frame)?;
396        let (slot, timing) = (self.slot, self.timing);
397        transact(
398            self.port.borrow_mut(),
399            slot,
400            timing,
401            &mut self.trace,
402            |link| link.read_frame(frame, out),
403        )
404    }
405
406    fn write_frame(&mut self, frame: u16, data: &[u8; FRAME_SIZE]) -> Result<()> {
407        check_range(frame)?;
408        let (slot, timing) = (self.slot, self.timing);
409        transact(
410            self.port.borrow_mut(),
411            slot,
412            timing,
413            &mut self.trace,
414            |link| link.write_frame(frame, data),
415        )
416    }
417}
418
419#[cfg(test)]
420mod tests;