Skip to main content

dwmmc_host/
dma.rs

1use core::{num::NonZeroUsize, ptr::NonNull};
2
3use dma_api::{CoherentArray, DeviceDma, DmaDirection, StreamingMap};
4use log::warn;
5use sdmmc_protocol::{
6    block::{
7        BlockPoll, BlockRequestId, BlockTransferDirection, BlockTransferMode, BlockTransferState,
8        CommandPoll, DataCommandPoll,
9    },
10    cmd::{CMD12, Command, DataDirection, cmd17, cmd18, cmd24, cmd25},
11    error::{Error, ErrorContext, Phase},
12    response::Response,
13};
14
15use crate::{
16    host::{DwMmc, PendingData},
17    regs::RegisterBlockVolatileFieldAccess,
18};
19
20const DESC_OWN: u32 = 1 << 31;
21const DESC_CH: u32 = 1 << 4;
22const DESC_FS: u32 = 1 << 3;
23const DESC_LD: u32 = 1 << 2;
24const DESC_DIC: u32 = 1 << 1;
25
26const BMOD_SWR: u32 = 1 << 0;
27const BMOD_FB: u32 = 1 << 1;
28const BMOD_DE: u32 = 1 << 7;
29
30const DMA_POLL_LIMIT: u32 = 8_000_000;
31pub const IDMAC_DESC_ALIGN: usize = 16;
32pub const IDMAC_DESC_SIZE: usize = core::mem::size_of::<IdmacDesc>();
33const BLOCK_SIZE: usize = 512;
34
35pub type RequestId = BlockRequestId;
36
37#[derive(Default)]
38pub struct BlockRequestSlot {
39    next: usize,
40    state: BlockTransferState,
41}
42
43impl BlockRequestSlot {
44    pub fn start(
45        &mut self,
46        mode: BlockTransferMode,
47        direction: BlockTransferDirection,
48    ) -> Result<RequestId, Error> {
49        if !matches!(self.state, BlockTransferState::Idle) {
50            return Err(Error::UnsupportedCommand);
51        }
52        let id = RequestId::new(self.next);
53        self.next = self.next.wrapping_add(1);
54        self.state = BlockTransferState::Submitted {
55            id,
56            mode,
57            direction,
58        };
59        Ok(id)
60    }
61
62    pub fn complete(&mut self, id: RequestId) -> Result<(), Error> {
63        if self.state.id() != Some(id) {
64            return Err(Error::InvalidArgument);
65        }
66        self.state = BlockTransferState::Idle;
67        Ok(())
68    }
69
70    pub fn state(&self) -> BlockTransferState {
71        self.state
72    }
73}
74
75pub struct BlockRequest {
76    inner: BlockRequestKind,
77}
78
79// `BlockRequest` owns the DMA mappings and descriptor buffer for one
80// submitted transfer. Moving that ownership to another queue thread does not
81// grant shared access to the mapped memory; completion still requires a
82// mutable `DwMmc` reference and consumes the request.
83unsafe impl Send for BlockRequest {}
84
85enum BlockRequestKind {
86    FifoRead {
87        id: RequestId,
88        buffer: NonNull<u8>,
89        len: usize,
90        block_size: usize,
91        offset: usize,
92        cmd_index: u8,
93        phase: Phase,
94        stage: BlockRequestStage,
95        stop_after_complete: bool,
96        response: Option<Response>,
97    },
98    FifoWrite {
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    Read {
111        id: RequestId,
112        map: StreamingMap<u8>,
113        _desc: CoherentArray<IdmacDesc>,
114        cmd_index: u8,
115        phase: Phase,
116        stage: BlockRequestStage,
117        stop_after_complete: bool,
118        response: Option<Response>,
119    },
120    Write {
121        id: RequestId,
122        _map: StreamingMap<u8>,
123        _desc: CoherentArray<IdmacDesc>,
124        cmd_index: u8,
125        phase: Phase,
126        stage: BlockRequestStage,
127        stop_after_complete: bool,
128        response: Option<Response>,
129    },
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133enum BlockRequestStage {
134    Command,
135    Data,
136    Stop,
137}
138
139impl BlockRequest {
140    pub fn id(&self) -> RequestId {
141        match &self.inner {
142            BlockRequestKind::FifoRead { id, .. }
143            | BlockRequestKind::FifoWrite { id, .. }
144            | BlockRequestKind::Read { id, .. }
145            | BlockRequestKind::Write { id, .. } => *id,
146        }
147    }
148
149    pub fn state(&self) -> BlockTransferState {
150        match &self.inner {
151            BlockRequestKind::FifoRead { id, .. } => BlockTransferState::Submitted {
152                id: *id,
153                mode: BlockTransferMode::Fifo,
154                direction: BlockTransferDirection::Read,
155            },
156            BlockRequestKind::FifoWrite { id, .. } => BlockTransferState::Submitted {
157                id: *id,
158                mode: BlockTransferMode::Fifo,
159                direction: BlockTransferDirection::Write,
160            },
161            BlockRequestKind::Read { id, .. } => BlockTransferState::Submitted {
162                id: *id,
163                mode: BlockTransferMode::Dma,
164                direction: BlockTransferDirection::Read,
165            },
166            BlockRequestKind::Write { id, .. } => BlockTransferState::Submitted {
167                id: *id,
168                mode: BlockTransferMode::Dma,
169                direction: BlockTransferDirection::Write,
170            },
171        }
172    }
173
174    fn response(&self) -> Option<Response> {
175        match &self.inner {
176            BlockRequestKind::FifoRead { response, .. }
177            | BlockRequestKind::FifoWrite { response, .. }
178            | BlockRequestKind::Read { response, .. }
179            | BlockRequestKind::Write { response, .. } => *response,
180        }
181    }
182}
183
184#[repr(C, align(16))]
185#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
186pub struct IdmacDesc {
187    des0: u32,
188    des1: u32,
189    des2: u32,
190    des3: u32,
191}
192
193impl IdmacDesc {
194    pub fn chained(buffer_dma: u32, len: u32, next_desc_dma: u32, first: bool, last: bool) -> Self {
195        let mut des0 = DESC_OWN | DESC_CH | DESC_DIC;
196        if first {
197            des0 |= DESC_FS;
198        }
199        if last {
200            des0 |= DESC_LD;
201        }
202        Self {
203            des0,
204            des1: len,
205            des2: buffer_dma,
206            des3: next_desc_dma,
207        }
208    }
209}
210
211impl DwMmc {
212    /// Submit one block read using the requested transfer engine.
213    ///
214    /// Both `BlockTransferMode::Dma` and `BlockTransferMode::Fifo` use the
215    /// same submit/poll queue contract. Runtimes that cannot use DMA can
216    /// submit FIFO requests without changing the external block queue shape.
217    pub fn submit_read_blocks(
218        &mut self,
219        start_block: u32,
220        buffer: NonNull<u8>,
221        size: NonZeroUsize,
222        dma: Option<&DeviceDma>,
223        mode: BlockTransferMode,
224        slot: &mut BlockRequestSlot,
225    ) -> Result<BlockRequest, Error> {
226        let id = slot.start(mode, BlockTransferDirection::Read)?;
227        let result = match mode {
228            BlockTransferMode::Dma => {
229                let dma = dma.ok_or(Error::UnsupportedCommand)?;
230                self.build_dma_read_request(start_block, buffer, size, dma, id)
231            }
232            BlockTransferMode::Fifo => self.build_fifo_read_request(start_block, buffer, size, id),
233            // Future BlockTransferMode variants are not supported by this controller.
234            _ => Err(Error::UnsupportedCommand),
235        };
236        match result {
237            Ok(request) => Ok(request),
238            Err(err) => {
239                let _ = slot.complete(id);
240                Err(err)
241            }
242        }
243    }
244
245    /// Submit one block write using the requested transfer engine.
246    ///
247    /// See [`DwMmc::submit_read_blocks`] for the completion contract.
248    pub fn submit_write_blocks(
249        &mut self,
250        start_block: u32,
251        buffer: NonNull<u8>,
252        size: NonZeroUsize,
253        dma: Option<&DeviceDma>,
254        mode: BlockTransferMode,
255        slot: &mut BlockRequestSlot,
256    ) -> Result<BlockRequest, Error> {
257        let id = slot.start(mode, BlockTransferDirection::Write)?;
258        let result = match mode {
259            BlockTransferMode::Dma => {
260                let dma = dma.ok_or(Error::UnsupportedCommand)?;
261                self.build_dma_write_request(start_block, buffer, size, dma, id)
262            }
263            BlockTransferMode::Fifo => self.build_fifo_write_request(start_block, buffer, size, id),
264            // Future BlockTransferMode variants are not supported by this controller.
265            _ => Err(Error::UnsupportedCommand),
266        };
267        match result {
268            Ok(request) => Ok(request),
269            Err(err) => {
270                let _ = slot.complete(id);
271                Err(err)
272            }
273        }
274    }
275
276    /// Poll a previously submitted block request.
277    pub fn poll_block_request(
278        &mut self,
279        request: &mut Option<BlockRequest>,
280        id: RequestId,
281        slot: &mut BlockRequestSlot,
282    ) -> Result<BlockPoll, Error> {
283        match self.poll_block_request_response(request, id, slot)? {
284            DataCommandPoll::Pending => Ok(BlockPoll::Pending),
285            DataCommandPoll::Complete(_) => Ok(BlockPoll::Complete),
286            // Future DataCommandPoll variants are treated as completion.
287            _ => Ok(BlockPoll::Complete),
288        }
289    }
290
291    pub fn poll_block_request_response(
292        &mut self,
293        request: &mut Option<BlockRequest>,
294        id: RequestId,
295        slot: &mut BlockRequestSlot,
296    ) -> Result<DataCommandPoll, Error> {
297        let Some(active) = request.as_ref() else {
298            return Err(Error::InvalidArgument);
299        };
300        if active.id() != id {
301            return Err(Error::InvalidArgument);
302        }
303
304        if matches!(
305            active.inner,
306            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. }
307        ) {
308            return self.poll_fifo_request(request, id, slot);
309        }
310
311        let (cmd_index, phase, stage) = match &active.inner {
312            BlockRequestKind::Read {
313                cmd_index,
314                phase,
315                stage,
316                ..
317            }
318            | BlockRequestKind::Write {
319                cmd_index,
320                phase,
321                stage,
322                ..
323            } => (*cmd_index, *phase, *stage),
324            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. } => {
325                unreachable!()
326            }
327        };
328
329        if stage == BlockRequestStage::Command {
330            match self.poll_command() {
331                Ok(CommandPoll::Pending) => return Ok(DataCommandPoll::Pending),
332                Ok(CommandPoll::Complete) => {
333                    let response = self.take_command_response()?;
334                    if let Some(active) = request.as_mut() {
335                        match &mut active.inner {
336                            BlockRequestKind::Read {
337                                stage,
338                                response: stored_response,
339                                ..
340                            }
341                            | BlockRequestKind::Write {
342                                stage,
343                                response: stored_response,
344                                ..
345                            } => {
346                                *stage = BlockRequestStage::Data;
347                                *stored_response = Some(response);
348                            }
349                            BlockRequestKind::FifoRead { .. }
350                            | BlockRequestKind::FifoWrite { .. } => unreachable!(),
351                        }
352                    }
353                    return Ok(DataCommandPoll::Pending);
354                }
355                // Future CommandPoll variants: best-effort, treat as still pending.
356                Ok(_) => return Ok(DataCommandPoll::Pending),
357                Err(err) => {
358                    self.abort_block_request(request, id, slot, phase);
359                    return Err(err);
360                }
361            }
362        }
363
364        if stage == BlockRequestStage::Stop {
365            return self.poll_block_stop(request, id, slot, phase);
366        }
367
368        match self.poll_dma_complete(cmd_index, phase) {
369            Ok(BlockPoll::Pending) => Ok(DataCommandPoll::Pending),
370            Ok(BlockPoll::Complete) => self.finish_dma_data(request, id, slot),
371            // Future BlockPoll variants: best-effort, treat as still pending.
372            Ok(_) => Ok(DataCommandPoll::Pending),
373            Err(err) => {
374                self.abort_block_request(request, id, slot, phase);
375                Err(err)
376            }
377        }
378    }
379
380    fn build_dma_read_request(
381        &mut self,
382        start_block: u32,
383        buffer: NonNull<u8>,
384        size: NonZeroUsize,
385        dma: &DeviceDma,
386        id: RequestId,
387    ) -> Result<BlockRequest, Error> {
388        let block_count = dma_read_block_count(size)?;
389        let map = dma
390            .map_streaming_slice_for_device(
391                unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) },
392                BLOCK_SIZE,
393                DmaDirection::FromDevice,
394            )
395            .map_err(|err| map_dma_error(err, Phase::DataRead))?;
396        let mut desc = dma
397            .coherent_array_zero_with_align::<IdmacDesc>(block_count as usize, IDMAC_DESC_ALIGN)
398            .map_err(|err| map_dma_error(err, Phase::DataRead))?;
399        let cmd = if block_count == 1 {
400            cmd17(start_block)
401        } else {
402            cmd18(start_block)
403        };
404        self.submit_idmac_transfer_mapped(&cmd, block_count, map.dma_addr().as_u64(), &mut desc)?;
405        Ok(BlockRequest {
406            inner: BlockRequestKind::Read {
407                id,
408                map,
409                _desc: desc,
410                cmd_index: cmd.cmd,
411                phase: Phase::DataRead,
412                stage: BlockRequestStage::Command,
413                stop_after_complete: block_count > 1,
414                response: None,
415            },
416        })
417    }
418
419    fn build_dma_write_request(
420        &mut self,
421        start_block: u32,
422        buffer: NonNull<u8>,
423        size: NonZeroUsize,
424        dma: &DeviceDma,
425        id: RequestId,
426    ) -> Result<BlockRequest, Error> {
427        let block_count = dma_write_block_count(size)?;
428        let map = dma
429            .map_streaming_slice_for_device(
430                unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) },
431                BLOCK_SIZE,
432                DmaDirection::ToDevice,
433            )
434            .map_err(|err| map_dma_error(err, Phase::DataWrite))?;
435        let mut desc = dma
436            .coherent_array_zero_with_align::<IdmacDesc>(block_count as usize, IDMAC_DESC_ALIGN)
437            .map_err(|err| map_dma_error(err, Phase::DataWrite))?;
438        let cmd = if block_count == 1 {
439            cmd24(start_block)
440        } else {
441            cmd25(start_block)
442        };
443        self.submit_idmac_transfer_mapped(&cmd, block_count, map.dma_addr().as_u64(), &mut desc)?;
444        Ok(BlockRequest {
445            inner: BlockRequestKind::Write {
446                id,
447                _map: map,
448                _desc: desc,
449                cmd_index: cmd.cmd,
450                phase: Phase::DataWrite,
451                stage: BlockRequestStage::Command,
452                stop_after_complete: block_count > 1,
453                response: None,
454            },
455        })
456    }
457
458    fn build_fifo_read_request(
459        &mut self,
460        start_block: u32,
461        buffer: NonNull<u8>,
462        size: NonZeroUsize,
463        id: RequestId,
464    ) -> Result<BlockRequest, Error> {
465        let block_count = dma_read_block_count(size)?;
466        let cmd = if block_count == 1 {
467            cmd17(start_block)
468        } else {
469            cmd18(start_block)
470        };
471        self.build_fifo_data_request(
472            &cmd,
473            buffer,
474            size.get(),
475            BLOCK_SIZE as u32,
476            block_count,
477            id,
478            DataDirection::Read,
479            block_count > 1,
480        )
481    }
482
483    fn build_fifo_write_request(
484        &mut self,
485        start_block: u32,
486        buffer: NonNull<u8>,
487        size: NonZeroUsize,
488        id: RequestId,
489    ) -> Result<BlockRequest, Error> {
490        let block_count = dma_write_block_count(size)?;
491        let cmd = if block_count == 1 {
492            cmd24(start_block)
493        } else {
494            cmd25(start_block)
495        };
496        self.build_fifo_data_request(
497            &cmd,
498            buffer,
499            size.get(),
500            BLOCK_SIZE as u32,
501            block_count,
502            id,
503            DataDirection::Write,
504            block_count > 1,
505        )
506    }
507
508    #[allow(clippy::too_many_arguments)]
509    pub fn submit_fifo_data_request(
510        &mut self,
511        cmd: &Command,
512        buffer: NonNull<u8>,
513        len: usize,
514        block_size: u32,
515        block_count: u32,
516        direction: DataDirection,
517        slot: &mut BlockRequestSlot,
518    ) -> Result<BlockRequest, Error> {
519        let transfer_direction = match direction {
520            DataDirection::Read => BlockTransferDirection::Read,
521            DataDirection::Write => BlockTransferDirection::Write,
522            DataDirection::None => return Err(Error::InvalidArgument),
523            // Future DataDirection variants are not supported by this engine.
524            _ => return Err(Error::InvalidArgument),
525        };
526        let id = slot.start(BlockTransferMode::Fifo, transfer_direction)?;
527        match self.build_fifo_data_request(
528            cmd,
529            buffer,
530            len,
531            block_size,
532            block_count,
533            id,
534            direction,
535            false,
536        ) {
537            Ok(request) => Ok(request),
538            Err(err) => {
539                let _ = slot.complete(id);
540                Err(err)
541            }
542        }
543    }
544
545    #[allow(clippy::too_many_arguments)]
546    fn build_fifo_data_request(
547        &mut self,
548        cmd: &Command,
549        buffer: NonNull<u8>,
550        len: usize,
551        block_size: u32,
552        block_count: u32,
553        id: RequestId,
554        direction: DataDirection,
555        stop_after_complete: bool,
556    ) -> Result<BlockRequest, Error> {
557        let block_size_usize = usize::try_from(block_size).map_err(|_| Error::InvalidArgument)?;
558        let block_count_usize = usize::try_from(block_count).map_err(|_| Error::InvalidArgument)?;
559        if block_size_usize == 0
560            || block_count_usize == 0
561            || len != block_size_usize.saturating_mul(block_count_usize)
562        {
563            return Err(Error::InvalidArgument);
564        }
565        let phase = match direction {
566            DataDirection::Read => Phase::DataRead,
567            DataDirection::Write => Phase::DataWrite,
568            DataDirection::None => return Err(Error::InvalidArgument),
569            // Future DataDirection variants are not supported by this engine.
570            _ => return Err(Error::InvalidArgument),
571        };
572        self.pending_data = Some(PendingData {
573            direction,
574            block_size,
575            block_count,
576        });
577        self.data_blocks_remaining = block_count;
578        self.submit_command(cmd)?;
579        let inner = match direction {
580            DataDirection::Read => BlockRequestKind::FifoRead {
581                id,
582                buffer,
583                len,
584                block_size: block_size_usize,
585                offset: 0,
586                cmd_index: cmd.cmd,
587                phase,
588                stage: BlockRequestStage::Command,
589                stop_after_complete,
590                response: None,
591            },
592            DataDirection::Write => BlockRequestKind::FifoWrite {
593                id,
594                buffer,
595                len,
596                block_size: block_size_usize,
597                offset: 0,
598                cmd_index: cmd.cmd,
599                phase,
600                stage: BlockRequestStage::Command,
601                stop_after_complete,
602                response: None,
603            },
604            DataDirection::None => return Err(Error::InvalidArgument),
605            // Future DataDirection variants are not supported by this engine.
606            _ => return Err(Error::InvalidArgument),
607        };
608        Ok(BlockRequest { inner })
609    }
610
611    fn submit_idmac_transfer_mapped(
612        &mut self,
613        cmd: &Command,
614        block_count: u32,
615        buffer_dma: u64,
616        desc: &mut CoherentArray<IdmacDesc>,
617    ) -> Result<(), Error> {
618        if block_count == 0 {
619            return Err(Error::InvalidArgument);
620        }
621        let direction = cmd.data_direction();
622        let phase = match direction {
623            DataDirection::Read => Phase::DataRead,
624            DataDirection::Write => Phase::DataWrite,
625            DataDirection::None => return Err(Error::InvalidArgument),
626            // Future DataDirection variants are not supported by this engine.
627            _ => return Err(Error::InvalidArgument),
628        };
629        let byte_count = block_count
630            .checked_mul(BLOCK_SIZE as u32)
631            .ok_or(Error::InvalidArgument)?;
632        let transfer_end = buffer_dma
633            .checked_add(byte_count as u64)
634            .ok_or(Error::InvalidArgument)?;
635        let desc_bytes = (block_count as usize)
636            .checked_mul(IDMAC_DESC_SIZE)
637            .ok_or(Error::InvalidArgument)?;
638        let desc_dma = desc.dma_addr().as_u64();
639        let desc_end = desc_dma
640            .checked_add(desc_bytes as u64)
641            .ok_or(Error::InvalidArgument)?;
642        if transfer_end > u32::MAX as u64 + 1
643            || desc_end > u32::MAX as u64 + 1
644            || desc.len() < block_count as usize
645        {
646            return Err(Error::InvalidArgument);
647        }
648
649        desc.write_with_cpu(block_count as usize, |descs| {
650            for (index, desc) in descs.iter_mut().enumerate() {
651                let last = index + 1 == block_count as usize;
652                let next = if last {
653                    0
654                } else {
655                    (desc_dma as u32) + ((index + 1) * IDMAC_DESC_SIZE) as u32
656                };
657                *desc = IdmacDesc::chained(
658                    (buffer_dma as u32) + (index * BLOCK_SIZE) as u32,
659                    BLOCK_SIZE as u32,
660                    next,
661                    index == 0,
662                    last,
663                );
664            }
665        });
666
667        self.clear_all_int_status();
668        self.irq_pending_status = 0;
669        self.program_data_phase(BLOCK_SIZE as u32, block_count);
670        self.reset_dma_for_phase(phase)?;
671
672        self.regs.dbaddr().write(desc_dma as u32);
673        self.regs.ctrl().update(|r| {
674            r.with_use_internal_dmac(true)
675                .with_dma_enable(true)
676                .with_int_enable(self.completion_irq_enabled)
677        });
678        self.regs.bmod().write(BMOD_FB | BMOD_DE);
679        self.regs.pldmnd().write(1);
680
681        self.pending_data = Some(PendingData {
682            direction,
683            block_size: BLOCK_SIZE as u32,
684            block_count,
685        });
686        self.data_blocks_remaining = block_count;
687        match self.submit_command(cmd) {
688            Ok(()) => Ok(()),
689            Err(err) => {
690                self.disable_idmac();
691                self.recover_after_idmac_error(phase);
692                self.clear_all_int_status();
693                Err(err)
694            }
695        }
696    }
697
698    fn finish_block_request(&mut self, request: BlockRequest) -> Result<(), Error> {
699        match request.inner {
700            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. } => {
701                self.pending_data = None;
702                self.data_blocks_remaining = 0;
703                self.data_cmd_index = 0;
704            }
705            BlockRequestKind::Read { stage, .. } => {
706                if stage == BlockRequestStage::Command {
707                    let _ = self.take_command_response();
708                }
709                self.disable_idmac();
710                self.clear_all_int_status();
711                self.pending_data = None;
712                self.data_blocks_remaining = 0;
713                self.data_cmd_index = 0;
714            }
715            BlockRequestKind::Write { stage, .. } => {
716                if stage == BlockRequestStage::Command {
717                    let _ = self.take_command_response();
718                }
719                self.disable_idmac();
720                self.clear_all_int_status();
721                self.pending_data = None;
722                self.data_blocks_remaining = 0;
723                self.data_cmd_index = 0;
724            }
725        }
726        Ok(())
727    }
728
729    fn finish_dma_data(
730        &mut self,
731        request: &mut Option<BlockRequest>,
732        id: RequestId,
733        slot: &mut BlockRequestSlot,
734    ) -> Result<DataCommandPoll, Error> {
735        let Some(active) = request.as_mut() else {
736            return Err(Error::InvalidArgument);
737        };
738        let stop_after_complete = match &mut active.inner {
739            BlockRequestKind::Read {
740                map,
741                stage,
742                stop_after_complete,
743                ..
744            } => {
745                map.complete_for_cpu_all();
746                *stage = BlockRequestStage::Stop;
747                *stop_after_complete
748            }
749            BlockRequestKind::Write {
750                stage,
751                stop_after_complete,
752                ..
753            } => {
754                *stage = BlockRequestStage::Stop;
755                *stop_after_complete
756            }
757            BlockRequestKind::FifoRead { .. } | BlockRequestKind::FifoWrite { .. } => {
758                return Err(Error::InvalidArgument);
759            }
760        };
761
762        if stop_after_complete {
763            self.submit_command(&CMD12)?;
764            return Ok(DataCommandPoll::Pending);
765        }
766
767        let active = request.take().ok_or(Error::InvalidArgument)?;
768        let response = active.response().ok_or(Error::InvalidArgument)?;
769        self.finish_block_request(active)?;
770        slot.complete(id)?;
771        Ok(DataCommandPoll::Complete(response))
772    }
773
774    fn poll_block_stop(
775        &mut self,
776        request: &mut Option<BlockRequest>,
777        id: RequestId,
778        slot: &mut BlockRequestSlot,
779        phase: Phase,
780    ) -> Result<DataCommandPoll, Error> {
781        match self.poll_command() {
782            Ok(CommandPoll::Pending) => Ok(DataCommandPoll::Pending),
783            Ok(CommandPoll::Complete) => {
784                let _ = self.take_command_response()?;
785                let active = request.take().ok_or(Error::InvalidArgument)?;
786                let response = active.response().ok_or(Error::InvalidArgument)?;
787                self.finish_block_request(active)?;
788                slot.complete(id)?;
789                Ok(DataCommandPoll::Complete(response))
790            }
791            // Future CommandPoll variants: best-effort, treat as still pending.
792            Ok(_) => Ok(DataCommandPoll::Pending),
793            Err(err) => {
794                self.abort_block_request(request, id, slot, phase);
795                Err(err)
796            }
797        }
798    }
799
800    fn poll_fifo_request(
801        &mut self,
802        request: &mut Option<BlockRequest>,
803        id: RequestId,
804        slot: &mut BlockRequestSlot,
805    ) -> Result<DataCommandPoll, Error> {
806        let (cmd_index, phase, stage) = match request.as_ref().map(|request| &request.inner) {
807            Some(BlockRequestKind::FifoRead {
808                cmd_index,
809                phase,
810                stage,
811                ..
812            })
813            | Some(BlockRequestKind::FifoWrite {
814                cmd_index,
815                phase,
816                stage,
817                ..
818            }) => (*cmd_index, *phase, *stage),
819            _ => return Err(Error::InvalidArgument),
820        };
821
822        if stage == BlockRequestStage::Command {
823            match self.poll_command() {
824                Ok(CommandPoll::Pending) => return Ok(DataCommandPoll::Pending),
825                Ok(CommandPoll::Complete) => {
826                    let response = self.take_command_response()?;
827                    if let Some(active) = request.as_mut() {
828                        match &mut active.inner {
829                            BlockRequestKind::FifoRead {
830                                response: stored_response,
831                                ..
832                            }
833                            | BlockRequestKind::FifoWrite {
834                                response: stored_response,
835                                ..
836                            } => *stored_response = Some(response),
837                            _ => return Err(Error::InvalidArgument),
838                        }
839                    }
840                    set_fifo_stage(request, BlockRequestStage::Data)?;
841                    return Ok(DataCommandPoll::Pending);
842                }
843                // Future CommandPoll variants: best-effort, treat as still pending.
844                Ok(_) => return Ok(DataCommandPoll::Pending),
845                Err(err) => {
846                    self.abort_block_request(request, id, slot, phase);
847                    return Err(err);
848                }
849            }
850        }
851
852        let stage = match request.as_ref().map(|request| &request.inner) {
853            Some(BlockRequestKind::FifoRead { stage, .. })
854            | Some(BlockRequestKind::FifoWrite { stage, .. }) => *stage,
855            _ => return Err(Error::InvalidArgument),
856        };
857        if stage == BlockRequestStage::Stop {
858            return self.poll_block_stop(request, id, slot, phase);
859        }
860
861        match self.poll_fifo_data_step(request, cmd_index, phase) {
862            Ok(BlockPoll::Pending) => Ok(DataCommandPoll::Pending),
863            Ok(BlockPoll::Complete) => self.finish_fifo_data(request, id, slot),
864            // Future BlockPoll variants: best-effort, treat as still pending.
865            Ok(_) => Ok(DataCommandPoll::Pending),
866            Err(err) => {
867                self.abort_block_request(request, id, slot, phase);
868                Err(err)
869            }
870        }
871    }
872
873    fn poll_fifo_data_step(
874        &mut self,
875        request: &mut Option<BlockRequest>,
876        cmd_index: u8,
877        phase: Phase,
878    ) -> Result<BlockPoll, Error> {
879        let Some(active) = request.as_mut() else {
880            return Err(Error::InvalidArgument);
881        };
882        match &mut active.inner {
883            BlockRequestKind::FifoRead {
884                buffer,
885                len,
886                block_size,
887                offset,
888                ..
889            } => poll_fifo_read_step(self, *buffer, *len, *block_size, offset, cmd_index, phase),
890            BlockRequestKind::FifoWrite {
891                buffer,
892                len,
893                block_size,
894                offset,
895                ..
896            } => poll_fifo_write_step(self, *buffer, *len, *block_size, offset, cmd_index, phase),
897            _ => Err(Error::InvalidArgument),
898        }
899    }
900
901    fn finish_fifo_data(
902        &mut self,
903        request: &mut Option<BlockRequest>,
904        id: RequestId,
905        slot: &mut BlockRequestSlot,
906    ) -> Result<DataCommandPoll, Error> {
907        let Some(active) = request.as_mut() else {
908            return Err(Error::InvalidArgument);
909        };
910        let stop_after_complete = match &mut active.inner {
911            BlockRequestKind::FifoRead {
912                stage,
913                stop_after_complete,
914                ..
915            }
916            | BlockRequestKind::FifoWrite {
917                stage,
918                stop_after_complete,
919                ..
920            } => {
921                *stage = BlockRequestStage::Stop;
922                *stop_after_complete
923            }
924            _ => return Err(Error::InvalidArgument),
925        };
926        if stop_after_complete {
927            self.submit_command(&CMD12)?;
928            return Ok(DataCommandPoll::Pending);
929        }
930
931        let active = request.take().ok_or(Error::InvalidArgument)?;
932        let response = active.response().ok_or(Error::InvalidArgument)?;
933        self.finish_block_request(active)?;
934        self.pending_data = None;
935        self.data_blocks_remaining = 0;
936        self.data_cmd_index = 0;
937        slot.complete(id)?;
938        Ok(DataCommandPoll::Complete(response))
939    }
940
941    fn abort_block_request(
942        &mut self,
943        request: &mut Option<BlockRequest>,
944        id: RequestId,
945        slot: &mut BlockRequestSlot,
946        phase: Phase,
947    ) {
948        let _ = request.take();
949        self.disable_idmac();
950        self.recover_after_idmac_error(phase);
951        self.clear_all_int_status();
952        let _ = slot.complete(id);
953    }
954
955    fn disable_idmac(&self) {
956        self.regs.ctrl().update(|r| {
957            r.with_use_internal_dmac(false)
958                .with_dma_enable(false)
959                .with_int_enable(false)
960        });
961        self.regs.bmod().write(0);
962    }
963
964    fn recover_after_idmac_error(&mut self, phase: Phase) {
965        let status = self.regs.status().read().into_bits();
966        let rintsts = self.regs.rintsts().read();
967        warn!(
968            "dwmmc: IDMAC {:?} error state rintsts={:#010x} status={:#010x} tcbcnt={} tbbcnt={}",
969            phase,
970            rintsts.into_bits(),
971            status,
972            self.regs.tcbcnt().read(),
973            self.regs.tbbcnt().read()
974        );
975
976        self.regs.ctrl().update(|r| r.with_abort_read_data(true));
977        let _ = self.regs.ctrl().read();
978        let _ = self.reset_fifo();
979        let _ = self.reset_dma();
980        self.regs.ctrl().update(|r| r.with_abort_read_data(false));
981        self.pending_data = None;
982        self.data_blocks_remaining = 0;
983        self.data_cmd_index = 0;
984    }
985
986    fn reset_dma(&self) -> Result<(), Error> {
987        self.reset_dma_for_phase(Phase::DataRead)
988    }
989
990    fn reset_dma_for_phase(&self, phase: Phase) -> Result<(), Error> {
991        self.regs.ctrl().update(|r| r.with_dma_reset(true));
992        for _ in 0..DMA_POLL_LIMIT {
993            if !self.regs.ctrl().read().dma_reset() {
994                self.regs.bmod().write(BMOD_SWR);
995                for _ in 0..DMA_POLL_LIMIT {
996                    if self.regs.bmod().read() & BMOD_SWR == 0 {
997                        return Ok(());
998                    }
999                    core::hint::spin_loop();
1000                }
1001                break;
1002            }
1003            core::hint::spin_loop();
1004        }
1005        Err(Error::Timeout(ErrorContext::new(phase)))
1006    }
1007
1008    fn poll_dma_complete(&mut self, cmd_index: u8, phase: Phase) -> Result<BlockPoll, Error> {
1009        let raw_status = self.take_data_irq_status();
1010        let rintsts = crate::regs::RIntSts::from_bits(raw_status);
1011        if rintsts.error() {
1012            return Err(self.translate_int_error(rintsts, phase, cmd_index));
1013        }
1014        if rintsts.data_transfer_over() {
1015            return Ok(BlockPoll::Complete);
1016        }
1017        Ok(BlockPoll::Pending)
1018    }
1019
1020    fn take_data_irq_status(&mut self) -> u32 {
1021        let raw_status = self.regs.rintsts().read().into_bits();
1022        let consume = raw_status
1023            & (crate::DWMMC_INT_DATA_TRANSFER_OVER
1024                | crate::DWMMC_INT_COMMAND_DONE
1025                | crate::DWMMC_INT_ERROR_MASK);
1026        if consume != 0 {
1027            self.regs
1028                .rintsts()
1029                .write(crate::regs::RIntSts::from_bits(consume));
1030        }
1031        let status = self.irq_pending_status | raw_status;
1032        self.irq_pending_status &= !(crate::DWMMC_INT_DATA_TRANSFER_OVER
1033            | crate::DWMMC_INT_COMMAND_DONE
1034            | crate::DWMMC_INT_ERROR_MASK);
1035        status
1036    }
1037}
1038
1039fn set_fifo_stage(
1040    request: &mut Option<BlockRequest>,
1041    next: BlockRequestStage,
1042) -> Result<(), Error> {
1043    let Some(active) = request.as_mut() else {
1044        return Err(Error::InvalidArgument);
1045    };
1046    match &mut active.inner {
1047        BlockRequestKind::FifoRead { stage, .. } | BlockRequestKind::FifoWrite { stage, .. } => {
1048            *stage = next;
1049            Ok(())
1050        }
1051        _ => Err(Error::InvalidArgument),
1052    }
1053}
1054
1055fn poll_fifo_read_step(
1056    host: &mut DwMmc,
1057    buffer: NonNull<u8>,
1058    len: usize,
1059    _block_size: usize,
1060    offset: &mut usize,
1061    cmd_index: u8,
1062    phase: Phase,
1063) -> Result<BlockPoll, Error> {
1064    let raw_status = host.take_data_irq_status();
1065    let rintsts = crate::regs::RIntSts::from_bits(raw_status);
1066    if rintsts.error() {
1067        let err = host.translate_int_error(rintsts, phase, cmd_index);
1068        let _ = host.reset_fifo();
1069        return Err(err);
1070    }
1071
1072    let fifo = host.fifo_ptr();
1073    let mut status = host.regs.status().read();
1074    while *offset < len && status.fifo_count() >= 2 {
1075        let value = unsafe { fifo.read_volatile() };
1076        let end = (*offset + 8).min(len);
1077        let block =
1078            unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr().add(*offset), end - *offset) };
1079        block.copy_from_slice(&value.to_le_bytes()[..block.len()]);
1080        *offset = end;
1081        status = host.regs.status().read();
1082    }
1083
1084    if *offset >= len && rintsts.data_transfer_over() {
1085        return Ok(BlockPoll::Complete);
1086    }
1087    Ok(BlockPoll::Pending)
1088}
1089
1090fn poll_fifo_write_step(
1091    host: &mut DwMmc,
1092    buffer: NonNull<u8>,
1093    len: usize,
1094    _block_size: usize,
1095    offset: &mut usize,
1096    cmd_index: u8,
1097    phase: Phase,
1098) -> Result<BlockPoll, Error> {
1099    let raw_status = host.take_data_irq_status();
1100    let rintsts = crate::regs::RIntSts::from_bits(raw_status);
1101    if rintsts.error() {
1102        let err = host.translate_int_error(rintsts, phase, cmd_index);
1103        let _ = host.reset_fifo();
1104        return Err(err);
1105    }
1106
1107    let fifo = host.fifo_ptr();
1108    while *offset < len && !host.regs.status().read().fifo_full() {
1109        let end = (*offset + 8).min(len);
1110        let block =
1111            unsafe { core::slice::from_raw_parts(buffer.as_ptr().add(*offset), end - *offset) };
1112        let mut bytes = [0u8; 8];
1113        bytes[..block.len()].copy_from_slice(block);
1114        unsafe { fifo.write_volatile(u64::from_le_bytes(bytes)) };
1115        *offset = end;
1116    }
1117
1118    if *offset >= len && rintsts.data_transfer_over() {
1119        return Ok(BlockPoll::Complete);
1120    }
1121    Ok(BlockPoll::Pending)
1122}
1123
1124fn dma_read_block_count(size: NonZeroUsize) -> Result<u32, Error> {
1125    let len = size.get();
1126    if !len.is_multiple_of(BLOCK_SIZE) {
1127        return Err(Error::Misaligned);
1128    }
1129    let blocks = len / BLOCK_SIZE;
1130    u32::try_from(blocks).map_err(|_| Error::InvalidArgument)
1131}
1132
1133fn dma_write_block_count(size: NonZeroUsize) -> Result<u32, Error> {
1134    dma_read_block_count(size)
1135}
1136
1137fn map_dma_error(err: dma_api::DmaError, phase: Phase) -> Error {
1138    match err {
1139        dma_api::DmaError::NoMemory => Error::BusError(ErrorContext::new(phase)),
1140        dma_api::DmaError::LayoutError(_)
1141        | dma_api::DmaError::DmaMaskNotMatch { .. }
1142        | dma_api::DmaError::AlignMismatch { .. }
1143        | dma_api::DmaError::SegmentTooLarge { .. }
1144        | dma_api::DmaError::BoundaryCross { .. }
1145        | dma_api::DmaError::NullPointer
1146        | dma_api::DmaError::ZeroSizedBuffer => Error::InvalidArgument,
1147    }
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152    use super::*;
1153
1154    #[test]
1155    fn first_descriptor_sets_owned_chained_first_read_buffer() {
1156        let desc = IdmacDesc::chained(0x1234_5000, 512, 0x2000, true, false);
1157
1158        assert_eq!(desc.des0, DESC_OWN | DESC_CH | DESC_FS | DESC_DIC);
1159        assert_eq!(desc.des1, 512);
1160        assert_eq!(desc.des2, 0x1234_5000);
1161        assert_eq!(desc.des3, 0x2000);
1162    }
1163
1164    #[test]
1165    fn last_descriptor_sets_last_and_terminates_chain() {
1166        let desc = IdmacDesc::chained(0x1234_5200, 512, 0, false, true);
1167
1168        assert_eq!(desc.des0, DESC_OWN | DESC_CH | DESC_LD | DESC_DIC);
1169        assert_eq!(desc.des1, 512);
1170        assert_eq!(desc.des2, 0x1234_5200);
1171        assert_eq!(desc.des3, 0);
1172    }
1173
1174    #[test]
1175    fn dma_read_plan_rejects_non_block_sized_buffers() {
1176        let size = NonZeroUsize::new(513).unwrap();
1177
1178        assert_eq!(dma_read_block_count(size), Err(Error::Misaligned));
1179    }
1180
1181    #[test]
1182    fn dma_read_plan_reports_block_count() {
1183        let size = NonZeroUsize::new(1024).unwrap();
1184
1185        assert_eq!(dma_read_block_count(size), Ok(2));
1186    }
1187
1188    #[test]
1189    fn dma_write_plan_rejects_non_block_sized_buffers() {
1190        let size = NonZeroUsize::new(513).unwrap();
1191
1192        assert_eq!(dma_write_block_count(size), Err(Error::Misaligned));
1193    }
1194
1195    #[test]
1196    fn block_request_slot_rejects_second_request_until_completed() {
1197        let mut slot = BlockRequestSlot::default();
1198        let first = slot
1199            .start(BlockTransferMode::Dma, BlockTransferDirection::Read)
1200            .unwrap();
1201
1202        assert_eq!(
1203            slot.start(BlockTransferMode::Dma, BlockTransferDirection::Read),
1204            Err(Error::UnsupportedCommand)
1205        );
1206        assert_eq!(
1207            slot.complete(RequestId::new(usize::from(first) + 1)),
1208            Err(Error::InvalidArgument)
1209        );
1210        assert_eq!(slot.complete(first), Ok(()));
1211        assert!(
1212            slot.start(BlockTransferMode::Dma, BlockTransferDirection::Read)
1213                .is_ok()
1214        );
1215    }
1216
1217    #[test]
1218    fn block_request_can_cross_queue_thread_boundary() {
1219        fn assert_send<T: Send>() {}
1220
1221        assert_send::<BlockRequest>();
1222        assert_send::<BlockRequestSlot>();
1223    }
1224}