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    /// Command index is not supported on this transport.
100    UnsupportedCommand,
101    /// Bad response received during the wrapped phase.
102    BadResponse(ErrorContext),
103    /// Card returned an error in its R1 response.
104    CardError(CardError),
105    /// Write operation failed during the wrapped phase.
106    WriteError(ErrorContext),
107    /// Read operation failed during the wrapped phase.
108    ReadError(ErrorContext),
109    /// Misaligned address or length passed by the caller.
110    Misaligned,
111    /// Caller passed an invalid argument.
112    InvalidArgument,
113    /// Card is locked (host needs to unlock before further I/O).
114    CardLocked,
115    /// Generic communication error on the bus during the wrapped phase.
116    BusError(ErrorContext),
117}
118
119/// Per-bit error status decoded out of an R1 response.
120///
121/// SD Physical Layer spec section 4.10.1 reserves bits 19..=31 of the 32-bit
122/// native R1 response for card-state error flags. SPI R1 reuses bits 1..=6 of
123/// the single response byte for a subset of those. Variants below cover both,
124/// with [`CardError::Unknown`] preserving the raw native bit pattern when no
125/// known flag matches (e.g. reserved-for-application bits).
126///
127/// Marked `#[non_exhaustive]`: new card-status bits may be classified out of
128/// `Unknown(_)` over time, and downstream match sites must keep a `_ => ...`
129/// arm.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum CardError {
133    /// `OUT_OF_RANGE` (bit 31): the command's argument was out of the allowed
134    /// range for this card (e.g. LBA beyond capacity).
135    OutOfRange,
136    /// `ADDRESS_ERROR` (bit 30) / SPI `ADDRESS_ERROR` (bit 5): misaligned
137    /// address for the current block length, or out-of-range address.
138    AddressError,
139    /// `BLOCK_LEN_ERROR` (bit 29) / SPI `PARAMETER_ERROR` (bit 6): transferred
140    /// block length is not allowed for this card or the parameter argument
141    /// was out of range.
142    BlockLenError,
143    /// `ERASE_SEQ_ERROR` (bit 28) / SPI `ERASE_SEQ_ERROR` (bit 4): erase
144    /// command sequence error, or `ERASE_RESET` (SPI bit 1).
145    EraseSequence,
146    /// `ERASE_PARAM` (bit 27): an invalid selection of write blocks for erase.
147    EraseParam,
148    /// `WP_VIOLATION` (bit 26): attempted write to a write-protected block.
149    WriteProtect,
150    /// `CARD_IS_LOCKED` (bit 25): card is locked by host, normal data
151    /// transfers are inhibited.
152    CardIsLocked,
153    /// `LOCK_UNLOCK_FAILED` (bit 24): a sequence or password error in the
154    /// lock/unlock command.
155    LockUnlockFailed,
156    /// `COM_CRC_ERROR` (bit 23) / SPI `COM_CRC_ERROR` (bit 3): CRC check of
157    /// the previous command failed.
158    CommandCrcFailed,
159    /// `ILLEGAL_COMMAND` (bit 22) / SPI `ILLEGAL_COMMAND` (bit 2): command not
160    /// legal for the current card state.
161    IllegalCommand,
162    /// `CARD_ECC_FAILED` (bit 21): card internal ECC was applied but failed
163    /// to correct the data.
164    CardEccFailed,
165    /// `CC_ERROR` (bit 20): generic card controller error.
166    ControllerError,
167    /// `ERROR` (bit 19): a catch-all reported by the card when a non-classified
168    /// internal error occurred during the command execution.
169    GenericError,
170    /// Unknown / reserved error bit set. Carries the native 13-bit error
171    /// nibble (`raw >> 19`) so the caller can log the exact pattern.
172    Unknown(u32),
173}
174
175impl fmt::Display for Phase {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        let s = match self {
178            Self::Unspecified => "unspecified phase",
179            Self::Init => "init",
180            Self::CommandSend => "command send",
181            Self::ResponseWait => "response wait",
182            Self::DataWrite => "data write",
183            Self::DataRead => "data read",
184            Self::BusyWait => "busy wait",
185            Self::Switch => "switch",
186            Self::Erase => "erase",
187        };
188        f.write_str(s)
189    }
190}
191
192impl fmt::Display for ErrorContext {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        match self.cmd {
195            Some(cmd) => write!(f, "{} (CMD{cmd})", self.phase),
196            None => fmt::Display::fmt(&self.phase, f),
197        }
198    }
199}
200
201impl fmt::Display for CardError {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        let s = match self {
204            Self::OutOfRange => "out-of-range argument",
205            Self::AddressError => "misaligned address",
206            Self::BlockLenError => "invalid block length",
207            Self::EraseSequence => "erase sequence error",
208            Self::EraseParam => "invalid erase selection",
209            Self::WriteProtect => "write-protect violation",
210            Self::CardIsLocked => "card is locked",
211            Self::LockUnlockFailed => "lock/unlock command failed",
212            Self::CommandCrcFailed => "command CRC failed",
213            Self::IllegalCommand => "illegal command for current card state",
214            Self::CardEccFailed => "card internal ECC failed",
215            Self::ControllerError => "card controller error",
216            Self::GenericError => "generic card error",
217            Self::Unknown(bits) => return write!(f, "unknown card error bits {bits:#x}"),
218        };
219        f.write_str(s)
220    }
221}
222
223impl fmt::Display for Error {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        match self {
226            Self::Timeout(ctx) => write!(f, "timeout during {ctx}"),
227            Self::Crc(ctx) => write!(f, "CRC mismatch during {ctx}"),
228            Self::NoCard => f.write_str("no card present"),
229            Self::UnsupportedCommand => f.write_str("command not supported by transport"),
230            Self::BadResponse(ctx) => write!(f, "bad response during {ctx}"),
231            Self::CardError(err) => write!(f, "card reported {err}"),
232            Self::WriteError(ctx) => write!(f, "write failed during {ctx}"),
233            Self::ReadError(ctx) => write!(f, "read failed during {ctx}"),
234            Self::Misaligned => f.write_str("misaligned address or length"),
235            Self::InvalidArgument => f.write_str("invalid argument"),
236            Self::CardLocked => f.write_str("card is locked; unlock before further I/O"),
237            Self::BusError(ctx) => write!(f, "bus error during {ctx}"),
238        }
239    }
240}
241
242impl core::error::Error for Error {
243    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
244        match self {
245            Self::CardError(err) => Some(err),
246            _ => None,
247        }
248    }
249}
250
251impl core::error::Error for CardError {}
252
253#[cfg(test)]
254mod tests {
255    extern crate std;
256
257    use std::format;
258
259    use super::*;
260
261    #[test]
262    fn display_error_includes_phase_and_cmd() {
263        let err = Error::Timeout(ErrorContext::for_cmd(Phase::DataRead, 17));
264        assert_eq!(format!("{err}"), "timeout during data read (CMD17)");
265    }
266
267    #[test]
268    fn display_error_without_cmd_drops_parenthesis() {
269        let err = Error::BadResponse(ErrorContext::new(Phase::ResponseWait));
270        assert_eq!(format!("{err}"), "bad response during response wait");
271    }
272
273    #[test]
274    fn display_card_error_known_variant() {
275        let err = Error::CardError(CardError::OutOfRange);
276        assert_eq!(format!("{err}"), "card reported out-of-range argument");
277    }
278
279    #[test]
280    fn display_card_error_unknown_preserves_bits() {
281        let err = Error::CardError(CardError::Unknown(0x1234));
282        assert_eq!(
283            format!("{err}"),
284            "card reported unknown card error bits 0x1234"
285        );
286    }
287
288    #[test]
289    fn error_trait_source_threads_card_error_through() {
290        let err = Error::CardError(CardError::WriteProtect);
291        let src = core::error::Error::source(&err).expect("source should be CardError");
292        assert_eq!(format!("{src}"), "write-protect violation");
293    }
294
295    #[test]
296    fn error_trait_source_is_none_for_bus_errors() {
297        let err = Error::Crc(ErrorContext::for_cmd(Phase::DataRead, 18));
298        assert!(core::error::Error::source(&err).is_none());
299    }
300}