Skip to main content

sdmmc_protocol/
error.rs

1//! Error and diagnostic context types returned by drivers and parsers.
2//!
3//! See [`Phase`] and [`ErrorContext`] for the operational metadata that
4//! recoverable [`Error`] variants carry.
5//!
6//! All public error types implement [`core::fmt::Display`] for human-readable
7//! logging, and [`Error`] additionally implements [`core::error::Error`]
8//! (stabilized in `no_std` since Rust 1.81) so it composes with
9//! `?`-propagation chains and downstream error-handling utilities. The
10//! `Debug` impls are still derived for `{:?}` use inside the driver.
11
12use core::fmt;
13
14/// Where in the driver pipeline a fault was observed.
15///
16/// Attached to recoverable [`Error`] variants via [`ErrorContext`] so callers
17/// can distinguish e.g. a CMD0 send timeout from a `BusyWait` programming
18/// timeout without parsing log strings.
19///
20/// Marked `#[non_exhaustive]`: more phases (e.g. tuning, voltage switch) are
21/// expected to land before 1.0, and downstream `match` sites must keep a
22/// `_ => ...` arm to absorb them without recompiling.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24#[non_exhaustive]
25pub enum Phase {
26    /// Phase was not recorded.
27    ///
28    /// Used as a default placeholder; real driver paths should pick a
29    /// concrete variant.
30    #[default]
31    Unspecified,
32    /// Power-up / running CMD0 → ACMD41 / sending CMD2/3/9/7.
33    Init,
34    /// Putting the command bytes onto the bus.
35    CommandSend,
36    /// Waiting for the card's response token / R1–R7 payload.
37    ResponseWait,
38    /// Streaming a data block to the card (CMD24 / CMD25 etc).
39    DataWrite,
40    /// Streaming a data block from the card (CMD17 / CMD18 etc).
41    DataRead,
42    /// Polling the card's busy line / programming status.
43    BusyWait,
44    /// Switching bus speed, width or function (CMD6 / ACMD6).
45    Switch,
46    /// Erase sequence (CMD32 / CMD33 / CMD38).
47    Erase,
48}
49
50/// Operational context attached to recoverable bus / protocol errors.
51///
52/// Helps callers triage failures: which phase of the SD/MMC pipeline
53/// raised the error, and which CMD/ACMD index was being processed at
54/// the time, when known.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56pub struct ErrorContext {
57    /// Pipeline phase when the fault was raised.
58    pub phase: Phase,
59    /// CMD/ACMD index being processed, if applicable.
60    pub cmd: Option<u8>,
61}
62
63impl ErrorContext {
64    /// Build a context with only the phase populated.
65    #[inline]
66    pub const fn new(phase: Phase) -> Self {
67        Self { phase, cmd: None }
68    }
69
70    /// Build a context tied to a specific CMD/ACMD index.
71    #[inline]
72    pub const fn for_cmd(phase: Phase, cmd: u8) -> Self {
73        Self {
74            phase,
75            cmd: Some(cmd),
76        }
77    }
78}
79
80/// Errors returned by SD/MMC protocol parsers and drivers.
81///
82/// Recoverable bus / protocol variants carry an [`ErrorContext`] pinpointing
83/// the phase and (when known) command index that raised them. Caller-facing
84/// programming errors (`Misaligned`, `InvalidArgument`) and card-state
85/// reports (`NoCard`, `CardError`, `CardLocked`) do not.
86///
87/// Marked `#[non_exhaustive]`: more variants (e.g. `NoCardDetected`,
88/// `VoltageSwitchFailed`, retry-exhausted) are expected before 1.0. Match
89/// sites in downstream crates must keep a `_ => ...` arm.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[non_exhaustive]
92pub enum Error {
93    /// No response from card within the deadline for the wrapped phase.
94    Timeout(ErrorContext),
95    /// CRC check failed during the wrapped phase.
96    Crc(ErrorContext),
97    /// Card is not responding or not inserted.
98    NoCard,
99    /// Host/controller currently has another active request.
100    Busy,
101    /// Command index is not supported on this transport.
102    UnsupportedCommand,
103    /// Bad response received during the wrapped phase.
104    BadResponse(ErrorContext),
105    /// Card returned an error in its R1 response.
106    CardError(CardError),
107    /// Write operation failed during the wrapped phase.
108    WriteError(ErrorContext),
109    /// Read operation failed during the wrapped phase.
110    ReadError(ErrorContext),
111    /// Misaligned address or length passed by the caller.
112    Misaligned,
113    /// Caller passed an invalid argument.
114    InvalidArgument,
115    /// Card is locked (host needs to unlock before further I/O).
116    CardLocked,
117    /// Generic communication error on the bus during the wrapped phase.
118    BusError(ErrorContext),
119}
120
121/// Per-bit error status decoded out of an R1 response.
122///
123/// SD Physical Layer spec section 4.10.1 reserves bits 19..=31 of the 32-bit
124/// native R1 response for card-state error flags. SPI R1 reuses bits 1..=6 of
125/// the single response byte for a subset of those. Variants below cover both,
126/// with [`CardError::Unknown`] preserving the raw native bit pattern when no
127/// known flag matches (e.g. reserved-for-application bits).
128///
129/// Marked `#[non_exhaustive]`: new card-status bits may be classified out of
130/// `Unknown(_)` over time, and downstream match sites must keep a `_ => ...`
131/// arm.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133#[non_exhaustive]
134pub enum CardError {
135    /// `OUT_OF_RANGE` (bit 31): the command's argument was out of the allowed
136    /// range for this card (e.g. LBA beyond capacity).
137    OutOfRange,
138    /// `ADDRESS_ERROR` (bit 30) / SPI `ADDRESS_ERROR` (bit 5): misaligned
139    /// address for the current block length, or out-of-range address.
140    AddressError,
141    /// `BLOCK_LEN_ERROR` (bit 29) / SPI `PARAMETER_ERROR` (bit 6): transferred
142    /// block length is not allowed for this card or the parameter argument
143    /// was out of range.
144    BlockLenError,
145    /// `ERASE_SEQ_ERROR` (bit 28) / SPI `ERASE_SEQ_ERROR` (bit 4): erase
146    /// command sequence error, or `ERASE_RESET` (SPI bit 1).
147    EraseSequence,
148    /// `ERASE_PARAM` (bit 27): an invalid selection of write blocks for erase.
149    EraseParam,
150    /// `WP_VIOLATION` (bit 26): attempted write to a write-protected block.
151    WriteProtect,
152    /// `CARD_IS_LOCKED` (bit 25): card is locked by host, normal data
153    /// transfers are inhibited.
154    CardIsLocked,
155    /// `LOCK_UNLOCK_FAILED` (bit 24): a sequence or password error in the
156    /// lock/unlock command.
157    LockUnlockFailed,
158    /// `COM_CRC_ERROR` (bit 23) / SPI `COM_CRC_ERROR` (bit 3): CRC check of
159    /// the previous command failed.
160    CommandCrcFailed,
161    /// `ILLEGAL_COMMAND` (bit 22) / SPI `ILLEGAL_COMMAND` (bit 2): command not
162    /// legal for the current card state.
163    IllegalCommand,
164    /// `CARD_ECC_FAILED` (bit 21): card internal ECC was applied but failed
165    /// to correct the data.
166    CardEccFailed,
167    /// `CC_ERROR` (bit 20): generic card controller error.
168    ControllerError,
169    /// `ERROR` (bit 19): a catch-all reported by the card when a non-classified
170    /// internal error occurred during the command execution.
171    GenericError,
172    /// Unknown / reserved error bit set. Carries the native 13-bit error
173    /// nibble (`raw >> 19`) so the caller can log the exact pattern.
174    Unknown(u32),
175}
176
177impl fmt::Display for Phase {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        let s = match self {
180            Self::Unspecified => "unspecified phase",
181            Self::Init => "init",
182            Self::CommandSend => "command send",
183            Self::ResponseWait => "response wait",
184            Self::DataWrite => "data write",
185            Self::DataRead => "data read",
186            Self::BusyWait => "busy wait",
187            Self::Switch => "switch",
188            Self::Erase => "erase",
189        };
190        f.write_str(s)
191    }
192}
193
194impl fmt::Display for ErrorContext {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        match self.cmd {
197            Some(cmd) => write!(f, "{} (CMD{cmd})", self.phase),
198            None => fmt::Display::fmt(&self.phase, f),
199        }
200    }
201}
202
203impl fmt::Display for CardError {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        let s = match self {
206            Self::OutOfRange => "out-of-range argument",
207            Self::AddressError => "misaligned address",
208            Self::BlockLenError => "invalid block length",
209            Self::EraseSequence => "erase sequence error",
210            Self::EraseParam => "invalid erase selection",
211            Self::WriteProtect => "write-protect violation",
212            Self::CardIsLocked => "card is locked",
213            Self::LockUnlockFailed => "lock/unlock command failed",
214            Self::CommandCrcFailed => "command CRC failed",
215            Self::IllegalCommand => "illegal command for current card state",
216            Self::CardEccFailed => "card internal ECC failed",
217            Self::ControllerError => "card controller error",
218            Self::GenericError => "generic card error",
219            Self::Unknown(bits) => return write!(f, "unknown card error bits {bits:#x}"),
220        };
221        f.write_str(s)
222    }
223}
224
225impl fmt::Display for Error {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        match self {
228            Self::Timeout(ctx) => write!(f, "timeout during {ctx}"),
229            Self::Crc(ctx) => write!(f, "CRC mismatch during {ctx}"),
230            Self::NoCard => f.write_str("no card present"),
231            Self::Busy => f.write_str("host controller is busy"),
232            Self::UnsupportedCommand => f.write_str("command not supported by transport"),
233            Self::BadResponse(ctx) => write!(f, "bad response during {ctx}"),
234            Self::CardError(err) => write!(f, "card reported {err}"),
235            Self::WriteError(ctx) => write!(f, "write failed during {ctx}"),
236            Self::ReadError(ctx) => write!(f, "read failed during {ctx}"),
237            Self::Misaligned => f.write_str("misaligned address or length"),
238            Self::InvalidArgument => f.write_str("invalid argument"),
239            Self::CardLocked => f.write_str("card is locked; unlock before further I/O"),
240            Self::BusError(ctx) => write!(f, "bus error during {ctx}"),
241        }
242    }
243}
244
245impl core::error::Error for Error {
246    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
247        match self {
248            Self::CardError(err) => Some(err),
249            _ => None,
250        }
251    }
252}
253
254impl core::error::Error for CardError {}
255
256#[cfg(test)]
257mod tests {
258    extern crate std;
259
260    use std::format;
261
262    use super::*;
263
264    #[test]
265    fn display_error_includes_phase_and_cmd() {
266        let err = Error::Timeout(ErrorContext::for_cmd(Phase::DataRead, 17));
267        assert_eq!(format!("{err}"), "timeout during data read (CMD17)");
268    }
269
270    #[test]
271    fn display_error_without_cmd_drops_parenthesis() {
272        let err = Error::BadResponse(ErrorContext::new(Phase::ResponseWait));
273        assert_eq!(format!("{err}"), "bad response during response wait");
274    }
275
276    #[test]
277    fn display_card_error_known_variant() {
278        let err = Error::CardError(CardError::OutOfRange);
279        assert_eq!(format!("{err}"), "card reported out-of-range argument");
280    }
281
282    #[test]
283    fn display_card_error_unknown_preserves_bits() {
284        let err = Error::CardError(CardError::Unknown(0x1234));
285        assert_eq!(
286            format!("{err}"),
287            "card reported unknown card error bits 0x1234"
288        );
289    }
290
291    #[test]
292    fn error_trait_source_threads_card_error_through() {
293        let err = Error::CardError(CardError::WriteProtect);
294        let src = core::error::Error::source(&err).expect("source should be CardError");
295        assert_eq!(format!("{src}"), "write-protect violation");
296    }
297
298    #[test]
299    fn error_trait_source_is_none_for_bus_errors() {
300        let err = Error::Crc(ErrorContext::for_cmd(Phase::DataRead, 18));
301        assert!(core::error::Error::source(&err).is_none());
302    }
303}