kernel/platform/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
6//! DMA fence synchronization primitives for sharing memory with DMA
7//! peripherals.
8
9/// Synchronization primitives for safely sharing memory with DMA peripherals.
10///
11/// An implementation of _acquire_ and _release_ memory fence operations to
12/// expose memory reads and writes by Rust and DMA peripherals to each other.
13/// These operations are from the perspective of Rust and the Tock kernel: a
14/// memory buffer is _released_ to the DMA peripheral, and then after the DMA
15/// operation is fully completed, _acquired_ back from the DMA peripheral.
16///
17/// When starting a DMA operation over a buffer prepared from Rust, it is
18/// important that the buffer's current contents are actually observable by the
19/// DMA hardware. Similarly, when a DMA operation is finished, we must ensure
20/// that Rust can see the latest buffer contents, as written by a DMA
21/// peripheral. However, instruction reordering by both the compiler, hardware,
22/// and non cache-coherent platforms complicate this story. These optimizations
23/// can mean that a write from within Rust may not be visible to a DMA
24/// peripheral, or a write performed by a DMA peripheral may not be visible to
25/// Rust.
26///
27/// This trait provides [`acquire`](Self::acquire) and
28/// [`release`](Self::release) memory fences that recover these guarantees for
29/// DMA buffers in the presence of compiler reordering and, if present on the
30/// target platform, hardware reordering or non-coherent caches.
31///
32/// Ordinarily, we'd use the built-in [`core::sync::atomic::fence`] for this,
33/// but that explicitly cannot be used to establish synchronization among
34/// non-atomic accesses. Additionally, certain platforms require
35/// platform-specific instructions to synchronize memory: for instance, the
36/// RISC-V unprivileged spec (version 20250508) states that "\[n\]on-coherent
37/// DMA may need additional synchronization (such as cache flush or invalidate
38/// mechanisms); currently any such extra synchronization will be
39/// device-specific" [1]. Therefore, Tock uses a DMA-specific trait implemented
40/// by its target architecture and platform crates.
41///
42/// [1]: https://docs.riscv.org/reference/isa/_attachments/riscv-unprivileged.pdf
43///
44/// # Implementations
45///
46/// Implementations may assume this trait is only used for DMA peripherals,
47/// where hardware has access to memory separate from normal load and store
48/// instructions executed on the CPU. Implementations do not need to provide
49/// any synchronization between loads and stores, for example to support
50/// multi-core execution. Implementations must correctly synchronize for all
51/// possible DMA operations on the chip.
52///
53/// Implementations may use any chip-specific DMA synchronization features that
54/// may exist on a particular microcontroller.
55///
56/// # Safety
57///
58/// This is an `unsafe` trait, as users of it rely on correct
59/// [`acquire`](Self::acquire) and [`release`](Self::acquire) implementations to
60/// maintain soundness.
61///
62/// Incorrect implementations of this trait can introduce undefined
63/// behavior. For example, an incorrect [`acquire`](Self::acquire) operation
64/// could cause DMA-issued writes to memory to be visible to Rust only *after* a
65/// shared or immutable reference to this buffer is made accessible, which
66/// effectively violates Rust's no-alias assumptions.
67pub unsafe trait DmaFence: core::fmt::Debug + Send + Sync + Copy {
68 /// Expose prior writes to in-memory buffers to subsequent DMA operations.
69 ///
70 /// Specifically, this function must ensure that any writes from Rust to the
71 /// buffer described by `ptr` and `len` _before_ this function, are visible
72 /// to any DMA operations, as long as these operations are initiated by MMIO read or write operations issued _after_ this function returns.
73 fn release<T>(self, buf: *mut [T]);
74
75 /// Expose prior writes by DMA peripherals to subsequent memory reads.
76 ///
77 /// Specifically, this function must ensure that any Rust reads of the buffer described by `ptr` and `len`, performed _after_ this function returns,
78 /// reflect all writes made by DMA operations finished _before_ this
79 /// function ran.
80 ///
81 /// This function must be called _after_ the program has observed that the DMA operation finished, by reading a status field through an MMIO or memory read.
82 fn acquire<T>(self, buf: *mut [T]);
83}