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}