Skip to main content

sdmmc_spec/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use bitfield::bitfield;
6use bitflags::bitflags;
7use std::num::NonZeroU16;
8
9/// The CQHCI spec requires 512 byte blocks (JESD84-B51A, 6.6.39.1)
10pub const MMC_BLOCK_SIZE: u64 = 512;
11
12// EXT_CSD fields (JESD84-B51A, 7.4)
13
14pub const EXT_CSD_BARRIER_EN: usize = 31;
15pub const EXT_CSD_BARRIER_ENABLED: u8 = 1;
16
17pub const EXT_CSD_FLUSH_CACHE: usize = 32;
18pub const EXT_CSD_FLUSH_CACHE_FLUSH: u8 = 0x1;
19pub const EXT_CSD_FLUSH_CACHE_BARRIER: u8 = 0x2;
20
21pub const EXT_CSD_CACHE_CTRL: usize = 33;
22pub const EXT_CSD_CACHE_EN_MASK: u8 = 1;
23
24pub const EXT_CSD_PARTITION_CONFIG: usize = 179;
25pub const EXT_CSD_PARTITION_ACCESS_MASK: u8 = 0xf8;
26
27pub const EXT_CSD_PARTITON_SWITCH_TIME: usize = 199;
28
29pub const EXT_CSD_GENERIC_CMD6_TIME: usize = 248;
30
31pub const EXT_CSD_BARRIER_SUPPORT: usize = 486;
32pub const EXT_CSD_BARRIER_SUPPORT_MASK: u8 = 0x1;
33
34#[derive(Clone, Copy, Debug, PartialEq, enumn::N)]
35#[repr(u8)]
36/// Command codes for MMC (JESD84-B51A, 6.10.4).
37///
38/// Only a limited subset which are useful for the CQHCI driver are included.
39pub enum MmcCommand {
40    Switch = 6,
41    SendStatus = 13,
42}
43
44impl MmcCommand {
45    fn response_type(&self) -> DcmdResponseType {
46        match self {
47            Self::Switch => DcmdResponseType::R1B,
48            Self::SendStatus => DcmdResponseType::R1,
49        }
50    }
51}
52
53// Necessary for bitfield
54impl From<MmcCommand> for u8 {
55    fn from(value: MmcCommand) -> Self {
56        value as u8
57    }
58}
59
60bitflags! {
61    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
62    /// Response for CMD13 SEND_STATUS (JESD84-B51A, 6.10.4)
63    pub struct MmcSendStatusResponse: u32 {
64        const CURRENT_STATE_STDBY = 0x3 << 9;
65        const CURRENT_STATE_TRAN = 0x4 << 9;
66        const CURRENT_STATE_DATA = 0x5 << 9;
67        const CURRENT_STATE_RECV = 0x6 << 9;
68        const CURRENT_STATE_SLP = 0xa << 9;
69        const READY_FOR_DATA = 1 << 8;
70        const SWITCH_ERR = 1 << 7;
71        const EXCEPTION_EVENT = 1 << 6;
72        const APP_CMD = 1 << 5;
73    }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77/// Direction of data transfer.
78pub enum Direction {
79    Read,
80    Write,
81}
82
83// All task descriptors have a constant act
84const TASK_DESCRIPTOR_ACT: u8 = 0b101;
85
86bitfield! {
87    #[derive(
88        Clone, Copy, Eq, PartialEq, zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::Immutable,
89    )]
90    /// A task descriptor in the CQHCI Task Descriptor List (JESD84-B51A, B.2.1)
91    pub struct CommandQueueTaskDescriptor(u128);
92    impl Debug;
93    bool, valid, set_valid: 0;
94    bool, end, set_end: 1;
95    bool, int, set_int: 2;
96    u8, act, set_act: 5, 3;
97    bool, forced_programming, set_forced_programming: 6;
98    u8, context_id, set_context_id: 10, 7;
99    bool, tag_request, set_tag_request: 11;
100    bool, data_direction, set_data_direction: 12;
101    bool, priority, set_priority: 13;
102    bool, qbr, set_qbr: 14;
103    bool, reliable_write, set_reliable_write: 15;
104    u16, block_count, set_block_count: 31, 16;
105    u64, block_offset, set_block_offset: 95, 32;
106    // 96..=127 reserved
107}
108
109impl CommandQueueTaskDescriptor {
110    fn new(direction: Direction, block_offset: u64, block_count: NonZeroU16) -> Self {
111        let mut this = Self(0);
112        this.set_valid(true);
113        this.set_end(true);
114        this.set_int(true);
115        this.set_act(TASK_DESCRIPTOR_ACT);
116        this.set_data_direction(direction == Direction::Read);
117        this.set_block_count(block_count.get());
118        this.set_block_offset(block_offset);
119        this
120    }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124#[repr(u8)]
125pub enum DcmdResponseType {
126    /// No response is expected for the command
127    NoResponse = 0b00,
128    /// Normal response expected
129    R1 = 0b10,
130    /// Like R1, but with an optional busy signal transmitted on the DATA line.
131    R1B = 0b11,
132}
133
134impl DcmdResponseType {
135    pub const R4: Self = Self::R1;
136    pub const R5: Self = Self::R1;
137}
138
139// Necessary for bitfield
140impl From<DcmdResponseType> for u8 {
141    fn from(value: DcmdResponseType) -> Self {
142        value as u8
143    }
144}
145
146bitfield! {
147    #[derive(
148        Clone, Copy, Eq, PartialEq, zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::Immutable,
149    )]
150    /// A Direct Command task descriptor in the CQHCI Task Descriptor List (JESD84-B51A, B.2.3)
151    pub struct CommandQueueDirectCmdTaskDescriptor(u128);
152    impl Debug;
153    pub bool, valid, set_valid: 0;
154    pub bool, end, set_end: 1;
155    pub bool, int, set_int: 2;
156    pub u8, act, set_act: 5, 3;
157    pub bool, qbr, set_qbr: 14;
158    pub u8, from into MmcCommand, _, set_cmd_index: 21, 16;
159    pub bool, cmd_timing, set_cmd_timing: 22;
160    pub u8, from into DcmdResponseType, _, set_response_type: 24, 23;
161    pub u32, cmd_arg, set_cmd_arg: 63, 32;
162}
163
164impl CommandQueueDirectCmdTaskDescriptor {
165    fn new(command: MmcCommand, command_arg: u32) -> Self {
166        let mut this = Self(0);
167        this.set_valid(true);
168        this.set_end(true);
169        this.set_act(TASK_DESCRIPTOR_ACT);
170        this.set_qbr(true);
171        this.set_int(true);
172        this.set_cmd_index(command);
173        let response_type = command.response_type();
174        this.set_response_type(response_type);
175        // Whether the command may be sent to device during data activity or busy time.
176        // From the spec: "NOTE Shall be set to 0 if response type is b11 (R1b)"
177        this.set_cmd_timing(response_type != DcmdResponseType::R1B);
178        this.set_cmd_arg(command_arg);
179        this
180    }
181}
182
183#[derive(Clone, Copy, Debug, PartialEq, Eq)]
184#[repr(transparent)]
185/// A wrapper around the transfer length field in CQHCI transfer descriptors.
186/// The raw value of 0 is interpreted as 64KiB, which is hidden behind this type for clarity
187/// (JESD84-B51A, B.3.2).
188pub struct TransferBytes(u16);
189
190impl TransferBytes {
191    /// The maximum number of bytes which can be referenced by a single transfer descriptor.
192    pub const MAX_BYTES: usize = u16::MAX as usize + 1;
193
194    /// The maximum number of blocks which can be referenced by a single transfer descriptor.
195    pub const MAX_BLOCKS: u64 = Self::MAX_BYTES as u64 / MMC_BLOCK_SIZE;
196
197    pub const MAX: Self = Self(0);
198}
199
200impl From<TransferBytes> for u32 {
201    fn from(length: TransferBytes) -> u32 {
202        if length == TransferBytes::MAX { TransferBytes::MAX_BYTES as u32 } else { length.0 as u32 }
203    }
204}
205
206impl TryFrom<usize> for TransferBytes {
207    type Error = usize;
208
209    fn try_from(size: usize) -> Result<Self, Self::Error> {
210        if size == 0 {
211            Err(size)
212        } else if size < Self::MAX_BYTES {
213            debug_assert!(size <= u16::MAX as usize);
214            Ok(Self(size as u16))
215        } else if size == Self::MAX_BYTES {
216            Ok(Self::MAX)
217        } else {
218            Err(size)
219        }
220    }
221}
222
223#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224#[repr(u8)]
225enum TransferAct {
226    /// The transfer descriptor points to a data region to read/write to.
227    Tran = 0b100,
228    /// The transfer descriptor points to a list of transfer descriptors.
229    Link = 0b110,
230}
231
232// Necessary for bitfield
233impl From<TransferAct> for u8 {
234    fn from(value: TransferAct) -> Self {
235        value as u8
236    }
237}
238
239bitfield! {
240    #[derive(
241        Clone, Copy, Eq, PartialEq, zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::Immutable,
242    )]
243    /// A transfer descriptor in the CQHCI Task Descriptor List (JESD84-B51A, B.2.2).
244    pub struct CommandQueueTransferDescriptor(u128);
245    impl Debug;
246    bool, valid, set_valid: 0;
247    bool, end, set_end: 1;
248    bool, int, set_int: 2;
249    u8, from into TransferAct, _, set_act: 5, 3;
250    // 6..=15 reserved
251    u16, length, set_length: 31, 16;
252    u64, address, set_address: 95, 32;
253    // 96..=127 reserved
254}
255
256impl CommandQueueTransferDescriptor {
257    /// Creates a new [`CommandQueueTransferDescriptor`] pointing to a data buffer.
258    pub fn transfer(address: u64, length: TransferBytes, end: bool) -> Self {
259        let mut this = Self(0);
260        this.set_valid(true);
261        this.set_end(end);
262        this.set_int(false);
263        this.set_act(TransferAct::Tran);
264        this.set_length(length.0);
265        this.set_address(address);
266        this
267    }
268
269    /// Creates a new [`CommandQueueTransferDescriptor`] pointing to a list of transfer descriptors.
270    pub fn link(address: u64) -> Self {
271        let mut this = Self(0);
272        this.set_valid(true);
273        this.set_end(false);
274        this.set_int(false);
275        this.set_act(TransferAct::Link);
276        this.set_address(address);
277        this
278    }
279}
280
281#[repr(C)]
282#[derive(
283    Debug, Clone, Copy, Eq, PartialEq, zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::Immutable,
284)]
285/// An entry in the CQHCI Task Descriptor List (JESD84-B51A, B.2).
286///
287/// Note that this assumes 16-byte descriptors.
288pub struct CommandQueueTDLEntry {
289    task: CommandQueueTaskDescriptor,
290    transfer: CommandQueueTransferDescriptor,
291}
292
293impl CommandQueueTDLEntry {
294    /// Creates a new [`CommandQueueTDLEntry`] which points to a single memory region at
295    /// `phys_address`.
296    ///
297    /// The caller must ensure that `block_count` does not exceed the maximum transfer size of
298    /// [`TransferBytes::MAX_BLOCKS`], otherwise an error is returned.
299    pub fn single_buffer(
300        direction: Direction,
301        block_offset: u64,
302        block_count: NonZeroU16,
303        phys_address: u64,
304    ) -> Result<Self, ()> {
305        // Unwrap OK because the caller should never pass a block_count which would exceed 64KiB of
306        // data.
307        let length = TransferBytes::try_from(block_count.get() as usize * MMC_BLOCK_SIZE as usize)
308            .map_err(|_| ())?;
309        Ok(Self {
310            task: CommandQueueTaskDescriptor::new(direction, block_offset, block_count),
311            transfer: CommandQueueTransferDescriptor::transfer(phys_address, length, true),
312        })
313    }
314
315    /// Creates a new [`CommandQueueTDLEntry`] which points to a list of
316    /// [`CommandQueueTransferDescriptor`]s at `descriptors_phys_address`.  The caller must ensure
317    /// that one or more descriptors, ending with one that has END set, is initialized at this
318    /// address before submitting the task.
319    pub fn scatter_gather_buffers(
320        direction: Direction,
321        block_offset: u64,
322        block_count: NonZeroU16,
323        descriptors_phys_address: u64,
324    ) -> Self {
325        debug_assert!(
326            descriptors_phys_address
327                .is_multiple_of(std::mem::align_of::<CommandQueueTransferDescriptor>() as u64)
328        );
329        Self {
330            task: CommandQueueTaskDescriptor::new(direction, block_offset, block_count),
331            transfer: CommandQueueTransferDescriptor::link(descriptors_phys_address),
332        }
333    }
334}
335
336#[repr(C)]
337#[derive(
338    Debug, Clone, Copy, Eq, PartialEq, zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::Immutable,
339)]
340/// A DCMD entry in the CQHCI Task Descriptor List (JESD84-B51A, B.2.2).
341///
342/// Note that this assumes 16-byte descriptors.
343///
344/// Should only be written into the DCMD slot in the TDL; regular transfers must be of type
345/// [`CommandQueueTDLEntry`].
346pub struct CommandQueueTDLDirectCmdEntry {
347    task: CommandQueueDirectCmdTaskDescriptor,
348    _transfer: u128,
349}
350
351impl CommandQueueTDLDirectCmdEntry {
352    pub fn new(command: MmcCommand, command_arg: u32) -> Self {
353        Self { task: CommandQueueDirectCmdTaskDescriptor::new(command, command_arg), _transfer: 0 }
354    }
355}
356
357pub const CQHCI_TASK_DESCRIPTOR_LIST_NUM_SLOTS: usize = 32;
358pub const CQHCI_TASK_DESCRIPTOR_LIST_DCMD_SLOT: u8 = 31;
359pub const CQHCI_TASK_DESCRIPTOR_LIST_SIZE: usize =
360    CQHCI_TASK_DESCRIPTOR_LIST_NUM_SLOTS * size_of::<CommandQueueTDLEntry>();
361
362// CQHCI registers (JESD84-B51A, B.3.1)
363
364pub const CQHCI_CQ_VER_OFFSET: usize = 0x0;
365pub const CQHCI_CQ_CAP_OFFSET: usize = 0x4;
366pub const CQHCI_CQ_CFG_OFFSET: usize = 0x8;
367pub const CQHCI_CQ_CTL_OFFSET: usize = 0xC;
368pub const CQHCI_CQ_IS_OFFSET: usize = 0x10;
369pub const CQHCI_CQ_ISTE_OFFSET: usize = 0x14;
370pub const CQHCI_CQ_ISGE_OFFSET: usize = 0x18;
371pub const CQHCI_CQ_IC_OFFSET: usize = 0x1c;
372pub const CQHCI_CQ_TDLBA_OFFSET: usize = 0x20;
373pub const CQHCI_CQ_TDLBAU_OFFSET: usize = 0x24;
374pub const CQHCI_CQ_TDBR_OFFSET: usize = 0x28;
375pub const CQHCI_CQ_TCN_OFFSET: usize = 0x2C;
376pub const CQHCI_CQ_DQS_OFFSET: usize = 0x30;
377pub const CQHCI_CQ_DPT_OFFSET: usize = 0x34;
378pub const CQHCI_CQ_TDPE_OFFSET: usize = 0x3C;
379pub const CQHCI_CQ_SSC1_OFFSET: usize = 0x40;
380pub const CQHCI_CQ_SSC2_OFFSET: usize = 0x44;
381pub const CQHCI_CQ_CRDCT_OFFSET: usize = 0x48;
382pub const CQHCI_CQ_RMEM_OFFSET: usize = 0x50;
383pub const CQHCI_CQ_TERRI_OFFSET: usize = 0x54;
384pub const CQHCI_CQ_CRI_OFFSET: usize = 0x58;
385pub const CQHCI_CQ_CRA_OFFSET: usize = 0x5C;
386pub const CQHCI_CQ_HCCAP_OFFSET: usize = 0x60;
387pub const CQHCI_CQ_HCCFG_OFFSET: usize = 0x64;
388// The following registers are valid iff CS is set in CQHCI_CQ_CAP_OFFSET.
389pub const CQHCI_CQ_CRYPTO_NQP_OFFSET: usize = 0x70;
390pub const CQHCI_CQ_CRYPTO_NQDUN_OFFSET: usize = 0x74;
391pub const CQHCI_CQ_CRYPTO_NQIS_OFFSET: usize = 0x78;
392pub const CQHCI_CQ_CRYPTO_NQIE_OFFSET: usize = 0x7C;
393pub const CQHCI_CQ_CRYPTO_CAP_OFFSET: usize = 0x100;
394
395bitfield! {
396    #[derive(Clone, Copy)]
397    pub struct CqhciCqCapsRegister(u32);
398    impl Debug;
399    pub u16, timer_clock_freq, set_timer_clock_freq: 9, 0;
400    pub u8, timer_clock_freq_multiplier, set_timer_clock_freq_multiplier: 15, 12;
401    pub bool, crypto_support, set_crypto_support: 28;
402}
403
404bitflags! {
405    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
406    pub struct CqhciCqCfgRegister: u32 {
407        const DCMD_ENABLE = 1 << 12;
408        const TASK_DESC_128 = 1 << 8;  // If 0, 64-bit
409        const CRYPTO_ENABLE = 1 << 1;
410        const CQE_ENABLE = 1;
411    }
412}
413
414bitflags! {
415    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
416    pub struct CqhciCqCtlRegister: u32 {
417        const CLEAR_ALL_TASKS = 1 << 8;
418        const HALT = 1;
419    }
420}
421
422bitfield! {
423    #[derive(Clone, Copy)]
424    pub struct CqhciCqSendStatusConfiguration1Register(u32);
425    impl Debug;
426    pub u16, ssc_idle_timer, set_ssc_idle_timer: 15, 0;
427    pub u8, ssc_block_counter, set_ssc_block_counter: 19, 16;
428}
429
430bitfield! {
431    #[derive(Clone, Copy)]
432    pub struct CqhciCqSendStatusConfiguration2Register(u32);
433    impl Debug;
434    impl New;
435    pub u16, rca, set_rca: 15, 0;
436}
437
438// TODO(https://fxbug.dev/42176727): Add crypto errors.
439bitfield! {
440    #[derive(Clone, Copy)]
441    pub struct CqhciCqInterruptStatusRegister(u32);
442    impl Debug;
443    pub bool, halt_complete, set_halt_complete: 0;
444    pub bool, task_complete, set_task_complete: 1;
445    pub bool, response_error_detected, set_response_error_detected: 2;
446    pub bool, task_cleared, set_task_cleared: 3;
447    pub bool, general_crypto_error, set_general_crypto_error: 4;
448    pub bool, invalid_crypto_config_error, set_invalid_crypto_config_error: 5;
449    pub bool, device_exception_event, set_device_exception_event: 6;
450    pub bool, host_controller_fatal_error, set_host_controller_fatal_error: 7;
451}
452
453impl CqhciCqInterruptStatusRegister {
454    pub fn is_error(&self) -> bool {
455        self.response_error_detected()
456            || self.general_crypto_error()
457            || self.invalid_crypto_config_error()
458            || self.device_exception_event()
459            || self.host_controller_fatal_error()
460    }
461}
462
463bitfield! {
464    #[derive(Clone, Copy)]
465    pub struct CqhciCqInterruptStatusEnableRegister(u32);
466    impl Debug;
467    pub bool, halt_complete, set_halt_complete: 0;
468    pub bool, task_complete, set_task_complete: 1;
469    pub bool, response_error_detected, set_response_error_detected: 2;
470    pub bool, task_cleared, set_task_cleared: 3;
471    pub bool, general_crypto_error, set_general_crypto_error: 4;
472    pub bool, invalid_crypto_config_error, set_invalid_crypto_config_error: 5;
473    pub bool, device_exception_event, set_device_exception_event: 6;
474    pub bool, host_controller_fatal_error, set_host_controller_fatal_error: 7;
475}
476
477impl CqhciCqInterruptStatusEnableRegister {
478    pub fn disabled() -> Self {
479        Self(0)
480    }
481    pub fn enabled() -> Self {
482        Self(0xff)
483    }
484}
485
486bitfield! {
487    #[derive(Clone, Copy)]
488    pub struct CqhciCqInterruptSignalEnableRegister(u32);
489    impl Debug;
490    pub bool, halt_complete, set_halt_complete: 0;
491    pub bool, task_complete, set_task_complete: 1;
492    pub bool, response_error_detected, set_response_error_detected: 2;
493    pub bool, task_cleared, set_task_cleared: 3;
494    pub bool, general_crypto_error, set_general_crypto_error: 4;
495    pub bool, invalid_crypto_config_error, set_invalid_crypto_config_error: 5;
496    pub bool, device_exception_event, set_device_exception_event: 6;
497    pub bool, host_controller_fatal_error, set_host_controller_fatal_error: 7;
498}
499
500impl CqhciCqInterruptSignalEnableRegister {
501    pub fn disabled() -> Self {
502        Self(0)
503    }
504    pub fn enabled() -> Self {
505        Self(0xff)
506    }
507}
508
509bitfield! {
510    #[derive(Clone, Copy)]
511    pub struct CqhciCqInterruptCoalescingRegister(u32);
512    impl Debug;
513    pub u8, ic_timeout_value, set_ic_timeout_value: 6, 0;
514    pub bool, ic_timeout_value_write_enable, set_ic_timeout_value_write_enable: 7;
515    pub u8, ic_counter_threshold, set_ic_counter_threshold: 12, 8;
516    pub bool, ic_counter_threshold_write_enable, set_ic_counter_threshold_write_enable: 15;
517    pub bool, ic_counter_timer_reset, set_ic_counter_timer_reset: 16;
518    pub bool, ic_status_bit, set_ic_status_bit: 20;
519    pub bool, ic_enable, set_ic_enable: 31;
520}
521
522impl CqhciCqInterruptCoalescingRegister {
523    pub fn disabled() -> Self {
524        let mut this = Self(0);
525        this.set_ic_enable(false);
526        this
527    }
528}
529
530bitfield! {
531    #[derive(Clone, Copy)]
532    pub struct CqhciCqTaskErrorRegister(u32);
533    impl Debug;
534    pub u8, response_mode_error_command_index, _: 5, 0;
535    pub u8, response_mode_error_task_id, _: 12, 8;
536    pub bool, response_mode_error_fields_valid, _: 15;
537    pub u8, data_transfer_error_command_index, _: 21, 16;
538    pub u8, data_transfer_error_task_id, _: 28, 24;
539    pub bool, data_transfer_error_fields_valid, _: 31;
540}