Skip to main content

sdmmc_protocol/
ext_csd.rs

1//! Extended CSD (EXT_CSD) register parsing for eMMC / MMC cards.
2//!
3//! EXT_CSD is a 512-byte register read with `CMD8` (`SEND_EXT_CSD`,
4//! `R1` + 512-byte data block) on MMC cards. Only a small subset of
5//! fields is consumed by this driver today; the parser exposes the
6//! ones that drive bring-up decisions (capacity, supported timing,
7//! current bus width).
8
9use crate::cmd::ext_csd;
10
11/// Lightly typed view over the 512-byte EXT_CSD payload.
12///
13/// Holding the full register lets later phases of the driver read
14/// fields that aren't needed today (e.g. `BOOT_PARTITION_SIZE`,
15/// `RPMB_SIZE_MULT`, `PARTITION_SETTING_COMPLETED`) without having to
16/// re-issue CMD8.
17#[derive(Debug, Clone)]
18pub struct ExtCsd {
19    raw: [u8; 512],
20}
21
22/// `DEVICE_TYPE` (EXT_CSD[196]) decoded into supported high-speed modes.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct DeviceType {
25    pub raw: u8,
26}
27
28/// Currently selected MMC bus width, decoded from `BUS_WIDTH` (EXT_CSD[183]).
29///
30/// Marked `#[non_exhaustive]`: HS400 / strobe encodings may be classified out
31/// of `Unknown(_)` over time.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum MmcBusWidth {
35    /// 1-bit SDR.
36    Sdr1,
37    /// 4-bit SDR.
38    Sdr4,
39    /// 8-bit SDR.
40    Sdr8,
41    /// 4-bit DDR.
42    Ddr4,
43    /// 8-bit DDR.
44    Ddr8,
45    /// Reserved / unknown encoding — caller should treat as 1-bit.
46    Unknown(u8),
47}
48
49/// Currently selected MMC timing mode, decoded from `HS_TIMING` (EXT_CSD[185]).
50///
51/// Marked `#[non_exhaustive]`: HS400_ES and future timing modes may be
52/// classified out of `Unknown(_)` over time.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54#[non_exhaustive]
55pub enum MmcTiming {
56    /// Backwards-compatible (≤ 26 MHz).
57    Compat,
58    /// High-Speed SDR (≤ 52 MHz).
59    HighSpeed,
60    /// HS200 (≤ 200 MHz, 1.8 V or 1.2 V).
61    Hs200,
62    /// HS400 (≤ 200 MHz DDR, requires HS200 tuning first).
63    Hs400,
64    /// Reserved / unknown encoding.
65    Unknown(u8),
66}
67
68impl ExtCsd {
69    pub fn from_bytes(raw: [u8; 512]) -> Self {
70        Self { raw }
71    }
72
73    /// Raw 512-byte payload (immutable view) for fields the typed API
74    /// hasn't grown to cover yet.
75    pub fn as_bytes(&self) -> &[u8; 512] {
76        &self.raw
77    }
78
79    /// Authoritative sector count for cards ≥ 2 GB. Returns `None` when
80    /// the field is zero, which means "use the legacy CSD `C_SIZE`
81    /// instead" (small cards).
82    pub fn sector_count(&self) -> Option<u32> {
83        let s = ext_csd::SEC_COUNT;
84        let v = u32::from_le_bytes([
85            self.raw[s],
86            self.raw[s + 1],
87            self.raw[s + 2],
88            self.raw[s + 3],
89        ]);
90        if v == 0 { None } else { Some(v) }
91    }
92
93    pub fn device_type(&self) -> DeviceType {
94        DeviceType {
95            raw: self.raw[ext_csd::DEVICE_TYPE],
96        }
97    }
98
99    pub fn bus_width(&self) -> MmcBusWidth {
100        match self.raw[ext_csd::BUS_WIDTH] {
101            0 => MmcBusWidth::Sdr1,
102            1 => MmcBusWidth::Sdr4,
103            2 => MmcBusWidth::Sdr8,
104            5 => MmcBusWidth::Ddr4,
105            6 => MmcBusWidth::Ddr8,
106            other => MmcBusWidth::Unknown(other),
107        }
108    }
109
110    pub fn timing(&self) -> MmcTiming {
111        match self.raw[ext_csd::HS_TIMING] & 0x0F {
112            0 => MmcTiming::Compat,
113            1 => MmcTiming::HighSpeed,
114            2 => MmcTiming::Hs200,
115            3 => MmcTiming::Hs400,
116            other => MmcTiming::Unknown(other),
117        }
118    }
119}
120
121impl DeviceType {
122    pub fn supports_hs_52(&self) -> bool {
123        self.raw & ext_csd::device_type::HS_52 != 0
124    }
125    pub fn supports_hs_26(&self) -> bool {
126        self.raw & ext_csd::device_type::HS_26 != 0
127    }
128    pub fn supports_hs200_18v(&self) -> bool {
129        self.raw & ext_csd::device_type::HS200_18V != 0
130    }
131    pub fn supports_hs200_12v(&self) -> bool {
132        self.raw & ext_csd::device_type::HS200_12V != 0
133    }
134    pub fn supports_hs200(&self) -> bool {
135        self.supports_hs200_18v() || self.supports_hs200_12v()
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    fn ext_csd_with(field: usize, val: &[u8]) -> ExtCsd {
144        let mut raw = [0u8; 512];
145        raw[field..field + val.len()].copy_from_slice(val);
146        ExtCsd::from_bytes(raw)
147    }
148
149    #[test]
150    fn sector_count_from_ext_csd() {
151        // 0x0080_0000 sectors = 4 GiB
152        let e = ext_csd_with(ext_csd::SEC_COUNT, &[0x00, 0x00, 0x80, 0x00]);
153        assert_eq!(e.sector_count(), Some(0x0080_0000));
154    }
155
156    #[test]
157    fn sector_count_zero_means_use_csd() {
158        let e = ExtCsd::from_bytes([0u8; 512]);
159        assert_eq!(e.sector_count(), None);
160    }
161
162    #[test]
163    fn device_type_decodes_known_bits() {
164        let e = ext_csd_with(ext_csd::DEVICE_TYPE, &[0b0011_0011]);
165        let dt = e.device_type();
166        assert!(dt.supports_hs_26());
167        assert!(dt.supports_hs_52());
168        assert!(dt.supports_hs200_18v());
169        assert!(dt.supports_hs200_12v());
170        assert!(dt.supports_hs200());
171    }
172
173    #[test]
174    fn bus_width_and_timing_round_trip() {
175        let mut raw = [0u8; 512];
176        raw[ext_csd::BUS_WIDTH] = 2; // 8-bit SDR
177        raw[ext_csd::HS_TIMING] = 2; // HS200
178        let e = ExtCsd::from_bytes(raw);
179        assert_eq!(e.bus_width(), MmcBusWidth::Sdr8);
180        assert_eq!(e.timing(), MmcTiming::Hs200);
181    }
182}