Skip to main content

sdio_host2/
lib.rs

1//! Physical SD/SDIO/MMC host-bus transaction traits.
2//!
3//! This crate intentionally models the shared CMD/DAT bus rather than a card,
4//! block device, filesystem, or runtime queue. A host accepts one transaction
5//! at a time: a command, an optional data phase, and a task-side poll path to
6//! observe completion. Higher-level SD/MMC card protocols live in
7//! `sdmmc-protocol`.
8
9#![no_std]
10
11extern crate alloc;
12
13use alloc::boxed::Box;
14use core::{
15    fmt,
16    num::{NonZeroU16, NonZeroU32},
17};
18
19use dma_api::{CompletedDma, DmaDirection, PreparedDma};
20
21/// SD/SDIO/MMC command packet submitted on the CMD line.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct Command {
24    pub index: u8,
25    pub argument: u32,
26    pub response: ResponseType,
27}
28
29impl Command {
30    pub const fn new(index: u8, argument: u32, response: ResponseType) -> Self {
31        Self {
32            index,
33            argument,
34            response,
35        }
36    }
37
38    pub const fn index(self) -> u8 {
39        self.index
40    }
41
42    pub const fn argument(self) -> u32 {
43        self.argument
44    }
45
46    pub const fn with_response(self, response: ResponseType) -> Self {
47        Self { response, ..self }
48    }
49
50    /// Return a copy of this command with its response type overridden.
51    ///
52    /// Kept as a compatibility alias for existing SD/MMC protocol helpers.
53    pub const fn with_resp_type(self, response: ResponseType) -> Self {
54        self.with_response(response)
55    }
56
57    /// Compatibility alias for older SD/MMC command helpers.
58    pub const fn cmd(self) -> u8 {
59        self.index
60    }
61
62    /// Compatibility alias for older SD/MMC command helpers.
63    pub const fn arg(self) -> u32 {
64        self.argument
65    }
66
67    /// Direction of the data phase that follows this command when it is
68    /// unambiguous from the command index alone.
69    ///
70    /// SDIO CMD53 carries its direction in the argument; CMD6 is also
71    /// overloaded between ACMD6 and SWITCH_FUNC, so both return `None`.
72    pub const fn data_direction(&self) -> Option<DataDirection> {
73        match self.index {
74            17 | 18 => Some(DataDirection::Read),
75            24 | 25 => Some(DataDirection::Write),
76            _ => None,
77        }
78    }
79
80    /// Size in bytes of the data block when fixed by the command index.
81    pub const fn data_block_size(&self) -> Option<u32> {
82        match self.index {
83            17 | 18 | 24 | 25 => Some(512),
84            _ => None,
85        }
86    }
87
88    /// Compute the SD SPI-mode CRC7 for this command packet.
89    pub fn crc7(&self) -> u8 {
90        let mut crc: u8 = 0;
91        let token: u8 = 0x40 | (self.index & 0x3F);
92        crc = crc7_update(crc, token);
93        for byte in self.argument.to_be_bytes() {
94            crc = crc7_update(crc, byte);
95        }
96        (crc << 1) | 1
97    }
98
99    /// Build the 6-byte SD SPI command packet.
100    pub fn to_spi_bytes(&self) -> [u8; 6] {
101        let crc = self.crc7();
102        let token = 0x40 | (self.index & 0x3F);
103        let arg = self.argument.to_be_bytes();
104        [token, arg[0], arg[1], arg[2], arg[3], crc]
105    }
106}
107
108fn crc7_update(crc: u8, byte: u8) -> u8 {
109    let mut crc = crc;
110    let mut data = byte;
111    for _ in 0..8 {
112        crc <<= 1;
113        if (crc ^ data) & 0x80 != 0 {
114            crc ^= 0x89;
115        }
116        data <<= 1;
117    }
118    crc
119}
120
121/// Command response shape expected from the card.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123#[non_exhaustive]
124pub enum ResponseType {
125    None,
126    R1,
127    R1b,
128    R2,
129    R3,
130    R4,
131    R5,
132    R6,
133    R7,
134}
135
136/// Raw response words harvested by a host controller.
137///
138/// ABI:
139///
140/// - 48-bit responses store their response payload in `words[0]`.
141/// - R2/CID/CSD responses store four 32-bit words in most-significant-word
142///   first order.
143/// - Each word is the big-endian value of the corresponding response bytes.
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct RawResponse {
146    pub ty: ResponseType,
147    pub words: [u32; 4],
148}
149
150impl RawResponse {
151    pub const fn new(ty: ResponseType, words: [u32; 4]) -> Self {
152        Self { ty, words }
153    }
154
155    pub const fn empty() -> Self {
156        Self {
157            ty: ResponseType::None,
158            words: [0; 4],
159        }
160    }
161}
162
163/// Direction of a data phase on DAT lines.
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165#[non_exhaustive]
166pub enum DataDirection {
167    Read,
168    Write,
169}
170
171/// Caller-owned data buffer tied to an in-flight transaction lifetime.
172pub enum DataBuffer<'a> {
173    Read(&'a mut [u8]),
174    Write(&'a [u8]),
175    Dma(PreparedDma),
176}
177
178impl DataBuffer<'_> {
179    pub fn len(&self) -> usize {
180        match self {
181            Self::Read(buf) => buf.len(),
182            Self::Write(buf) => buf.len(),
183            Self::Dma(buffer) => buffer.len().get(),
184        }
185    }
186
187    pub fn is_empty(&self) -> bool {
188        self.len() == 0
189    }
190
191    pub fn matches_direction(&self, direction: DataDirection) -> bool {
192        match self {
193            Self::Read(_) => direction == DataDirection::Read,
194            Self::Write(_) => direction == DataDirection::Write,
195            Self::Dma(buffer) => matches!(
196                (buffer.direction(), direction),
197                (DmaDirection::FromDevice, DataDirection::Read)
198                    | (DmaDirection::ToDevice, DataDirection::Write)
199                    | (DmaDirection::Bidirectional, _)
200            ),
201        }
202    }
203}
204
205pub type DataTransfer<'a> = DataBuffer<'a>;
206
207/// Error returned while constructing an owned-DMA data phase.
208pub struct DmaPhaseError {
209    error: Error,
210    buffer: Box<PreparedDma>,
211}
212
213impl DmaPhaseError {
214    fn new(error: Error, buffer: PreparedDma) -> Self {
215        Self {
216            error,
217            buffer: Box::new(buffer),
218        }
219    }
220
221    pub const fn error(&self) -> Error {
222        self.error
223    }
224
225    pub fn into_buffer(self) -> PreparedDma {
226        *self.buffer
227    }
228
229    pub fn into_parts(self) -> (Error, PreparedDma) {
230        (self.error, *self.buffer)
231    }
232}
233
234impl fmt::Debug for DmaPhaseError {
235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236        f.debug_struct("DmaPhaseError")
237            .field("error", &self.error)
238            .finish_non_exhaustive()
239    }
240}
241
242impl fmt::Display for DmaPhaseError {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        self.error.fmt(f)
245    }
246}
247
248impl core::error::Error for DmaPhaseError {}
249
250/// Optional data phase associated with a command.
251pub struct DataPhase<'a> {
252    pub direction: DataDirection,
253    pub block_size: NonZeroU16,
254    pub block_count: NonZeroU32,
255    pub buffer: DataBuffer<'a>,
256}
257
258impl<'a> DataPhase<'a> {
259    pub fn read(
260        block_size: NonZeroU16,
261        block_count: NonZeroU32,
262        buffer: &'a mut [u8],
263    ) -> Result<Self, Error> {
264        let phase = Self {
265            direction: DataDirection::Read,
266            block_size,
267            block_count,
268            buffer: DataBuffer::Read(buffer),
269        };
270        phase.validate()?;
271        Ok(phase)
272    }
273
274    pub fn write(
275        block_size: NonZeroU16,
276        block_count: NonZeroU32,
277        buffer: &'a [u8],
278    ) -> Result<Self, Error> {
279        let phase = Self {
280            direction: DataDirection::Write,
281            block_size,
282            block_count,
283            buffer: DataBuffer::Write(buffer),
284        };
285        phase.validate()?;
286        Ok(phase)
287    }
288
289    pub fn dma(
290        direction: DataDirection,
291        block_size: NonZeroU16,
292        block_count: NonZeroU32,
293        buffer: PreparedDma,
294    ) -> Result<Self, DmaPhaseError> {
295        let phase = Self {
296            direction,
297            block_size,
298            block_count,
299            buffer: DataBuffer::Dma(buffer),
300        };
301        match phase.validate() {
302            Ok(()) => Ok(phase),
303            Err(err) => {
304                let DataBuffer::Dma(buffer) = phase.buffer else {
305                    unreachable!("DataPhase::dma always stores a DMA buffer")
306                };
307                Err(DmaPhaseError::new(err, buffer))
308            }
309        }
310    }
311
312    pub fn validate(&self) -> Result<(), Error> {
313        let expected = usize::from(self.block_size.get())
314            .checked_mul(
315                usize::try_from(self.block_count.get()).map_err(|_| Error::InvalidArgument)?,
316            )
317            .ok_or(Error::InvalidArgument)?;
318        if self.buffer.len() != expected {
319            return Err(Error::InvalidArgument);
320        }
321        if !self.buffer.matches_direction(self.direction) {
322            return Err(Error::InvalidArgument);
323        }
324        Ok(())
325    }
326}
327
328/// One physical bus transaction: a command and an optional data phase.
329pub struct Transaction<'a> {
330    pub command: Command,
331    pub data: Option<DataPhase<'a>>,
332}
333
334impl<'a> Transaction<'a> {
335    pub const fn command(command: Command) -> Self {
336        Self {
337            command,
338            data: None,
339        }
340    }
341
342    pub const fn with_data(command: Command, data: DataPhase<'a>) -> Self {
343        Self {
344            command,
345            data: Some(data),
346        }
347    }
348}
349
350/// Submit failure for an owned transaction.
351///
352/// When `transaction` is present, the caller may recover and retry the DMA
353/// backing. When it is absent, the host had to consume/quiesce the transaction
354/// while handling the error; no hardware access remains active on return.
355pub struct SubmitTransactionError<'a> {
356    pub error: Error,
357    transaction: Option<Box<Transaction<'a>>>,
358}
359
360impl<'a> SubmitTransactionError<'a> {
361    pub fn new(error: Error, transaction: Transaction<'a>) -> Self {
362        Self {
363            error,
364            transaction: Some(Box::new(transaction)),
365        }
366    }
367
368    pub const fn consumed(error: Error) -> Self {
369        Self {
370            error,
371            transaction: None,
372        }
373    }
374
375    pub fn into_transaction(self) -> Option<Transaction<'a>> {
376        self.transaction.map(|transaction| *transaction)
377    }
378}
379
380/// Result of advancing a submitted request once.
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub enum RequestPoll<T> {
383    Pending,
384    Ready(Result<T, Error>),
385}
386
387/// Error returned when a request is polled through the wrong handle or after
388/// its terminal state.
389///
390/// Unlike [`RequestPoll::Ready`], this is not a transfer terminal state for
391/// the request payload. Implementations must not report a terminal
392/// [`RequestPoll::Ready`] error until the controller is no longer accessing
393/// the transaction buffer.
394#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395#[non_exhaustive]
396pub enum PollRequestError {
397    WrongOwner,
398    WrongKind,
399    AlreadyCompleted,
400    StaleGeneration,
401    /// Recovery could not be reported through the requested handle.
402    ///
403    /// Safe host implementations must still quiesce the hardware before any
404    /// request object that borrows caller memory can be dropped. This variant
405    /// is diagnostic only; it must not mean DMA is still active.
406    RecoveryFailed,
407}
408
409/// SD/SDIO/MMC bus width.
410#[derive(Debug, Clone, Copy, PartialEq, Eq)]
411#[non_exhaustive]
412pub enum BusWidth {
413    Bit1,
414    Bit4,
415    Bit8,
416}
417
418/// Named card clock modes used by SD/MMC protocol state machines.
419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420#[non_exhaustive]
421pub enum ClockSpeed {
422    Identification,
423    Default,
424    HighSpeed,
425    Sdr12,
426    Sdr25,
427    Sdr50,
428    Sdr104,
429    Ddr50,
430    Hs200,
431}
432
433/// Concrete clock frequency request.
434#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
435pub struct ClockHz(pub u32);
436
437/// Bus signaling voltage.
438#[derive(Debug, Clone, Copy, PartialEq, Eq)]
439#[non_exhaustive]
440pub enum SignalVoltage {
441    V330,
442    V180,
443    V120,
444}
445
446/// Non-data bus operation that may itself need asynchronous completion.
447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448#[non_exhaustive]
449pub enum BusOp {
450    ResetAll,
451    ResetCommandLine,
452    ResetDataLine,
453    PowerOn,
454    PowerOff,
455    SetClock(ClockSpeed),
456    SetClockHz(ClockHz),
457    SetBusWidth(BusWidth),
458    SetSignalVoltage(SignalVoltage),
459    ExecuteTuning {
460        command: Command,
461        block_size: NonZeroU16,
462    },
463}
464
465/// Host/bus-layer error.
466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
467#[non_exhaustive]
468pub enum Error {
469    Busy,
470    Timeout,
471    Crc,
472    NoCard,
473    Unsupported,
474    InvalidArgument,
475    Misaligned,
476    Bus,
477    Controller,
478}
479
480impl fmt::Display for Error {
481    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482        let s = match self {
483            Self::Busy => "host bus is busy",
484            Self::Timeout => "host bus timeout",
485            Self::Crc => "host bus CRC error",
486            Self::NoCard => "no card present",
487            Self::Unsupported => "operation is not supported",
488            Self::InvalidArgument => "invalid host bus argument",
489            Self::Misaligned => "misaligned host bus buffer",
490            Self::Bus => "host bus error",
491            Self::Controller => "host controller error",
492        };
493        f.write_str(s)
494    }
495}
496
497impl core::error::Error for Error {}
498
499impl fmt::Display for PollRequestError {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        let s = match self {
502            Self::WrongOwner => "request belongs to a different host",
503            Self::WrongKind => "request was polled through the wrong operation kind",
504            Self::AlreadyCompleted => "request has already completed",
505            Self::StaleGeneration => "request generation is no longer active",
506            Self::RecoveryFailed => "request recovery failed",
507        };
508        f.write_str(s)
509    }
510}
511
512impl core::error::Error for PollRequestError {}
513
514/// Physical SD/SDIO/MMC host bus.
515///
516/// The base contract is single active transaction: a host may reject a submit
517/// with [`Error::Busy`] while another transaction or bus operation is active.
518pub trait SdioHost {
519    type TransactionRequest<'a>
520    where
521        Self: 'a;
522    type BusRequest;
523
524    /// Submit one CMD/DAT transaction.
525    ///
526    /// # Safety
527    ///
528    /// Callers must poll the returned request until [`RequestPoll::Ready`] or
529    /// call [`Self::abort_transaction`] before dropping it. Until one of those
530    /// terminal paths runs, the host may still access the associated data
531    /// buffer through DMA or FIFO PIO.
532    unsafe fn submit_transaction<'a>(
533        &mut self,
534        transaction: Transaction<'a>,
535    ) -> Result<Self::TransactionRequest<'a>, Error>
536    where
537        Self: 'a;
538
539    /// Submit one CMD/DAT transaction while preserving transaction ownership
540    /// on submit-side failure when the host has not started hardware access.
541    ///
542    /// The default path is kept for legacy hosts. Native DMA users should
543    /// override it so submit failure can return the original transaction.
544    ///
545    /// # Safety
546    ///
547    /// Same lifetime contract as [`Self::submit_transaction`].
548    unsafe fn submit_transaction_owned<'a>(
549        &mut self,
550        transaction: Transaction<'a>,
551    ) -> Result<Self::TransactionRequest<'a>, SubmitTransactionError<'a>>
552    where
553        Self: 'a,
554    {
555        match unsafe { self.submit_transaction(transaction) } {
556            Ok(request) => Ok(request),
557            Err(error) => Err(SubmitTransactionError::consumed(error)),
558        }
559    }
560
561    fn poll_transaction<'a>(
562        &mut self,
563        request: &mut Self::TransactionRequest<'a>,
564    ) -> Result<RequestPoll<RawResponse>, PollRequestError>
565    where
566        Self: 'a;
567
568    /// Abort a transaction.
569    ///
570    /// This is part of the safe lifetime contract for borrowed transaction
571    /// buffers. Implementations may return an error to report that the
572    /// controller had to be reset or poisoned, but before returning they must
573    /// have stopped command/data engines and any DMA bus-master access that
574    /// could still touch the request buffer.
575    fn abort_transaction<'a>(
576        &mut self,
577        request: &mut Self::TransactionRequest<'a>,
578    ) -> Result<(), Error>
579    where
580        Self: 'a;
581
582    fn take_completed_dma<'a>(
583        &mut self,
584        _request: &mut Self::TransactionRequest<'a>,
585    ) -> Option<CompletedDma>
586    where
587        Self: 'a,
588    {
589        None
590    }
591
592    /// Submit one non-data bus operation.
593    ///
594    /// # Safety
595    ///
596    /// The returned request must be polled until [`RequestPoll::Ready`] or
597    /// passed to [`Self::abort_bus_op`] before being dropped.
598    unsafe fn submit_bus_op(&mut self, op: BusOp) -> Result<Self::BusRequest, Error>;
599
600    fn poll_bus_op(
601        &mut self,
602        request: &mut Self::BusRequest,
603    ) -> Result<RequestPoll<()>, PollRequestError>;
604
605    /// Abort a bus operation.
606    ///
607    /// Like [`Self::abort_transaction`], returning from this method means the
608    /// controller is no longer executing the operation even when the return
609    /// value carries a diagnostic error.
610    fn abort_bus_op(&mut self, request: &mut Self::BusRequest) -> Result<(), Error>;
611
612    fn now_ms(&self) -> Option<u64> {
613        None
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620
621    struct MockHost {
622        busy: bool,
623    }
624
625    #[derive(Debug)]
626    struct MockTransactionRequest {
627        response: RawResponse,
628        pending_once: bool,
629        done: bool,
630    }
631
632    #[derive(Debug)]
633    struct MockBusRequest {
634        pending_once: bool,
635        done: bool,
636    }
637
638    impl SdioHost for MockHost {
639        type TransactionRequest<'a>
640            = MockTransactionRequest
641        where
642            Self: 'a;
643        type BusRequest = MockBusRequest;
644
645        unsafe fn submit_transaction<'a>(
646            &mut self,
647            transaction: Transaction<'a>,
648        ) -> Result<Self::TransactionRequest<'a>, Error>
649        where
650            Self: 'a,
651        {
652            if self.busy {
653                return Err(Error::Busy);
654            }
655            self.busy = true;
656            Ok(MockTransactionRequest {
657                response: RawResponse::new(transaction.command.response, [0x1234, 0, 0, 0]),
658                pending_once: true,
659                done: false,
660            })
661        }
662
663        fn poll_transaction<'a>(
664            &mut self,
665            request: &mut Self::TransactionRequest<'a>,
666        ) -> Result<RequestPoll<RawResponse>, PollRequestError>
667        where
668            Self: 'a,
669        {
670            if request.done {
671                return Err(PollRequestError::AlreadyCompleted);
672            }
673            if request.pending_once {
674                request.pending_once = false;
675                return Ok(RequestPoll::Pending);
676            }
677            self.busy = false;
678            request.done = true;
679            Ok(RequestPoll::Ready(Ok(request.response)))
680        }
681
682        fn abort_transaction<'a>(
683            &mut self,
684            request: &mut Self::TransactionRequest<'a>,
685        ) -> Result<(), Error>
686        where
687            Self: 'a,
688        {
689            request.done = true;
690            self.busy = false;
691            Ok(())
692        }
693
694        unsafe fn submit_bus_op(&mut self, _op: BusOp) -> Result<Self::BusRequest, Error> {
695            if self.busy {
696                return Err(Error::Busy);
697            }
698            self.busy = true;
699            Ok(MockBusRequest {
700                pending_once: false,
701                done: false,
702            })
703        }
704
705        fn poll_bus_op(
706            &mut self,
707            request: &mut Self::BusRequest,
708        ) -> Result<RequestPoll<()>, PollRequestError> {
709            if request.done {
710                return Err(PollRequestError::AlreadyCompleted);
711            }
712            if request.pending_once {
713                request.pending_once = false;
714                return Ok(RequestPoll::Pending);
715            }
716            self.busy = false;
717            request.done = true;
718            Ok(RequestPoll::Ready(Ok(())))
719        }
720
721        fn abort_bus_op(&mut self, request: &mut Self::BusRequest) -> Result<(), Error> {
722            request.done = true;
723            self.busy = false;
724            Ok(())
725        }
726    }
727
728    #[test]
729    fn data_phase_validates_buffer_shape() {
730        let mut read = [0u8; 1024];
731        let block = NonZeroU16::new(512).unwrap();
732        let phase = DataPhase::read(block, NonZeroU32::new(2).unwrap(), &mut read).unwrap();
733        assert_eq!(phase.direction, DataDirection::Read);
734        assert_eq!(phase.buffer.len(), 1024);
735    }
736
737    #[test]
738    fn host_reports_busy_for_second_active_transaction() {
739        let mut host = MockHost { busy: false };
740        let cmd = Command::new(17, 0, ResponseType::R1);
741        let mut request = unsafe { host.submit_transaction(Transaction::command(cmd)) }.unwrap();
742        assert_eq!(
743            unsafe { host.submit_transaction(Transaction::command(cmd)) }.unwrap_err(),
744            Error::Busy
745        );
746        assert_eq!(
747            host.poll_transaction(&mut request),
748            Ok(RequestPoll::Pending)
749        );
750        assert!(matches!(
751            host.poll_transaction(&mut request),
752            Ok(RequestPoll::Ready(Ok(_)))
753        ));
754        assert_eq!(
755            host.poll_transaction(&mut request),
756            Err(PollRequestError::AlreadyCompleted)
757        );
758        assert!(unsafe { host.submit_transaction(Transaction::command(cmd)) }.is_ok());
759    }
760
761    #[test]
762    fn bus_op_uses_same_single_active_contract() {
763        let mut host = MockHost { busy: false };
764        let _request = unsafe { host.submit_bus_op(BusOp::SetClock(ClockSpeed::Default)) }.unwrap();
765        assert_eq!(
766            unsafe { host.submit_bus_op(BusOp::SetBusWidth(BusWidth::Bit4)) }.unwrap_err(),
767            Error::Busy
768        );
769    }
770
771    #[test]
772    fn abort_releases_single_active_contract() {
773        let mut host = MockHost { busy: false };
774        let cmd = Command::new(17, 0, ResponseType::R1);
775        let mut request = unsafe { host.submit_transaction(Transaction::command(cmd)) }.unwrap();
776
777        host.abort_transaction(&mut request).unwrap();
778
779        assert!(unsafe { host.submit_transaction(Transaction::command(cmd)) }.is_ok());
780        assert_eq!(
781            host.poll_transaction(&mut request),
782            Err(PollRequestError::AlreadyCompleted)
783        );
784    }
785}