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}