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}