Skip to main content

sdhci_host/
dma.rs

1//! DMA glue for the SDHCI ADMA2 data path.
2//!
3//! The crate is `no_std` and refuses to assume an allocator, an MMU layout,
4//! or a particular cache architecture. Callers wire those concerns up via
5//! `dma-api`'s [`DeviceDma`].
6//!
7//! ## Responsibilities split
8//!
9//! - **The host driver** builds the ADMA2 descriptor table inside the
10//!   DMA descriptor buffer, programs the controller, and waits on the
11//!   transfer-complete IRQ.
12//! - **The [`DeviceDma`] impl** translates kernel/CPU pointers to the bus
13//!   addresses the SDHCI sees, and performs whatever cache maintenance is
14//!   needed before the device reads CPU-written memory and after the
15//!   device writes CPU-read memory.
16//!
17//! That split keeps the SDHCI logic portable across hosted kernels,
18//! bare-metal coherent systems (identity mapping, no cache ops), and
19//! bare-metal incoherent systems (identity mapping + dcache flush/invalidate).
20
21use core::{num::NonZeroUsize, ptr::NonNull};
22
23use dma_api::{CoherentArray, DeviceDma, DmaDirection, StreamingMap};
24use sdmmc_protocol::{
25    block::{
26        BlockPoll, BlockRequestId, BlockTransferDirection, BlockTransferMode, BlockTransferState,
27        CommandPoll, DataCommandPoll,
28    },
29    cmd::{Command, DataDirection, cmd17, cmd18, cmd24, cmd25},
30    error::{Error, ErrorContext, Phase},
31    response::Response,
32};
33
34use crate::{
35    command::CommandState,
36    host::{PendingData, Sdhci},
37    regs::*,
38};
39
40/// 32-bit ADMA2 descriptor.
41///
42/// Layout (little-endian, per SDHCI v3.00 ยง1.13):
43///
44/// ```text
45///   0      attr[15:0]   (Valid | End | Int | Act2 | Act1)
46///   2      length[15:0] (0 means 64 KiB)
47///   4      address[31:0]
48/// ```
49#[repr(C, align(4))]
50#[derive(Clone, Copy, Default)]
51pub(crate) struct Adma2Desc32 {
52    attr: u16,
53    length: u16,
54    address: u32,
55}
56
57const ADMA2_ATTR_VALID: u16 = 1 << 0;
58const ADMA2_ATTR_END: u16 = 1 << 1;
59const _ADMA2_ATTR_INT: u16 = 1 << 2;
60// act = 0b10 โ†’ "tran" (data transfer descriptor)
61const ADMA2_ATTR_ACT_TRAN: u16 = 0b10 << 4;
62
63/// Largest single ADMA2 transfer โ€” the length field is 16 bits and `0`
64/// is interpreted as 64 KiB, but we cap a hair below to keep the math
65/// trivial and to leave room for hosts whose ADMA engine refuses
66/// `length == 0` (some Synopsys MSHC variants).
67const ADMA2_MAX_PER_DESC: usize = 65_528; // 64 KiB - 8B, multiple of 8
68
69/// Caller-owned scratch region for the ADMA2 descriptor table.
70///
71/// Sized for a worst-case 64 KiB transfer split into 4 KiB chunks (16
72/// descriptors), which is the SDMA boundary the controller falls back to
73/// on page boundary crossings. Bumping this constant is the only thing
74/// needed to support larger contiguous transfers.
75pub const ADMA2_DESC_COUNT: usize = 16;
76pub const ADMA2_DESC_ALIGN: usize = 64;
77const BLOCK_SIZE: usize = 512;
78
79pub type RequestId = BlockRequestId;
80
81#[derive(Default)]
82pub struct BlockRequestSlot {
83    next: usize,
84    state: BlockTransferState,
85}
86
87pub struct BlockRequest {
88    inner: BlockRequestKind,
89}
90
91// `BlockRequest` owns the DMA mappings and descriptor buffer for one
92// submitted transfer. Moving that ownership to another queue thread does not
93// grant shared access to the mapped memory; completion still requires a
94// mutable `Sdhci` reference and consumes the request.
95unsafe impl Send for BlockRequest {}
96
97enum BlockRequestKind {
98    FifoRead {
99        id: RequestId,
100        buffer: NonNull<u8>,
101        len: usize,
102        block_size: usize,
103        offset: usize,
104        cmd_index: u8,
105        phase: Phase,
106        stage: BlockRequestStage,
107        stop_after_complete: bool,
108        response: Option<Response>,
109    },
110    FifoWrite {
111        id: RequestId,
112        buffer: NonNull<u8>,
113        len: usize,
114        block_size: usize,
115        offset: usize,
116        cmd_index: u8,
117        phase: Phase,
118        stage: BlockRequestStage,
119        stop_after_complete: bool,
120        response: Option<Response>,
121    },
122    Read {
123        id: RequestId,
124        map: StreamingMap<u8>,
125        _desc: CoherentArray<Adma2Desc32>,
126        cmd_index: u8,
127        phase: Phase,
128        stage: BlockRequestStage,
129        stop_after_complete: bool,
130        response: Option<Response>,
131    },
132    Write {
133        id: RequestId,
134        _map: StreamingMap<u8>,
135        _desc: CoherentArray<Adma2Desc32>,
136        cmd_index: u8,
137        phase: Phase,
138        stage: BlockRequestStage,
139        stop_after_complete: bool,
140        response: Option<Response>,
141    },
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145enum BlockRequestStage {
146    Command,
147    Data,
148    Stop,
149}
150
151impl BlockRequest {
152    pub fn id(&self) -> RequestId {
153        match &self.inner {
154            BlockRequestKind::FifoRead { id, .. }
155            | BlockRequestKind::FifoWrite { id, .. }
156            | BlockRequestKind::Read { id, .. }
157            | BlockRequestKind::Write { id, .. } => *id,
158        }
159    }
160
161    pub fn state(&self) -> BlockTransferState {
162        match &self.inner {
163            BlockRequestKind::FifoRead { id, .. } => BlockTransferState::Submitted {
164                id: *id,
165                mode: BlockTransferMode::Fifo,
166                direction: BlockTransferDirection::Read,
167            },
168            BlockRequestKind::FifoWrite { id, .. } => BlockTransferState::Submitted {
169                id: *id,
170                mode: BlockTransferMode::Fifo,
171                direction: BlockTransferDirection::Write,
172            },
173            BlockRequestKind::Read { id, .. } => BlockTransferState::Submitted {
174                id: *id,
175                mode: BlockTransferMode::Dma,
176                direction: BlockTransferDirection::Read,
177            },
178            BlockRequestKind::Write { id, .. } => BlockTransferState::Submitted {
179                id: *id,
180                mode: BlockTransferMode::Dma,
181                direction: BlockTransferDirection::Write,
182            },
183        }
184    }
185
186    fn response(&self) -> Option<Response> {
187        match &self.inner {
188            BlockRequestKind::FifoRead { response, .. }
189            | BlockRequestKind::FifoWrite { response, .. }
190            | BlockRequestKind::Read { response, .. }
191            | BlockRequestKind::Write { response, .. } => *response,
192        }
193    }
194}
195
196impl BlockRequestSlot {
197    pub fn start(
198        &mut self,
199        mode: BlockTransferMode,
200        direction: BlockTransferDirection,
201    ) -> Result<RequestId, Error> {
202        if !matches!(self.state, BlockTransferState::Idle) {
203            return Err(Error::UnsupportedCommand);
204        }
205        let id = RequestId::new(self.next);
206        self.next = self.next.wrapping_add(1);
207        self.state = BlockTransferState::Submitted {
208            id,
209            mode,
210            direction,
211        };
212        Ok(id)
213    }
214
215    pub fn complete(&mut self, id: RequestId) -> Result<(), Error> {
216        if self.state.id() != Some(id) {
217            return Err(Error::InvalidArgument);
218        }
219        self.state = BlockTransferState::Idle;
220        Ok(())
221    }
222
223    pub fn state(&self) -> BlockTransferState {
224        self.state
225    }
226}
227
228/// Build the ADMA2 descriptor table covering `[base, base+total_len)`.
229///
230/// `base` is the *bus* address the controller will use, already translated
231/// by [`DeviceDma`]. Returns the number of descriptors written or
232/// [`Error::Misaligned`] if the buffer would not fit in
233/// [`ADMA2_DESC_COUNT`] entries.
234pub(crate) fn build_descriptors(
235    table: &mut [Adma2Desc32; ADMA2_DESC_COUNT],
236    base: u64,
237    total_len: usize,
238    phase: Phase,
239) -> Result<usize, Error> {
240    if total_len == 0 {
241        return Err(Error::Misaligned);
242    }
243    if base >> 32 != 0 {
244        // 32-bit ADMA2 only addresses the low 4 GiB. 64-bit ADMA2 needs a
245        // different descriptor layout we don't ship yet โ€” surface it as a
246        // capability mismatch rather than truncating silently.
247        return Err(Error::BadResponse(ErrorContext::new(phase)));
248    }
249
250    let mut remaining = total_len;
251    let mut offset: u64 = 0;
252    let mut written = 0usize;
253
254    while remaining > 0 {
255        if written >= ADMA2_DESC_COUNT {
256            return Err(Error::Misaligned);
257        }
258        let chunk = remaining.min(ADMA2_MAX_PER_DESC);
259        let is_last = chunk == remaining;
260        let mut attr = ADMA2_ATTR_VALID | ADMA2_ATTR_ACT_TRAN;
261        if is_last {
262            attr |= ADMA2_ATTR_END;
263        }
264        table[written] = Adma2Desc32 {
265            attr,
266            length: chunk as u16,
267            address: (base + offset) as u32,
268        };
269        written += 1;
270        offset += chunk as u64;
271        remaining -= chunk;
272    }
273
274    Ok(written)
275}
276
277impl Sdhci {
278    /// Submit one block read using the requested transfer engine.
279    ///
280    /// Both `BlockTransferMode::Dma` and `BlockTransferMode::Fifo` use the
281    /// same submit/poll queue contract. Runtimes that cannot use DMA can
282    /// submit FIFO requests without changing the external block queue shape.
283    pub fn submit_read_blocks(
284        &mut self,
285        start_block: u32,
286        buffer: NonNull<u8>,
287        size: NonZeroUsize,
288        dma: Option<&DeviceDma>,
289        mode: BlockTransferMode,
290        slot: &mut BlockRequestSlot,
291    ) -> Result<BlockRequest, Error> {
292        let id = slot.start(mode, BlockTransferDirection::Read)?;
293        let result = match mode {
294            BlockTransferMode::Dma => {
295                let dma = dma.ok_or(Error::UnsupportedCommand)?;
296                self.build_dma_read_request(start_block, buffer, size, dma, id)
297            }
298            BlockTransferMode::Fifo => self.build_fifo_read_request(start_block, buffer, size, id),
299            // Future BlockTransferMode variants are not supported by this controller.
300            _ => Err(Error::UnsupportedCommand),
301        };
302        match result {
303            Ok(request) => Ok(request),
304            Err(err) => {
305                let _ = slot.complete(id);
306                Err(err)
307            }
308        }
309    }
310
311    /// Submit one block write using the requested transfer engine.
312    ///
313    /// See [`Sdhci::submit_read_blocks`] for the completion contract.
314    pub fn submit_write_blocks(
315        &mut self,
316        start_block: u32,
317        buffer: NonNull<u8>,
318        size: NonZeroUsize,
319        dma: Option<&DeviceDma>,
320        mode: BlockTransferMode,
321        slot: &mut BlockRequestSlot,
322    ) -> Result<BlockRequest, Error> {
323        let id = slot.start(mode, BlockTransferDirection::Write)?;
324        let result = match mode {
325            BlockTransferMode::Dma => {
326                let dma = dma.ok_or(Error::UnsupportedCommand)?;
327                self.build_dma_write_request(start_block, buffer, size, dma, id)
328            }
329            BlockTransferMode::Fifo => self.build_fifo_write_request(start_block, buffer, size, id),
330            // Future BlockTransferMode variants are not supported by this controller.
331            _ => Err(Error::UnsupportedCommand),
332        };
333        match result {
334            Ok(request) => Ok(request),
335            Err(err) => {
336                let _ = slot.complete(id);
337                Err(err)
338            }
339        }
340    }
341
342    /// Poll a previously submitted block request.
343    pub fn poll_block_request(
344        &mut self,
345        request: &mut Option<BlockRequest>,
346        id: RequestId,
347        slot: &mut BlockRequestSlot,
348    ) -> Result<BlockPoll, Error> {
349        match self.poll_block_request_response(request, id, slot)? {
350            DataCommandPoll::Pending => Ok(BlockPoll::Pending),
351            DataCommandPoll::Complete(_) => Ok(BlockPoll::Complete),
352            // Future DataCommandPoll variants are treated as completion.
353            _ => Ok(BlockPoll::Complete),
354        }
355    }
356
357    pub fn poll_block_request_response(
358        &mut self,
359        request: &mut Option<BlockRequest>,
360        id: RequestId,
361        slot: &mut BlockRequestSlot,
362    ) -> Result<DataCommandPoll, Error> {
363        let Some(active) = request.as_ref() else {
364            return Err(Error::InvalidArgument);
365        };
366        if active.id() != id {
367            return Err(Error::InvalidArgument);
368        }
369
370        if matches!(
371            active.inner,
372            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. }
373        ) {
374            return self.poll_fifo_request(request, id, slot);
375        }
376
377        let (cmd_index, phase, stage) = match &active.inner {
378            BlockRequestKind::Read {
379                cmd_index,
380                phase,
381                stage,
382                ..
383            }
384            | BlockRequestKind::Write {
385                cmd_index,
386                phase,
387                stage,
388                ..
389            } => (*cmd_index, *phase, *stage),
390            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. } => {
391                unreachable!()
392            }
393        };
394
395        if stage == BlockRequestStage::Command {
396            match self.poll_command() {
397                Ok(CommandPoll::Pending) => return Ok(DataCommandPoll::Pending),
398                Ok(CommandPoll::Complete) => {
399                    let response = self.take_command_response()?;
400                    if let Some(active) = request.as_mut() {
401                        match &mut active.inner {
402                            BlockRequestKind::Read {
403                                stage,
404                                response: stored_response,
405                                ..
406                            }
407                            | BlockRequestKind::Write {
408                                stage,
409                                response: stored_response,
410                                ..
411                            } => {
412                                *stage = BlockRequestStage::Data;
413                                *stored_response = Some(response);
414                            }
415                            BlockRequestKind::FifoRead { .. }
416                            | BlockRequestKind::FifoWrite { .. } => unreachable!(),
417                        }
418                    }
419                    return Ok(DataCommandPoll::Pending);
420                }
421                // Future CommandPoll variants: best-effort, treat as still pending.
422                Ok(_) => return Ok(DataCommandPoll::Pending),
423                Err(err) => {
424                    self.abort_block_request(request, id, slot);
425                    return Err(err);
426                }
427            }
428        }
429
430        if stage == BlockRequestStage::Stop {
431            return self.poll_block_stop(request, id, slot);
432        }
433
434        match self.poll_data_complete_with_adma(cmd_index, phase) {
435            Ok(BlockPoll::Pending) => Ok(DataCommandPoll::Pending),
436            Ok(BlockPoll::Complete) => self.finish_dma_data(request, id, slot),
437            // Future BlockPoll variants: best-effort, treat as still pending.
438            Ok(_) => Ok(DataCommandPoll::Pending),
439            Err(err) => {
440                self.abort_block_request(request, id, slot);
441                Err(err)
442            }
443        }
444    }
445
446    fn build_dma_read_request(
447        &mut self,
448        start_block: u32,
449        buffer: NonNull<u8>,
450        size: NonZeroUsize,
451        dma: &DeviceDma,
452        id: RequestId,
453    ) -> Result<BlockRequest, Error> {
454        if !self.supports_adma2() {
455            return Err(Error::UnsupportedCommand);
456        }
457        let block_count = dma_read_block_count(size)?;
458        let map = dma
459            .map_streaming_slice_for_device(
460                unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) },
461                BLOCK_SIZE,
462                DmaDirection::FromDevice,
463            )
464            .map_err(map_dma_error)?;
465        let mut desc = dma
466            .coherent_array_zero_with_align::<Adma2Desc32>(ADMA2_DESC_COUNT, ADMA2_DESC_ALIGN)
467            .map_err(map_dma_error)?;
468        let cmd = if block_count == 1 {
469            cmd17(start_block)
470        } else {
471            cmd18(start_block)
472        };
473        self.submit_adma2_blocks_mapped(
474            &cmd,
475            block_count,
476            map.dma_addr().as_u64(),
477            &mut desc,
478            DataDirection::Read,
479            Phase::DataRead,
480        )?;
481        Ok(BlockRequest {
482            inner: BlockRequestKind::Read {
483                id,
484                map,
485                _desc: desc,
486                cmd_index: cmd.cmd,
487                phase: Phase::DataRead,
488                stage: BlockRequestStage::Command,
489                stop_after_complete: block_count > 1,
490                response: None,
491            },
492        })
493    }
494
495    fn build_dma_write_request(
496        &mut self,
497        start_block: u32,
498        buffer: NonNull<u8>,
499        size: NonZeroUsize,
500        dma: &DeviceDma,
501        id: RequestId,
502    ) -> Result<BlockRequest, Error> {
503        if !self.supports_adma2() {
504            return Err(Error::UnsupportedCommand);
505        }
506        let block_count = dma_write_block_count(size)?;
507        let map = dma
508            .map_streaming_slice_for_device(
509                unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) },
510                BLOCK_SIZE,
511                DmaDirection::ToDevice,
512            )
513            .map_err(map_dma_error)?;
514        let mut desc = dma
515            .coherent_array_zero_with_align::<Adma2Desc32>(ADMA2_DESC_COUNT, ADMA2_DESC_ALIGN)
516            .map_err(map_dma_error)?;
517        let cmd = if block_count == 1 {
518            cmd24(start_block)
519        } else {
520            cmd25(start_block)
521        };
522        self.submit_adma2_blocks_mapped(
523            &cmd,
524            block_count,
525            map.dma_addr().as_u64(),
526            &mut desc,
527            DataDirection::Write,
528            Phase::DataWrite,
529        )?;
530        Ok(BlockRequest {
531            inner: BlockRequestKind::Write {
532                id,
533                _map: map,
534                _desc: desc,
535                cmd_index: cmd.cmd,
536                phase: Phase::DataWrite,
537                stage: BlockRequestStage::Command,
538                stop_after_complete: block_count > 1,
539                response: None,
540            },
541        })
542    }
543
544    fn build_fifo_read_request(
545        &mut self,
546        start_block: u32,
547        buffer: NonNull<u8>,
548        size: NonZeroUsize,
549        id: RequestId,
550    ) -> Result<BlockRequest, Error> {
551        let block_count = dma_read_block_count(size)?;
552        let cmd = if block_count == 1 {
553            cmd17(start_block)
554        } else {
555            cmd18(start_block)
556        };
557        self.build_fifo_data_request(
558            &cmd,
559            buffer,
560            size.get(),
561            BLOCK_SIZE as u32,
562            block_count,
563            id,
564            DataDirection::Read,
565            block_count > 1,
566        )
567    }
568
569    fn build_fifo_write_request(
570        &mut self,
571        start_block: u32,
572        buffer: NonNull<u8>,
573        size: NonZeroUsize,
574        id: RequestId,
575    ) -> Result<BlockRequest, Error> {
576        let block_count = dma_write_block_count(size)?;
577        let cmd = if block_count == 1 {
578            cmd24(start_block)
579        } else {
580            cmd25(start_block)
581        };
582        self.build_fifo_data_request(
583            &cmd,
584            buffer,
585            size.get(),
586            BLOCK_SIZE as u32,
587            block_count,
588            id,
589            DataDirection::Write,
590            block_count > 1,
591        )
592    }
593
594    #[allow(clippy::too_many_arguments)]
595    pub fn submit_fifo_data_request(
596        &mut self,
597        cmd: &Command,
598        buffer: NonNull<u8>,
599        len: usize,
600        block_size: u32,
601        block_count: u32,
602        direction: DataDirection,
603        slot: &mut BlockRequestSlot,
604    ) -> Result<BlockRequest, Error> {
605        let transfer_direction = match direction {
606            DataDirection::Read => BlockTransferDirection::Read,
607            DataDirection::Write => BlockTransferDirection::Write,
608            DataDirection::None => return Err(Error::InvalidArgument),
609            // Future DataDirection variants are not supported by this engine.
610            _ => return Err(Error::InvalidArgument),
611        };
612        let id = slot.start(BlockTransferMode::Fifo, transfer_direction)?;
613        match self.build_fifo_data_request(
614            cmd,
615            buffer,
616            len,
617            block_size,
618            block_count,
619            id,
620            direction,
621            false,
622        ) {
623            Ok(request) => Ok(request),
624            Err(err) => {
625                let _ = slot.complete(id);
626                Err(err)
627            }
628        }
629    }
630
631    #[allow(clippy::too_many_arguments)]
632    fn build_fifo_data_request(
633        &mut self,
634        cmd: &Command,
635        buffer: NonNull<u8>,
636        len: usize,
637        block_size: u32,
638        block_count: u32,
639        id: RequestId,
640        direction: DataDirection,
641        stop_after_complete: bool,
642    ) -> Result<BlockRequest, Error> {
643        let block_size_usize = usize::try_from(block_size).map_err(|_| Error::InvalidArgument)?;
644        let block_count_usize = usize::try_from(block_count).map_err(|_| Error::InvalidArgument)?;
645        if block_size_usize == 0
646            || block_count_usize == 0
647            || len != block_size_usize.saturating_mul(block_count_usize)
648        {
649            return Err(Error::InvalidArgument);
650        }
651        let phase = match direction {
652            DataDirection::Read => Phase::DataRead,
653            DataDirection::Write => Phase::DataWrite,
654            DataDirection::None => return Err(Error::InvalidArgument),
655            // Future DataDirection variants are not supported by this engine.
656            _ => return Err(Error::InvalidArgument),
657        };
658        self.pending_data = Some(PendingData {
659            direction,
660            block_size,
661            block_count,
662        });
663        self.use_dma = false;
664        self.submit_command(cmd)?;
665        let inner = match direction {
666            DataDirection::Read => BlockRequestKind::FifoRead {
667                id,
668                buffer,
669                len,
670                block_size: block_size_usize,
671                offset: 0,
672                cmd_index: cmd.cmd,
673                phase,
674                stage: BlockRequestStage::Command,
675                stop_after_complete,
676                response: None,
677            },
678            DataDirection::Write => BlockRequestKind::FifoWrite {
679                id,
680                buffer,
681                len,
682                block_size: block_size_usize,
683                offset: 0,
684                cmd_index: cmd.cmd,
685                phase,
686                stage: BlockRequestStage::Command,
687                stop_after_complete,
688                response: None,
689            },
690            DataDirection::None => return Err(Error::InvalidArgument),
691            // Future DataDirection variants are not supported by this engine.
692            _ => return Err(Error::InvalidArgument),
693        };
694        Ok(BlockRequest { inner })
695    }
696
697    fn submit_adma2_blocks_mapped(
698        &mut self,
699        cmd: &Command,
700        block_count: u32,
701        buffer_dma: u64,
702        desc: &mut CoherentArray<Adma2Desc32>,
703        direction: DataDirection,
704        phase: Phase,
705    ) -> Result<(), Error> {
706        if block_count == 0 {
707            return Err(Error::InvalidArgument);
708        }
709        let byte_count = block_count
710            .checked_mul(BLOCK_SIZE as u32)
711            .ok_or(Error::InvalidArgument)? as usize;
712        build_descriptors_into_dma(desc, buffer_dma, byte_count, phase)?;
713
714        let desc_bus = desc.dma_addr().as_u64();
715        let desc_end = desc_bus
716            .checked_add(desc.bytes_len() as u64)
717            .ok_or(Error::InvalidArgument)?;
718        if desc_end > u32::MAX as u64 + 1 {
719            return Err(Error::BadResponse(ErrorContext::new(phase)));
720        }
721
722        self.pending_data = Some(PendingData {
723            direction,
724            block_size: BLOCK_SIZE as u32,
725            block_count,
726        });
727        self.use_dma = true;
728        self.select_adma2_32();
729        self.write_adma_addr(desc_bus as u32);
730        let response = self.submit_command(cmd);
731        self.use_dma = false;
732        response
733    }
734
735    fn finish_block_request(&mut self, request: BlockRequest) -> Result<(), Error> {
736        match request.inner {
737            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. } => {}
738            BlockRequestKind::Read { stage, .. } => {
739                if stage == BlockRequestStage::Command {
740                    let _ = self.take_command_response();
741                }
742            }
743            BlockRequestKind::Write { stage, .. } => {
744                if stage == BlockRequestStage::Command {
745                    let _ = self.take_command_response();
746                }
747            }
748        }
749        self.pending_data = None;
750        self.active_data_cmd = 0;
751        Ok(())
752    }
753
754    fn finish_dma_data(
755        &mut self,
756        request: &mut Option<BlockRequest>,
757        id: RequestId,
758        slot: &mut BlockRequestSlot,
759    ) -> Result<DataCommandPoll, Error> {
760        let Some(active) = request.as_mut() else {
761            return Err(Error::InvalidArgument);
762        };
763
764        let stop_after_complete = match &mut active.inner {
765            BlockRequestKind::Read {
766                map,
767                stop_after_complete,
768                stage,
769                ..
770            } => {
771                map.complete_for_cpu_all();
772                *stage = BlockRequestStage::Stop;
773                *stop_after_complete
774            }
775            BlockRequestKind::Write {
776                stop_after_complete,
777                stage,
778                ..
779            } => {
780                *stage = BlockRequestStage::Stop;
781                *stop_after_complete
782            }
783            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. } => {
784                return Err(Error::InvalidArgument);
785            }
786        };
787
788        if stop_after_complete {
789            self.submit_command(&sdmmc_protocol::cmd::CMD12)?;
790            return Ok(DataCommandPoll::Pending);
791        }
792
793        let active = request.take().ok_or(Error::InvalidArgument)?;
794        let response = active.response().ok_or(Error::InvalidArgument)?;
795        self.finish_block_request(active)?;
796        slot.complete(id)?;
797        Ok(DataCommandPoll::Complete(response))
798    }
799
800    fn poll_block_stop(
801        &mut self,
802        request: &mut Option<BlockRequest>,
803        id: RequestId,
804        slot: &mut BlockRequestSlot,
805    ) -> Result<DataCommandPoll, Error> {
806        match self.poll_command() {
807            Ok(CommandPoll::Pending) => Ok(DataCommandPoll::Pending),
808            Ok(CommandPoll::Complete) => {
809                let _ = self.take_command_response()?;
810                let active = request.take().ok_or(Error::InvalidArgument)?;
811                let response = active.response().ok_or(Error::InvalidArgument)?;
812                self.finish_block_request(active)?;
813                slot.complete(id)?;
814                Ok(DataCommandPoll::Complete(response))
815            }
816            // Future CommandPoll variants: best-effort, treat as still pending.
817            Ok(_) => Ok(DataCommandPoll::Pending),
818            Err(err) => {
819                self.abort_block_request(request, id, slot);
820                Err(err)
821            }
822        }
823    }
824
825    fn poll_fifo_request(
826        &mut self,
827        request: &mut Option<BlockRequest>,
828        id: RequestId,
829        slot: &mut BlockRequestSlot,
830    ) -> Result<DataCommandPoll, Error> {
831        let (cmd_index, phase, stage) = match request.as_ref().map(|request| &request.inner) {
832            Some(BlockRequestKind::FifoRead {
833                cmd_index,
834                phase,
835                stage,
836                ..
837            })
838            | Some(BlockRequestKind::FifoWrite {
839                cmd_index,
840                phase,
841                stage,
842                ..
843            }) => (*cmd_index, *phase, *stage),
844            _ => return Err(Error::InvalidArgument),
845        };
846
847        if stage == BlockRequestStage::Command {
848            match self.poll_command() {
849                Ok(CommandPoll::Pending) => return Ok(DataCommandPoll::Pending),
850                Ok(CommandPoll::Complete) => {
851                    let response = self.take_command_response()?;
852                    if let Some(active) = request.as_mut() {
853                        match &mut active.inner {
854                            BlockRequestKind::FifoRead {
855                                response: stored_response,
856                                ..
857                            }
858                            | BlockRequestKind::FifoWrite {
859                                response: stored_response,
860                                ..
861                            } => *stored_response = Some(response),
862                            _ => return Err(Error::InvalidArgument),
863                        }
864                    }
865                    set_fifo_stage(request, BlockRequestStage::Data)?;
866                    return Ok(DataCommandPoll::Pending);
867                }
868                // Future CommandPoll variants: best-effort, treat as still pending.
869                Ok(_) => return Ok(DataCommandPoll::Pending),
870                Err(err) => {
871                    self.abort_block_request(request, id, slot);
872                    return Err(err);
873                }
874            }
875        }
876
877        let stage = match request.as_ref().map(|request| &request.inner) {
878            Some(BlockRequestKind::FifoRead { stage, .. })
879            | Some(BlockRequestKind::FifoWrite { stage, .. }) => *stage,
880            _ => return Err(Error::InvalidArgument),
881        };
882
883        if stage == BlockRequestStage::Stop {
884            return self.poll_block_stop(request, id, slot);
885        }
886
887        match self.poll_fifo_data_step(request, cmd_index, phase) {
888            Ok(BlockPoll::Pending) => Ok(DataCommandPoll::Pending),
889            Ok(BlockPoll::Complete) => self.finish_fifo_data(request, id, slot),
890            // Future BlockPoll variants: best-effort, treat as still pending.
891            Ok(_) => Ok(DataCommandPoll::Pending),
892            Err(err) => {
893                self.abort_block_request(request, id, slot);
894                Err(err)
895            }
896        }
897    }
898
899    fn poll_fifo_data_step(
900        &mut self,
901        request: &mut Option<BlockRequest>,
902        cmd_index: u8,
903        phase: Phase,
904    ) -> Result<BlockPoll, Error> {
905        let Some(active) = request.as_mut() else {
906            return Err(Error::InvalidArgument);
907        };
908        match &mut active.inner {
909            BlockRequestKind::FifoRead {
910                buffer,
911                len,
912                block_size,
913                offset,
914                ..
915            } => poll_fifo_read_step(self, *buffer, *len, *block_size, offset, cmd_index, phase),
916            BlockRequestKind::FifoWrite {
917                buffer,
918                len,
919                block_size,
920                offset,
921                ..
922            } => poll_fifo_write_step(self, *buffer, *len, *block_size, offset, cmd_index, phase),
923            _ => Err(Error::InvalidArgument),
924        }
925    }
926
927    fn finish_fifo_data(
928        &mut self,
929        request: &mut Option<BlockRequest>,
930        id: RequestId,
931        slot: &mut BlockRequestSlot,
932    ) -> Result<DataCommandPoll, Error> {
933        let Some(active) = request.as_mut() else {
934            return Err(Error::InvalidArgument);
935        };
936        let stop_after_complete = match &mut active.inner {
937            BlockRequestKind::FifoRead {
938                stop_after_complete,
939                stage,
940                ..
941            }
942            | BlockRequestKind::FifoWrite {
943                stop_after_complete,
944                stage,
945                ..
946            } => {
947                *stage = BlockRequestStage::Stop;
948                *stop_after_complete
949            }
950            _ => return Err(Error::InvalidArgument),
951        };
952
953        if stop_after_complete {
954            self.submit_command(&sdmmc_protocol::cmd::CMD12)?;
955            return Ok(DataCommandPoll::Pending);
956        }
957
958        let active = request.take().ok_or(Error::InvalidArgument)?;
959        let response = active.response().ok_or(Error::InvalidArgument)?;
960        self.finish_block_request(active)?;
961        slot.complete(id)?;
962        Ok(DataCommandPoll::Complete(response))
963    }
964
965    fn abort_block_request(
966        &mut self,
967        request: &mut Option<BlockRequest>,
968        id: RequestId,
969        slot: &mut BlockRequestSlot,
970    ) {
971        let _ = request.take();
972        self.recover_after_adma2_error();
973        let _ = slot.complete(id);
974    }
975
976    fn recover_after_adma2_error(&mut self) {
977        self.use_dma = false;
978        self.pending_data = None;
979        self.active_data_cmd = 0;
980        self.command_state = CommandState::Idle;
981        self.write_u16(REG_NORMAL_INT_STATUS, NORMAL_INT_CLEAR_ALL);
982        self.write_u16(REG_ERROR_INT_STATUS, ERROR_INT_CLEAR_ALL);
983        let _ = self.reset_cmd();
984        let _ = self.reset_dat();
985    }
986
987    pub(crate) fn poll_data_complete_with_adma(
988        &mut self,
989        cmd_index: u8,
990        phase: Phase,
991    ) -> Result<BlockPoll, Error> {
992        let (status, err) = self.take_data_irq_status();
993        if status & NORMAL_INT_XFER_COMPLETE != 0 {
994            return Ok(BlockPoll::Complete);
995        }
996        if status & NORMAL_INT_ERROR != 0 {
997            let ctx = ErrorContext::for_cmd(phase, cmd_index);
998            return Err(if err & ERROR_INT_ADMA != 0 {
999                Error::Misaligned
1000            } else if err & (ERROR_INT_DATA_TIMEOUT | ERROR_INT_CMD_TIMEOUT) != 0 {
1001                Error::Timeout(ctx)
1002            } else if err & (ERROR_INT_DATA_CRC | ERROR_INT_CMD_CRC) != 0 {
1003                Error::Crc(ctx)
1004            } else if matches!(phase, Phase::DataRead) {
1005                Error::ReadError(ctx)
1006            } else {
1007                Error::WriteError(ctx)
1008            });
1009        }
1010        Ok(BlockPoll::Pending)
1011    }
1012}
1013
1014fn build_descriptors_into_dma(
1015    desc: &mut CoherentArray<Adma2Desc32>,
1016    base: u64,
1017    total_len: usize,
1018    phase: Phase,
1019) -> Result<usize, Error> {
1020    if desc.len() < ADMA2_DESC_COUNT {
1021        return Err(Error::InvalidArgument);
1022    }
1023    let mut table = [Adma2Desc32::default(); ADMA2_DESC_COUNT];
1024    let written = build_descriptors(&mut table, base, total_len, phase)?;
1025    desc.write_with_cpu(ADMA2_DESC_COUNT, |descs| {
1026        descs.copy_from_slice(&table);
1027    });
1028    Ok(written)
1029}
1030
1031fn set_fifo_stage(
1032    request: &mut Option<BlockRequest>,
1033    next: BlockRequestStage,
1034) -> Result<(), Error> {
1035    let Some(active) = request.as_mut() else {
1036        return Err(Error::InvalidArgument);
1037    };
1038    match &mut active.inner {
1039        BlockRequestKind::FifoRead { stage, .. } | BlockRequestKind::FifoWrite { stage, .. } => {
1040            *stage = next;
1041            Ok(())
1042        }
1043        _ => Err(Error::InvalidArgument),
1044    }
1045}
1046
1047fn poll_fifo_read_step(
1048    host: &mut Sdhci,
1049    buffer: NonNull<u8>,
1050    len: usize,
1051    block_size: usize,
1052    offset: &mut usize,
1053    cmd_index: u8,
1054    phase: Phase,
1055) -> Result<BlockPoll, Error> {
1056    if *offset >= len {
1057        return host.poll_data_complete_with_adma(cmd_index, phase);
1058    }
1059
1060    let status = host.take_fifo_irq_status(NORMAL_INT_BUFFER_READ_READY | NORMAL_INT_ERROR);
1061    if status & NORMAL_INT_BUFFER_READ_READY == 0 {
1062        return poll_fifo_status(host, status, cmd_index, phase, true);
1063    }
1064
1065    let end = (*offset + block_size).min(len);
1066    let block =
1067        unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr().add(*offset), end - *offset) };
1068    for word_chunk in block.chunks_mut(4) {
1069        let word = host.read_u32(REG_BUFFER_DATA_PORT);
1070        let bytes = word.to_le_bytes();
1071        for (i, b) in word_chunk.iter_mut().enumerate() {
1072            *b = bytes[i];
1073        }
1074    }
1075    *offset = end;
1076    Ok(BlockPoll::Pending)
1077}
1078
1079fn poll_fifo_write_step(
1080    host: &mut Sdhci,
1081    buffer: NonNull<u8>,
1082    len: usize,
1083    block_size: usize,
1084    offset: &mut usize,
1085    cmd_index: u8,
1086    phase: Phase,
1087) -> Result<BlockPoll, Error> {
1088    if *offset >= len {
1089        return host.poll_data_complete_with_adma(cmd_index, phase);
1090    }
1091
1092    let status = host.take_fifo_irq_status(NORMAL_INT_BUFFER_WRITE_READY | NORMAL_INT_ERROR);
1093    if status & NORMAL_INT_BUFFER_WRITE_READY == 0 {
1094        return poll_fifo_status(host, status, cmd_index, phase, false);
1095    }
1096
1097    let end = (*offset + block_size).min(len);
1098    let block = unsafe { core::slice::from_raw_parts(buffer.as_ptr().add(*offset), end - *offset) };
1099    for word_chunk in block.chunks(4) {
1100        let mut bytes = [0u8; 4];
1101        for (i, b) in word_chunk.iter().enumerate() {
1102            bytes[i] = *b;
1103        }
1104        host.write_u32(REG_BUFFER_DATA_PORT, u32::from_le_bytes(bytes));
1105    }
1106    *offset = end;
1107    Ok(BlockPoll::Pending)
1108}
1109
1110fn poll_fifo_status(
1111    host: &mut Sdhci,
1112    status: u16,
1113    cmd_index: u8,
1114    phase: Phase,
1115    read: bool,
1116) -> Result<BlockPoll, Error> {
1117    if status & NORMAL_INT_ERROR == 0 {
1118        return Ok(BlockPoll::Pending);
1119    }
1120
1121    host.log_status("data buffer error", cmd_index);
1122    let err = host.read_u16(REG_ERROR_INT_STATUS);
1123    host.write_u16(REG_NORMAL_INT_STATUS, NORMAL_INT_CLEAR_ALL);
1124    host.write_u16(REG_ERROR_INT_STATUS, ERROR_INT_CLEAR_ALL);
1125    let _ = host.reset_cmd();
1126    let _ = host.reset_dat();
1127    let ctx = ErrorContext::for_cmd(phase, cmd_index);
1128    Err(
1129        if err & (ERROR_INT_DATA_TIMEOUT | ERROR_INT_CMD_TIMEOUT) != 0 {
1130            Error::Timeout(ctx)
1131        } else if err & (ERROR_INT_DATA_CRC | ERROR_INT_CMD_CRC) != 0 {
1132            Error::Crc(ctx)
1133        } else if read {
1134            Error::ReadError(ctx)
1135        } else {
1136            Error::WriteError(ctx)
1137        },
1138    )
1139}
1140
1141fn dma_read_block_count(size: NonZeroUsize) -> Result<u32, Error> {
1142    let len = size.get();
1143    if !len.is_multiple_of(BLOCK_SIZE) {
1144        return Err(Error::Misaligned);
1145    }
1146    let blocks = len / BLOCK_SIZE;
1147    u32::try_from(blocks).map_err(|_| Error::InvalidArgument)
1148}
1149
1150fn dma_write_block_count(size: NonZeroUsize) -> Result<u32, Error> {
1151    dma_read_block_count(size)
1152}
1153
1154fn map_dma_error(err: dma_api::DmaError) -> Error {
1155    match err {
1156        dma_api::DmaError::NoMemory => Error::BusError(ErrorContext::new(Phase::DataRead)),
1157        dma_api::DmaError::LayoutError(_)
1158        | dma_api::DmaError::DmaMaskNotMatch { .. }
1159        | dma_api::DmaError::AlignMismatch { .. }
1160        | dma_api::DmaError::SegmentTooLarge { .. }
1161        | dma_api::DmaError::BoundaryCross { .. }
1162        | dma_api::DmaError::NullPointer
1163        | dma_api::DmaError::ZeroSizedBuffer => Error::InvalidArgument,
1164    }
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169    use super::*;
1170
1171    fn empty_table() -> [Adma2Desc32; ADMA2_DESC_COUNT] {
1172        [Adma2Desc32 {
1173            attr: 0,
1174            length: 0,
1175            address: 0,
1176        }; ADMA2_DESC_COUNT]
1177    }
1178
1179    #[test]
1180    fn single_descriptor_for_small_buffer() {
1181        let mut table = empty_table();
1182        let n = build_descriptors(&mut table, 0x1000_0000, 512, Phase::DataRead).unwrap();
1183        assert_eq!(n, 1);
1184        assert_eq!(table[0].length, 512);
1185        assert_eq!(table[0].address, 0x1000_0000);
1186        // Valid + End + Tran action
1187        assert_eq!(
1188            table[0].attr,
1189            ADMA2_ATTR_VALID | ADMA2_ATTR_END | ADMA2_ATTR_ACT_TRAN
1190        );
1191    }
1192
1193    #[test]
1194    fn splits_across_max_chunk() {
1195        let mut table = empty_table();
1196        let total = ADMA2_MAX_PER_DESC + 4096;
1197        let n = build_descriptors(&mut table, 0x2000_0000, total, Phase::DataRead).unwrap();
1198        assert_eq!(n, 2);
1199        assert_eq!(table[0].length as usize, ADMA2_MAX_PER_DESC);
1200        // first descriptor must NOT have END
1201        assert!(table[0].attr & ADMA2_ATTR_END == 0);
1202        // second descriptor covers the tail and has END
1203        assert_eq!(table[1].length, 4096);
1204        assert!(table[1].attr & ADMA2_ATTR_END != 0);
1205        assert_eq!(table[1].address, 0x2000_0000 + ADMA2_MAX_PER_DESC as u32);
1206    }
1207
1208    #[test]
1209    fn rejects_64bit_bus_address() {
1210        let mut table = empty_table();
1211        let err = build_descriptors(&mut table, 0x1_0000_0000, 512, Phase::DataRead).unwrap_err();
1212        assert!(matches!(err, Error::BadResponse(_)));
1213    }
1214
1215    #[test]
1216    fn rejects_zero_length() {
1217        let mut table = empty_table();
1218        let err = build_descriptors(&mut table, 0, 0, Phase::DataRead).unwrap_err();
1219        assert!(matches!(err, Error::Misaligned));
1220    }
1221
1222    #[test]
1223    fn sdhci_dma_read_plan_rejects_non_block_sized_buffers() {
1224        let size = core::num::NonZeroUsize::new(513).unwrap();
1225        assert_eq!(dma_read_block_count(size), Err(Error::Misaligned));
1226    }
1227
1228    #[test]
1229    fn sdhci_dma_read_plan_reports_block_count() {
1230        let size = core::num::NonZeroUsize::new(1024).unwrap();
1231        assert_eq!(dma_read_block_count(size), Ok(2));
1232    }
1233
1234    #[test]
1235    fn sdhci_dma_write_plan_rejects_non_block_sized_buffers() {
1236        let size = core::num::NonZeroUsize::new(513).unwrap();
1237        assert_eq!(dma_write_block_count(size), Err(Error::Misaligned));
1238    }
1239
1240    #[test]
1241    fn block_request_slot_rejects_second_request_until_completed() {
1242        let mut slot = BlockRequestSlot::default();
1243        let first = slot
1244            .start(BlockTransferMode::Dma, BlockTransferDirection::Read)
1245            .unwrap();
1246
1247        assert_eq!(
1248            slot.start(BlockTransferMode::Dma, BlockTransferDirection::Read),
1249            Err(Error::UnsupportedCommand)
1250        );
1251        assert_eq!(
1252            slot.complete(RequestId::new(usize::from(first) + 1)),
1253            Err(Error::InvalidArgument)
1254        );
1255        assert_eq!(slot.complete(first), Ok(()));
1256        assert!(
1257            slot.start(BlockTransferMode::Dma, BlockTransferDirection::Read)
1258                .is_ok()
1259        );
1260    }
1261
1262    #[test]
1263    fn block_request_can_cross_queue_thread_boundary() {
1264        fn assert_send<T: Send>() {}
1265
1266        assert_send::<BlockRequest>();
1267        assert_send::<BlockRequestSlot>();
1268    }
1269}