riscv/
dma_fence.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Leon Schuermann <leon@is.currently.online> 2026.
4// Copyright Tock Contributors 2026.
5
6use kernel::platform::dma_fence::DmaFence;
7
8/// An implementation of [`DmaFence`] for RISC-V systems with cache-coherent DMA
9/// memory accesses.
10///
11/// The provided `release` and `acquire` methods use opaque assembly blocks and
12/// RISC-V `FENCE` instructions to make prior writes to DMA buffers visible to
13/// DMA devices, and DMA writes visible to prior memory reads, respectively.
14///
15/// These primitives are sufficient to implement the `release` / `acquire`
16/// semantics of [`DmaFence`] for cache-coherent platforms where all memory
17/// writes written back are immediately visible to DMA devices, and all DMA
18/// writes are immediately visible to CPU fetches.
19///
20/// For platforms where explicit cache-flush instructions are required, this
21/// implementation alone is insufficient and must be extended with the necessary
22/// platform-specific instructions.
23#[derive(Debug, Copy, Clone)]
24pub struct RiscvCoherentDmaFence {
25    _private: (),
26}
27
28impl RiscvCoherentDmaFence {
29    /// Construct a new [`RiscvCoherentDmaFence`].
30    ///
31    /// Refer to the [type-level documentation](Self) and the documentation of
32    /// the [`DmaFence` trait](DmaFence) and [its implementation for
33    /// `RiscvCoherentDmaFence`](<RiscvCoherentDmaFence as DmaFence>) for more
34    /// details.
35    ///
36    /// # Safety
37    ///
38    /// This [`RiscvCoherentDmaFence`] implementation is insufficient for
39    /// platforms with non-coherent DMA, where explicit cache-flush instructions
40    /// are required. By using `unsafe`, callers of this function promise that
41    /// the resulting instance is not used for non-coherent DMA mappings.
42    pub unsafe fn new() -> Self {
43        RiscvCoherentDmaFence { _private: () }
44    }
45}
46
47#[cfg(any(doc, target_arch = "riscv32", target_arch = "riscv64"))]
48unsafe impl DmaFence for RiscvCoherentDmaFence {
49    /// Expose prior writes to in-memory buffers to subsequent DMA operations.
50    ///
51    /// This assembly block ensures that neither the compiler, not the CPU
52    /// reorder any memory writes beyond the point at which a subsequent memory
53    /// or I/O access is made (e.g., to start a DMA transaction).
54    ///
55    /// Conventionally, we'd use the built-in `core::sync::atomic::fence` for
56    /// this, but that explicitly cannot be used to establish synchronization
57    /// among non-atomic accesses.
58    ///
59    /// Instead, to deal with any potential compiler re-ordering, we use an
60    /// `asm!()` that does **not** have the `nomem` clobber set. This block is
61    /// opaque to the compiler. We further explicitly pass in a pointer
62    /// originating from, and thus carrying provenance of our DMA buffer. This
63    /// should be sufficient to make the compiler assume that this function may
64    /// read the entire DMA buffer, and thus cause it to commit all pending
65    /// writes before this `asm!()` block.
66    ///
67    /// To deal with any hardware re-ordering, use manually issue RISC-V fence
68    /// instruction with a predecessor set including all memory writes (to make
69    /// the buffer contents visible to hardware), and a successor set of all
70    /// memory reads and memory writes, and I/O reads or writes. Then, all
71    /// updates to the buffer are guaranteed to be written out to memory before
72    /// starting a DMA operation by reading or writing an MMIO register. We
73    /// include memory reads or writes in the successor set too, in case the
74    /// memory containing the MMIO registers is incorrectly not mapped as I/O
75    /// memory.
76    ///
77    /// This is only sufficient for platforms or devices with coherent DMA. As
78    /// per the RISC-V unprivileged spec (version 20250508), "\[n\]on-coherent
79    /// DMA may need additional synchronization (such as cache flush or
80    /// invalidate mechanisms); currently any such extra synchronization will be
81    /// device-specific" [1].
82    ///
83    /// [1]: https://docs.riscv.org/reference/isa/_attachments/riscv-unprivileged.pdf
84    #[inline(always)]
85    fn release<T>(self, slice_ptr: *mut [T]) {
86        let slice_start_ptr: *mut T = slice_ptr.cast();
87        unsafe {
88            core::arch::asm!(
89                "
90    // This block is opaque to the compiler; it must assume
91    // that it could read the entire buffer from which the
92    // pointer stored in {dma_buffer_ptr_reg} was derived.
93
94    // Do not reorder prior memory writes over subsequent
95    // I/O or memory reads or writes; see above Rust source
96    // comment for explanation.
97    fence w, iorw
98                ",
99                dma_buffer_ptr_reg = in(reg) slice_start_ptr,
100            );
101        }
102    }
103
104    /// Expose prior writes by DMA peripherals to subsequent memory reads.
105    ///
106    /// This assembly block ensures that neither the compiler, not the CPU
107    /// reorder any memory reads before the point at which a subsequent
108    /// memory or I/O access is made (e.g., to start a DMA transaction).
109    ///
110    /// Conventionally, we'd use the built-in `core::sync::atomic::fence` for
111    /// this, but that explicitly cannot be used to establish synchronization
112    /// among non-atomic accesses.
113    ///
114    /// Instead, to deal with any potential compiler re-ordering, we use an
115    /// `asm!()` that does **not** have the `nomem` clobber set. This block
116    /// is opaque to the compiler. We further explicitly pass in a pointer
117    /// originating from, and thus carrying provenance of our DMA
118    /// buffer. This should be sufficient to make the compiler assume that
119    /// this function may write to the entire DMA buffer, and thus prevent it
120    /// from moving reads to before this `asm!()` block.
121    ///
122    /// To deal with any hardware re-ordering, use manually issue RISC-V
123    /// fence instruction with a predecessor set including all memory reads
124    /// and I/O reads (to ensure that the DMA data is only read _after_ a
125    /// prior status register or in-memory descriptor indicates that the DMA
126    /// data is ready, and a successor set of all memory reads. This prevents
127    /// the CPU from issuing read instructions to the DMA buffer _before_ a
128    /// prior read confirmed that the data was ready.
129    ///
130    /// This is only sufficient for platforms or devices with coherent DMA. As
131    /// per the RISC-V unprivileged spec (version 20250508), "\[n\]on-coherent
132    /// DMA may need additional synchronization (such as cache flush or
133    /// invalidate mechanisms); currently any such extra synchronization will be
134    /// device-specific" [1].
135    ///
136    /// [1]: https://docs.riscv.org/reference/isa/_attachments/riscv-unprivileged.pdf
137    #[inline(always)]
138    fn acquire<T>(self, slice_ptr: *mut [T]) {
139        let slice_start_ptr: *mut T = slice_ptr.cast();
140        unsafe {
141            core::arch::asm!(
142                "
143    // This block is opaque to the compiler; it must assume
144    // that it could write to the entire buffer from which
145    // the pointer stored in {dma_buffer_ptr_reg} was
146    // derived.
147
148    // Do not reorder subsequent memory reads over prior I/O
149    // or memory reads; see above Rust source comment for
150    // explanation.
151    fence ir, r
152                ",
153                dma_buffer_ptr_reg = in(reg) slice_start_ptr,
154            );
155        }
156    }
157}
158
159#[cfg(not(any(doc, target_arch = "riscv32", target_arch = "riscv64")))]
160unsafe impl DmaFence for RiscvCoherentDmaFence {
161    fn release<T>(self, _slice_ptr: *mut [T]) {
162        // When building for another architecture, such as for tests or CI:
163        unimplemented!("RiscvCoherentDmaFence can only be used on RISC-V targets");
164    }
165    fn acquire<T>(self, _slice_ptr: *mut [T]) {
166        // When building for another architecture, such as for tests or CI:
167        unimplemented!("RiscvCoherentDmaFence can only be used on RISC-V targets");
168    }
169}