use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Phase {
#[default]
Unspecified,
Init,
CommandSend,
ResponseWait,
DataWrite,
DataRead,
BusyWait,
Switch,
Erase,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ErrorContext {
pub phase: Phase,
pub cmd: Option<u8>,
}
impl ErrorContext {
#[inline]
pub const fn new(phase: Phase) -> Self {
Self { phase, cmd: None }
}
#[inline]
pub const fn for_cmd(phase: Phase, cmd: u8) -> Self {
Self {
phase,
cmd: Some(cmd),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Timeout(ErrorContext),
Crc(ErrorContext),
NoCard,
UnsupportedCommand,
BadResponse(ErrorContext),
CardError(CardError),
WriteError(ErrorContext),
ReadError(ErrorContext),
Misaligned,
InvalidArgument,
CardLocked,
BusError(ErrorContext),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CardError {
OutOfRange,
AddressError,
BlockLenError,
EraseSequence,
EraseParam,
WriteProtect,
CardIsLocked,
LockUnlockFailed,
CommandCrcFailed,
IllegalCommand,
CardEccFailed,
ControllerError,
GenericError,
Unknown(u32),
}
impl fmt::Display for Phase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Unspecified => "unspecified phase",
Self::Init => "init",
Self::CommandSend => "command send",
Self::ResponseWait => "response wait",
Self::DataWrite => "data write",
Self::DataRead => "data read",
Self::BusyWait => "busy wait",
Self::Switch => "switch",
Self::Erase => "erase",
};
f.write_str(s)
}
}
impl fmt::Display for ErrorContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.cmd {
Some(cmd) => write!(f, "{} (CMD{cmd})", self.phase),
None => fmt::Display::fmt(&self.phase, f),
}
}
}
impl fmt::Display for CardError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::OutOfRange => "out-of-range argument",
Self::AddressError => "misaligned address",
Self::BlockLenError => "invalid block length",
Self::EraseSequence => "erase sequence error",
Self::EraseParam => "invalid erase selection",
Self::WriteProtect => "write-protect violation",
Self::CardIsLocked => "card is locked",
Self::LockUnlockFailed => "lock/unlock command failed",
Self::CommandCrcFailed => "command CRC failed",
Self::IllegalCommand => "illegal command for current card state",
Self::CardEccFailed => "card internal ECC failed",
Self::ControllerError => "card controller error",
Self::GenericError => "generic card error",
Self::Unknown(bits) => return write!(f, "unknown card error bits {bits:#x}"),
};
f.write_str(s)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Timeout(ctx) => write!(f, "timeout during {ctx}"),
Self::Crc(ctx) => write!(f, "CRC mismatch during {ctx}"),
Self::NoCard => f.write_str("no card present"),
Self::UnsupportedCommand => f.write_str("command not supported by transport"),
Self::BadResponse(ctx) => write!(f, "bad response during {ctx}"),
Self::CardError(err) => write!(f, "card reported {err}"),
Self::WriteError(ctx) => write!(f, "write failed during {ctx}"),
Self::ReadError(ctx) => write!(f, "read failed during {ctx}"),
Self::Misaligned => f.write_str("misaligned address or length"),
Self::InvalidArgument => f.write_str("invalid argument"),
Self::CardLocked => f.write_str("card is locked; unlock before further I/O"),
Self::BusError(ctx) => write!(f, "bus error during {ctx}"),
}
}
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::CardError(err) => Some(err),
_ => None,
}
}
}
impl core::error::Error for CardError {}
#[cfg(test)]
mod tests {
extern crate std;
use std::format;
use super::*;
#[test]
fn display_error_includes_phase_and_cmd() {
let err = Error::Timeout(ErrorContext::for_cmd(Phase::DataRead, 17));
assert_eq!(format!("{err}"), "timeout during data read (CMD17)");
}
#[test]
fn display_error_without_cmd_drops_parenthesis() {
let err = Error::BadResponse(ErrorContext::new(Phase::ResponseWait));
assert_eq!(format!("{err}"), "bad response during response wait");
}
#[test]
fn display_card_error_known_variant() {
let err = Error::CardError(CardError::OutOfRange);
assert_eq!(format!("{err}"), "card reported out-of-range argument");
}
#[test]
fn display_card_error_unknown_preserves_bits() {
let err = Error::CardError(CardError::Unknown(0x1234));
assert_eq!(
format!("{err}"),
"card reported unknown card error bits 0x1234"
);
}
#[test]
fn error_trait_source_threads_card_error_through() {
let err = Error::CardError(CardError::WriteProtect);
let src = core::error::Error::source(&err).expect("source should be CardError");
assert_eq!(format!("{src}"), "write-protect violation");
}
#[test]
fn error_trait_source_is_none_for_bus_errors() {
let err = Error::Crc(ErrorContext::for_cmd(Phase::DataRead, 18));
assert!(core::error::Error::source(&err).is_none());
}
}