Skip to main content

sdmmc_protocol/
spi.rs

1//! SPI mode transport layer for SD/MMC cards
2//!
3//! Usage: implement [`SpiTransport`] for your platform's SPI peripheral,
4//! then use [`SpiSdmmc`] to interact with the card. A [`DelayNs`]
5//! implementation is also required so the driver can apply wall-clock
6//! timeouts to busy/wait loops.
7
8use embedded_hal::{delay::DelayNs, spi::SpiDevice};
9use log::{debug, info, warn};
10
11use crate::{
12    cmd::Command,
13    common::{block_addr_of, crc16_ccitt},
14    error::{Error, ErrorContext, Phase},
15    response::{
16        CidResponse, CsdResponse, IfCondResponse, OcrResponse, R1Response, Response, ResponseType,
17        SwitchStatus,
18    },
19};
20
21/// Token markers for SPI mode data transfer
22const TOKEN_START_BLOCK: u8 = 0xFE;
23const TOKEN_START_MULTI_BLOCK: u8 = 0xFC;
24const TOKEN_STOP_TRAN: u8 = 0xFD;
25
26/// SPI transport trait — users implement this for their platform
27pub trait SpiTransport {
28    /// Assert chip select before a command/data transaction.
29    fn select(&mut self) -> Result<(), Error> {
30        Ok(())
31    }
32
33    /// Deassert chip select after a command/data transaction.
34    fn deselect(&mut self) -> Result<(), Error> {
35        Ok(())
36    }
37
38    /// Send and receive a single byte
39    fn transfer_byte(&mut self, byte: u8) -> Result<u8, Error>;
40    /// Send a byte (ignore response)
41    fn send_byte(&mut self, byte: u8) -> Result<(), Error> {
42        self.transfer_byte(byte)?;
43        Ok(())
44    }
45    /// Send 8 clock cycles (write 0xFF)
46    fn clock(&mut self) -> Result<(), Error> {
47        self.transfer_byte(0xFF)?;
48        Ok(())
49    }
50}
51
52/// Blanket impl for embedded-hal v1 `SpiDevice<u8>`
53impl<SPI> SpiTransport for SpiDeviceWrapper<SPI>
54where
55    SPI: SpiDevice<u8>,
56{
57    fn transfer_byte(&mut self, byte: u8) -> Result<u8, Error> {
58        let mut buf = [byte];
59        self.spi
60            .transfer(&mut buf, &[byte])
61            .map_err(|_| Error::BusError(ErrorContext::new(Phase::Unspecified)))?;
62        Ok(buf[0])
63    }
64}
65
66/// Wrapper that owns an `SpiDevice`
67pub struct SpiDeviceWrapper<SPI> {
68    spi: SPI,
69}
70
71impl<SPI> SpiDeviceWrapper<SPI> {
72    pub fn new(spi: SPI) -> Self {
73        Self { spi }
74    }
75}
76
77/// SPI mode SD/MMC driver
78pub struct SpiSdmmc<T: SpiTransport, D: DelayNs> {
79    transport: T,
80    delay: D,
81    sd_v2: bool,
82    high_capacity: bool,
83    verify_data_crc: bool,
84}
85
86impl<T: SpiTransport, D: DelayNs> SpiSdmmc<T, D> {
87    /// Maximum time to wait for an R1 byte after sending a command.
88    const RESPONSE_TIMEOUT_US: u32 = 100_000;
89    /// Maximum time to wait for a data start token.
90    const READ_TIMEOUT_US: u32 = 100_000;
91    /// Maximum time to wait for the card to leave busy state after a write.
92    /// SD spec gives ~250 ms as the worst-case write-busy time.
93    const WRITE_BUSY_TIMEOUT_US: u32 = 250_000;
94    /// Maximum time to wait for ACMD41 to clear the idle bit.
95    const INIT_TIMEOUT_US: u32 = 1_000_000;
96    /// How often we sleep between polls.
97    const POLL_INTERVAL_US: u32 = 50;
98
99    pub fn new(transport: T, delay: D) -> Self {
100        Self {
101            transport,
102            delay,
103            sd_v2: false,
104            high_capacity: false,
105            verify_data_crc: true,
106        }
107    }
108
109    /// Enable or disable verification of the CRC16 trailer that follows
110    /// every data block read from the card.
111    ///
112    /// SPI mode generates CRC16 bytes on transmit and the card emits them on
113    /// receive, but the SD spec allows the host to ignore them. Verification
114    /// is on by default; disable it only if you know your bus is reliable
115    /// and want to skip the per-block computation.
116    pub fn set_verify_data_crc(&mut self, on: bool) {
117        self.verify_data_crc = on;
118    }
119
120    // ── Initialization ──────────────────────────────────────────
121
122    /// Initialize the card. Must be called before any other operation.
123    ///
124    /// Performs the standard SD card initialization sequence:
125    /// 1. Send 80+ clock cycles (CMD0 preamble)
126    /// 2. CMD0 → idle
127    /// 3. CMD8 → detect SD v2
128    /// 4. ACMD41 → wait for card ready
129    /// 5. CMD58 → determine capacity type (SDHC vs SDSC)
130    pub fn init(&mut self) -> Result<CardInfo, Error> {
131        debug!("spi: init starting");
132        for _ in 0..10 {
133            self.transport.clock()?;
134        }
135
136        self.send_command(&crate::cmd::CMD0)?;
137        self.sd_v2 = self.check_cmd8()?;
138        debug!("spi: sd_v2={}", self.sd_v2);
139        self.wait_ready()?;
140
141        let ocr = self.read_ocr()?;
142        self.high_capacity = ocr.ccs() || self.sd_v2;
143        let csd = self.read_csd()?;
144        let capacity_blocks = csd.capacity_blocks();
145        let cid = self.read_cid().ok();
146        if !self.high_capacity {
147            self.send_command(&crate::cmd::cmd16(512))?;
148        }
149
150        info!(
151            "spi: init done sd_v2={} high_capacity={} ocr={:#x}",
152            self.sd_v2, self.high_capacity, ocr.raw
153        );
154        Ok(CardInfo {
155            sd_v2: self.sd_v2,
156            high_capacity: self.high_capacity,
157            ocr: ocr.raw,
158            capacity_blocks,
159            cid,
160        })
161    }
162
163    fn check_cmd8(&mut self) -> Result<bool, Error> {
164        let cmd = crate::cmd::cmd8(0x01, 0xAA);
165        match self.send_command_raw(&cmd) {
166            Ok(Response::R7(resp)) => Ok(resp.verify(0x01, 0xAA)),
167            Ok(Response::R1(resp)) if resp.illegal_command() => Ok(false),
168            Ok(_) => Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 8))),
169            Err(Error::Timeout(_)) => Ok(false),
170            Err(e) => Err(e),
171        }
172    }
173
174    fn wait_ready(&mut self) -> Result<(), Error> {
175        let mut elapsed = 0u32;
176        loop {
177            let cmd55 = crate::cmd::cmd55(0);
178            self.send_command(&cmd55)?;
179
180            // ACMD41 returns R3 (R1 + OCR) in native mode but a single-byte
181            // R1 in SPI mode. Override the response type for this transport.
182            let acmd41 = crate::cmd::cmd41(self.sd_v2, 0xFF8000).with_resp_type(ResponseType::R1);
183            match self.send_command_raw(&acmd41)? {
184                Response::R1(r1) => {
185                    if !r1.idle() {
186                        return Ok(());
187                    }
188                }
189                _ => return Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 41))),
190            }
191
192            if elapsed >= Self::INIT_TIMEOUT_US {
193                warn!("spi: ACMD41 timed out after {}us", elapsed);
194                return Err(Error::Timeout(ErrorContext::for_cmd(Phase::Init, 41)));
195            }
196            self.delay.delay_us(1_000);
197            elapsed = elapsed.saturating_add(1_000);
198        }
199    }
200
201    fn read_ocr(&mut self) -> Result<OcrResponse, Error> {
202        match self.send_command_raw(&crate::cmd::CMD58)? {
203            Response::R3(ocr) => Ok(ocr),
204            _ => Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 58))),
205        }
206    }
207
208    fn read_csd(&mut self) -> Result<CsdResponse, Error> {
209        match self.send_command_raw(&crate::cmd::cmd9(0))? {
210            Response::R2(raw) => Ok(CsdResponse::from_raw(raw)),
211            _ => Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 9))),
212        }
213    }
214
215    fn read_cid(&mut self) -> Result<CidResponse, Error> {
216        match self.send_command_raw(&crate::cmd::cmd10(0))? {
217            Response::R2(raw) => Ok(CidResponse::from_raw(raw)),
218            _ => Err(Error::BadResponse(ErrorContext::for_cmd(Phase::Init, 10))),
219        }
220    }
221
222    // ── Data Transfer ───────────────────────────────────────────
223
224    /// Read a single 512-byte block at the given address
225    pub fn read_block(&mut self, addr: u32, buf: &mut [u8; 512]) -> Result<(), Error> {
226        let block_addr = block_addr_of(addr, self.high_capacity);
227        let cmd = crate::cmd::cmd17(block_addr);
228        self.send_command(&cmd)?;
229        self.read_data_block(buf)
230    }
231
232    /// Write a single 512-byte block at the given address
233    pub fn write_block(&mut self, addr: u32, buf: &[u8; 512]) -> Result<(), Error> {
234        let block_addr = block_addr_of(addr, self.high_capacity);
235        let cmd = crate::cmd::cmd24(block_addr);
236        self.send_command(&cmd)?;
237        self.write_data_block(buf)
238    }
239
240    /// Read multiple blocks starting at `addr`
241    pub fn read_blocks<F>(&mut self, addr: u32, count: u32, mut handler: F) -> Result<(), Error>
242    where
243        F: FnMut(u32, &[u8; 512]),
244    {
245        let block_addr = block_addr_of(addr, self.high_capacity);
246        let cmd = crate::cmd::cmd18(block_addr);
247        self.send_command(&cmd)?;
248
249        let mut buf = [0u8; 512];
250        for i in 0..count {
251            self.read_data_block(&mut buf)?;
252            handler(addr + i, &buf);
253        }
254
255        self.send_command(&crate::cmd::CMD12)?;
256        self.wait_not_busy()?;
257        self.transport.deselect()?;
258        Ok(())
259    }
260
261    /// Write multiple blocks starting at `addr`
262    pub fn write_blocks(&mut self, addr: u32, blocks: &[[u8; 512]]) -> Result<(), Error> {
263        let block_addr = block_addr_of(addr, self.high_capacity);
264        let cmd = crate::cmd::cmd25(block_addr);
265        self.send_command(&cmd)?;
266
267        for block in blocks {
268            self.transport.send_byte(TOKEN_START_MULTI_BLOCK)?;
269            for &b in block {
270                self.transport.send_byte(b)?;
271            }
272            let crc = crc16_ccitt(block).to_be_bytes();
273            self.transport.send_byte(crc[0])?;
274            self.transport.send_byte(crc[1])?;
275
276            let resp = self.wait_for_response(Self::RESPONSE_TIMEOUT_US)?;
277            if (resp & 0x1F) != 0x05 {
278                return Err(Error::WriteError(ErrorContext::for_cmd(
279                    Phase::DataWrite,
280                    25,
281                )));
282            }
283            self.wait_not_busy()?;
284        }
285
286        self.transport.send_byte(TOKEN_STOP_TRAN)?;
287        self.transport.clock()?;
288        self.wait_not_busy()?;
289        self.transport.deselect()?;
290        Ok(())
291    }
292
293    // ── Low-level helpers ───────────────────────────────────────
294
295    fn send_command(&mut self, cmd: &Command) -> Result<R1Response, Error> {
296        let resp = self.send_command_raw(cmd)?;
297        match resp {
298            Response::R1(r1) | Response::R1b(r1) => Ok(r1),
299            _ => Err(Error::BadResponse(ErrorContext::for_cmd(
300                Phase::ResponseWait,
301                cmd.cmd,
302            ))),
303        }
304    }
305
306    fn send_command_raw(&mut self, cmd: &Command) -> Result<Response, Error> {
307        self.transport.select()?;
308        let bytes = cmd.to_spi_bytes();
309        for &b in &bytes {
310            self.transport.send_byte(b)?;
311        }
312
313        let response = match cmd.resp_type {
314            ResponseType::None => {
315                let r1 = self.read_r1()?;
316                Ok(Response::R1(r1))
317            }
318            ResponseType::R1 => {
319                let r1 = self.read_r1()?;
320                Ok(Response::R1(r1))
321            }
322            ResponseType::R1b => {
323                let r1 = self.read_r1()?;
324                self.wait_not_busy()?;
325                Ok(Response::R1b(r1))
326            }
327            ResponseType::R3 => {
328                self.read_r1()?;
329                let mut ocr = [0u8; 4];
330                for b in &mut ocr {
331                    *b = self.transport.transfer_byte(0xFF)?;
332                }
333                let raw = u32::from_be_bytes(ocr);
334                Ok(Response::R3(OcrResponse::from_raw(raw)))
335            }
336            ResponseType::R7 => {
337                let r1 = self.read_r1()?;
338                if r1.illegal_command() {
339                    return Ok(Response::R1(r1));
340                }
341                let mut data = [0u8; 4];
342                for b in &mut data {
343                    *b = self.transport.transfer_byte(0xFF)?;
344                }
345                let raw = u32::from_be_bytes(data);
346                Ok(Response::R7(IfCondResponse::from_raw(raw)))
347            }
348            ResponseType::R2 => {
349                let r1 = self.read_r1()?;
350                if r1.raw != 0 {
351                    return Ok(Response::R1(r1));
352                }
353                let mut buf = [0u8; 16];
354                self.read_data_register(&mut buf)?;
355                Ok(Response::R2(buf))
356            }
357            _ => {
358                let r1 = self.read_r1()?;
359                Ok(Response::R1(r1))
360            }
361        };
362        // Hold CS asserted across the data phase that follows the command.
363        if cmd.data_direction().is_none() {
364            self.transport.deselect()?;
365        }
366        response
367    }
368
369    fn read_r1(&mut self) -> Result<R1Response, Error> {
370        let raw = self.wait_for_response(Self::RESPONSE_TIMEOUT_US)?;
371        R1Response::from_spi_byte(raw)
372    }
373
374    /// Poll the bus for the first non-`0xFF` byte, up to `timeout_us`.
375    fn wait_for_response(&mut self, timeout_us: u32) -> Result<u8, Error> {
376        let mut elapsed = 0u32;
377        loop {
378            let b = self.transport.transfer_byte(0xFF)?;
379            if b != 0xFF {
380                return Ok(b);
381            }
382            if elapsed >= timeout_us {
383                return Err(Error::Timeout(ErrorContext::new(Phase::ResponseWait)));
384            }
385            self.delay.delay_us(Self::POLL_INTERVAL_US);
386            elapsed = elapsed.saturating_add(Self::POLL_INTERVAL_US);
387        }
388    }
389
390    /// Wait for the data start token (0xFE) and read `buf.len()` payload bytes.
391    fn read_data_into(&mut self, buf: &mut [u8]) -> Result<(), Error> {
392        let mut elapsed = 0u32;
393        loop {
394            let b = self.transport.transfer_byte(0xFF)?;
395            if b == TOKEN_START_BLOCK {
396                break;
397            }
398            if b != 0xFF {
399                return Err(Error::ReadError(ErrorContext::new(Phase::DataRead)));
400            }
401            if elapsed >= Self::READ_TIMEOUT_US {
402                return Err(Error::Timeout(ErrorContext::new(Phase::DataRead)));
403            }
404            self.delay.delay_us(Self::POLL_INTERVAL_US);
405            elapsed = elapsed.saturating_add(Self::POLL_INTERVAL_US);
406        }
407
408        for b in buf.iter_mut() {
409            *b = self.transport.transfer_byte(0xFF)?;
410        }
411
412        let crc_high = self.transport.transfer_byte(0xFF)?;
413        let crc_low = self.transport.transfer_byte(0xFF)?;
414        if self.verify_data_crc {
415            let received = u16::from_be_bytes([crc_high, crc_low]);
416            let computed = crc16_ccitt(buf);
417            if received != computed {
418                warn!(
419                    "spi: data CRC mismatch (received={:#x} computed={:#x})",
420                    received, computed
421                );
422                return Err(Error::Crc(ErrorContext::new(Phase::DataRead)));
423            }
424        }
425        Ok(())
426    }
427
428    fn read_data_register(&mut self, buf: &mut [u8; 16]) -> Result<(), Error> {
429        self.read_data_into(buf)
430    }
431
432    fn read_data_block(&mut self, buf: &mut [u8; 512]) -> Result<(), Error> {
433        self.read_data_into(buf)?;
434        self.transport.deselect()?;
435        Ok(())
436    }
437
438    fn write_data_block(&mut self, buf: &[u8; 512]) -> Result<(), Error> {
439        self.transport.send_byte(TOKEN_START_BLOCK)?;
440        for &b in buf {
441            self.transport.send_byte(b)?;
442        }
443        let crc = crc16_ccitt(buf).to_be_bytes();
444        self.transport.send_byte(crc[0])?;
445        self.transport.send_byte(crc[1])?;
446
447        let resp = self.wait_for_response(Self::RESPONSE_TIMEOUT_US)?;
448        if (resp & 0x1F) != 0x05 {
449            return Err(Error::WriteError(ErrorContext::new(Phase::DataWrite)));
450        }
451
452        self.wait_not_busy()?;
453        self.transport.deselect()?;
454        Ok(())
455    }
456
457    fn wait_not_busy(&mut self) -> Result<(), Error> {
458        let mut elapsed = 0u32;
459        loop {
460            if self.transport.transfer_byte(0xFF)? == 0xFF {
461                return Ok(());
462            }
463            if elapsed >= Self::WRITE_BUSY_TIMEOUT_US {
464                return Err(Error::Timeout(ErrorContext::new(Phase::BusyWait)));
465            }
466            self.delay.delay_us(Self::POLL_INTERVAL_US);
467            elapsed = elapsed.saturating_add(Self::POLL_INTERVAL_US);
468        }
469    }
470
471    /// Issue a CMD6 SWITCH_FUNC and read back the 64-byte status block.
472    pub fn switch_function(&mut self, cmd: &Command) -> Result<SwitchStatus, Error> {
473        // CMD6 has no inherent data direction (ACMD6 vs SWITCH_FUNC overlap),
474        // so we drive the bus manually here to keep CS asserted across the
475        // R1 byte and the 64-byte data phase that follows.
476        self.transport.select()?;
477        let bytes = cmd.to_spi_bytes();
478        for &b in &bytes {
479            self.transport.send_byte(b)?;
480        }
481        let _r1 = self.read_r1()?;
482
483        let mut buf = [0u8; 64];
484        self.read_data_into(&mut buf)?;
485        self.transport.deselect()?;
486        Ok(SwitchStatus::from_raw(buf))
487    }
488
489    /// Switch the card to high speed (50 MHz) by sending CMD6 with mode=1
490    /// and group 1 = 1. Returns `Ok(true)` if the status block confirms
491    /// high-speed selected; `Ok(false)` otherwise.
492    ///
493    /// The host is responsible for actually raising the SPI clock after this
494    /// returns success.
495    pub fn switch_to_high_speed(&mut self) -> Result<bool, Error> {
496        let status = self.switch_function(&crate::cmd::cmd6_high_speed(true))?;
497        let active = status.high_speed_active();
498        if active {
499            info!("spi: switched to high-speed mode");
500        } else {
501            warn!("spi: high-speed switch did not take effect");
502        }
503        Ok(active)
504    }
505}
506
507/// Card information obtained during initialization
508#[derive(Debug, Clone, Copy)]
509pub struct CardInfo {
510    pub sd_v2: bool,
511    pub high_capacity: bool,
512    pub ocr: u32,
513    /// User-data capacity in 512-byte blocks, parsed from the CSD.
514    /// `None` if the CSD reports a structure version we do not yet support.
515    pub capacity_blocks: Option<u64>,
516    /// Card identification register, parsed via CMD10. `None` if the card
517    /// did not return a valid R2 response.
518    pub cid: Option<CidResponse>,
519}
520
521#[cfg(test)]
522mod tests {
523    extern crate std;
524
525    use std::vec::Vec;
526
527    use super::*;
528    use crate::cmd;
529
530    /// `DelayNs` that does nothing — fine for unit tests against a scripted
531    /// transport because no real time is being measured.
532    struct NullDelay;
533
534    impl DelayNs for NullDelay {
535        fn delay_ns(&mut self, _ns: u32) {}
536    }
537
538    struct ScriptedTransport {
539        rx: Vec<u8>,
540        tx: Vec<u8>,
541    }
542
543    impl ScriptedTransport {
544        fn new(rx: Vec<u8>) -> Self {
545            Self { rx, tx: Vec::new() }
546        }
547
548        fn push_ignored(rx: &mut Vec<u8>, count: usize) {
549            for _ in 0..count {
550                rx.push(0xFF);
551            }
552        }
553
554        fn push_command_response(rx: &mut Vec<u8>, r1: u8, extra: &[u8]) {
555            Self::push_ignored(rx, 6);
556            rx.push(r1);
557            rx.extend_from_slice(extra);
558        }
559
560        fn tx_contains(&self, bytes: &[u8]) -> bool {
561            self.tx.windows(bytes.len()).any(|window| window == bytes)
562        }
563    }
564
565    impl SpiTransport for ScriptedTransport {
566        fn transfer_byte(&mut self, byte: u8) -> Result<u8, Error> {
567            self.tx.push(byte);
568            if self.rx.is_empty() {
569                return Err(Error::Timeout(ErrorContext::default()));
570            }
571            Ok(self.rx.remove(0))
572        }
573    }
574
575    fn driver(rx: Vec<u8>) -> SpiSdmmc<ScriptedTransport, NullDelay> {
576        SpiSdmmc::new(ScriptedTransport::new(rx), NullDelay)
577    }
578
579    fn push_csd_v2_response(rx: &mut Vec<u8>) {
580        // R2 wrapper: R1=0, then start token + 16 CSD bytes + 2 CRC
581        ScriptedTransport::push_command_response(rx, 0x00, &[]);
582        rx.push(TOKEN_START_BLOCK);
583        let mut csd = [0u8; 16];
584        csd[0] = 0x40; // CSD v2
585        csd[7] = 0x00;
586        csd[8] = 0x0F;
587        csd[9] = 0x0F;
588        rx.extend_from_slice(&csd);
589        rx.extend_from_slice(&crc16_ccitt(&csd).to_be_bytes());
590    }
591
592    fn push_cid_response(rx: &mut Vec<u8>) {
593        ScriptedTransport::push_command_response(rx, 0x00, &[]);
594        rx.push(TOKEN_START_BLOCK);
595        let mut cid = [0u8; 16];
596        cid[0] = 0x03;
597        cid[1] = b'S';
598        cid[2] = b'D';
599        cid[3] = b'A';
600        cid[4] = b'B';
601        cid[5] = b'C';
602        cid[6] = b'1';
603        cid[7] = b'2';
604        rx.extend_from_slice(&cid);
605        rx.extend_from_slice(&crc16_ccitt(&cid).to_be_bytes());
606    }
607
608    #[test]
609    fn init_polls_acmd41_until_spi_r1_leaves_idle() {
610        let mut rx = Vec::new();
611        ScriptedTransport::push_ignored(&mut rx, 10);
612        ScriptedTransport::push_command_response(&mut rx, 0x01, &[]);
613        ScriptedTransport::push_command_response(&mut rx, 0x01, &[0x00, 0x00, 0x01, 0xAA]);
614        ScriptedTransport::push_command_response(&mut rx, 0x01, &[]);
615        ScriptedTransport::push_command_response(&mut rx, 0x01, &[]);
616        ScriptedTransport::push_command_response(&mut rx, 0x01, &[]);
617        ScriptedTransport::push_command_response(&mut rx, 0x00, &[]);
618        ScriptedTransport::push_command_response(&mut rx, 0x00, &[0xC0, 0xFF, 0x80, 0x00]);
619        push_csd_v2_response(&mut rx);
620        push_cid_response(&mut rx);
621
622        let mut driver = driver(rx);
623        let info = driver.init().unwrap();
624
625        assert!(info.sd_v2);
626        assert!(info.high_capacity);
627        assert_eq!(info.ocr, 0xC0FF_8000);
628        assert_eq!(info.capacity_blocks, Some((0x0F0F + 1) * 1024));
629        let cid = info.cid.expect("CID parsed during init");
630        assert_eq!(cid.manufacturer_id(), 0x03);
631        assert_eq!(&cid.oem_id(), b"SD");
632        assert_eq!(&cid.product_name(), b"ABC12");
633        assert!(driver.transport.tx_contains(&cmd::CMD0.to_spi_bytes()));
634        assert!(
635            driver
636                .transport
637                .tx_contains(&cmd::cmd8(0x01, 0xAA).to_spi_bytes())
638        );
639        assert!(driver.transport.tx_contains(&cmd::CMD58.to_spi_bytes()));
640        assert!(driver.transport.tx_contains(&cmd::cmd10(0).to_spi_bytes()));
641    }
642
643    #[test]
644    fn read_block_times_out_when_start_token_never_arrives() {
645        let mut rx = Vec::new();
646        ScriptedTransport::push_command_response(&mut rx, 0x00, &[]);
647        // Provide enough 0xFF bytes so the wait loop hits its timeout, plus
648        // padding that would have been the block payload had it arrived.
649        ScriptedTransport::push_ignored(&mut rx, 10_000);
650        rx.extend_from_slice(&[0xAA; 512]);
651        rx.extend_from_slice(&[0xFF; 2]);
652
653        let mut driver = driver(rx);
654        driver.high_capacity = true;
655        let mut buf = [0u8; 512];
656
657        assert!(matches!(
658            driver.read_block(7, &mut buf),
659            Err(Error::Timeout(_))
660        ));
661        assert!(driver.transport.tx_contains(&cmd::cmd17(7).to_spi_bytes()));
662    }
663
664    #[test]
665    fn read_block_returns_payload_when_crc_matches() {
666        let mut payload = [0u8; 512];
667        for (i, b) in payload.iter_mut().enumerate() {
668            *b = (i & 0xFF) as u8;
669        }
670        let crc = crc16_ccitt(&payload).to_be_bytes();
671
672        let mut rx = Vec::new();
673        ScriptedTransport::push_command_response(&mut rx, 0x00, &[]);
674        rx.push(0xFF);
675        rx.push(TOKEN_START_BLOCK);
676        rx.extend_from_slice(&payload);
677        rx.extend_from_slice(&crc);
678
679        let mut driver = driver(rx);
680        driver.high_capacity = true;
681        let mut buf = [0u8; 512];
682        driver.read_block(0, &mut buf).unwrap();
683        assert_eq!(&buf[..], &payload[..]);
684    }
685
686    #[test]
687    fn read_block_reports_crc_error_when_trailer_mismatches() {
688        let payload = [0u8; 512];
689        let mut bad_crc = crc16_ccitt(&payload).to_be_bytes();
690        bad_crc[0] ^= 0xFF;
691
692        let mut rx = Vec::new();
693        ScriptedTransport::push_command_response(&mut rx, 0x00, &[]);
694        rx.push(0xFF);
695        rx.push(TOKEN_START_BLOCK);
696        rx.extend_from_slice(&payload);
697        rx.extend_from_slice(&bad_crc);
698
699        let mut driver = driver(rx);
700        driver.high_capacity = true;
701        let mut buf = [0u8; 512];
702        assert!(matches!(driver.read_block(0, &mut buf), Err(Error::Crc(_))));
703    }
704
705    #[test]
706    fn write_block_emits_correct_crc16_after_payload() {
707        let mut rx = Vec::new();
708        // R1=0x00 ack from CMD24…
709        ScriptedTransport::push_command_response(&mut rx, 0x00, &[]);
710        // …then echo bytes for the start token, the 512-byte payload, and
711        // the 2 CRC bytes the driver is about to push out.
712        ScriptedTransport::push_ignored(&mut rx, 1 + 512 + 2);
713        rx.push(0x05); // data response: accepted
714        rx.push(0xFF); // not busy
715
716        let mut driver = driver(rx);
717        driver.high_capacity = true;
718        let payload = [0xA5u8; 512];
719        driver.write_block(0, &payload).unwrap();
720
721        let crc = crc16_ccitt(&payload).to_be_bytes();
722        assert!(driver.transport.tx_contains(&payload));
723        assert!(driver.transport.tx_contains(&crc));
724    }
725
726    #[test]
727    fn switch_to_high_speed_returns_true_when_status_confirms() {
728        let mut status = [0u8; 64];
729        status[16] = 0x01; // group 1 = high-speed
730        let crc = crc16_ccitt(&status).to_be_bytes();
731
732        let mut rx = Vec::new();
733        // CMD6 ack: 6 padding + R1=0x00.
734        ScriptedTransport::push_command_response(&mut rx, 0x00, &[]);
735        rx.push(0xFF); // gap before start token
736        rx.push(TOKEN_START_BLOCK);
737        rx.extend_from_slice(&status);
738        rx.extend_from_slice(&crc);
739
740        let mut driver = driver(rx);
741        let active = driver.switch_to_high_speed().unwrap();
742        assert!(active);
743
744        // Confirm CMD6 with mode=1, group1=1 was actually sent.
745        let cmd = cmd::cmd6_high_speed(true);
746        assert!(driver.transport.tx_contains(&cmd.to_spi_bytes()));
747    }
748
749    #[test]
750    fn switch_to_high_speed_returns_false_when_card_keeps_default() {
751        let status = [0u8; 64]; // group 1 = 0 (default speed)
752        let crc = crc16_ccitt(&status).to_be_bytes();
753
754        let mut rx = Vec::new();
755        ScriptedTransport::push_command_response(&mut rx, 0x00, &[]);
756        rx.push(0xFF);
757        rx.push(TOKEN_START_BLOCK);
758        rx.extend_from_slice(&status);
759        rx.extend_from_slice(&crc);
760
761        let mut driver = driver(rx);
762        let active = driver.switch_to_high_speed().unwrap();
763        assert!(!active);
764    }
765}