Skip to main content

fake_mmio/
atomic.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::borrow::Borrow;
6use std::ptr::NonNull;
7use std::sync::atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, Ordering};
8
9use mmio::MmioError;
10use mmio::region::{MmioRegion, UnsafeMmio};
11
12/// A type that can perform atomic operations given an [`AtomicMmioPtr`].
13///
14/// All atomic operations are performed with [`Ordering::SeqCst`].
15pub trait AtomicOperand: sealed::AtomicOperand {
16    /// The atomic value type.
17    type Atomic;
18    /// Returns a reference to the atomic value type from `ptr` at `offset`.
19    fn get_atomic<P: AtomicMmioPtr>(ptr: &P, offset: usize) -> &Self::Atomic;
20    /// Performs an atomic swap at `offset` within` ptr` with `val`.
21    fn swap<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self;
22    /// Performs an atomic fetch_and at `offset` within` ptr` with `val`.
23    fn fetch_and<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self;
24    /// Performs an atomic fetch_or at `offset` within` ptr` with `val`.
25    fn fetch_or<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self;
26}
27
28// Ordering: We always pick the strongest ordering guarantee so AtomicOperand is
29// closest to an atomic volatile backed by device memory.
30const ORDERING: Ordering = Ordering::SeqCst;
31
32macro_rules! impl_atomic_operand {
33    ($t:ident, $a:ident) => {
34        impl AtomicOperand for $t {
35            type Atomic = $a;
36
37            fn get_atomic<P: AtomicMmioPtr>(ptr: &P, offset: usize) -> &Self::Atomic {
38                let ptr = ptr.ptr::<Self>(offset);
39                // SAFETY: AtomicMmioPtr provides the guarantee that ptr is
40                // valid for the lifetime of `&P` and is aligned to Self.
41                unsafe { $a::from_ptr(ptr.as_ptr()) }
42            }
43
44            fn swap<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self {
45                Self::get_atomic(ptr, offset).swap(val, ORDERING)
46            }
47
48            fn fetch_and<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self {
49                // Ordering: We always pick the strongest ordering guarantee so
50                // this is closest to an atomic volatile backed by device
51                // memory.
52                Self::get_atomic(ptr, offset).fetch_and(val, ORDERING)
53            }
54
55            fn fetch_or<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self {
56                // Ordering: We always pick the strongest ordering guarantee so
57                // this is closest to an atomic volatile backed by device
58                // memory.
59                Self::get_atomic(ptr, offset).fetch_or(val, ORDERING)
60            }
61        }
62    };
63}
64
65impl_atomic_operand!(u8, AtomicU8);
66impl_atomic_operand!(u16, AtomicU16);
67impl_atomic_operand!(u32, AtomicU32);
68impl_atomic_operand!(u64, AtomicU64);
69
70mod sealed {
71    pub trait AtomicOperand {}
72
73    impl AtomicOperand for u8 {}
74    impl AtomicOperand for u16 {}
75    impl AtomicOperand for u32 {}
76    impl AtomicOperand for u64 {}
77}
78
79/// A trait abstracting a type that can provide valid pointers for atomic MMIO
80/// operations.
81///
82/// Notably implemented by [`crate::CachedVmoMemory`].
83pub trait AtomicMmioPtr {
84    /// Returns a valid pointer to T to perform atomic operations on.
85    ///
86    /// The returned pointer _must_ be properly aligned for `T` at `offset` and
87    /// valid for atomic reads and writes for the lifetime of `Self`.
88    ///
89    /// # Panics
90    ///
91    /// Panics if a pointer with the conditions above can't be created at
92    /// `offset`.
93    fn ptr<T: AtomicOperand>(&self, offset: usize) -> NonNull<T>;
94}
95
96/// An extension trait applied to [`MmioRegion`] to support atomic operations.
97///
98/// All atomic operations are performed with [`Ordering::SeqCst`].
99pub trait AtomicMmio {
100    /// Atomic swap of `value` at `offset`.
101    ///
102    /// # Panics
103    ///
104    /// Panics if `offset` is invalid (unaligned or out of bounds for `T`).
105    fn swap<T: AtomicOperand>(&self, offset: usize, value: T) -> T {
106        self.try_swap(offset, value).unwrap()
107    }
108
109    /// Atomic fetch_and with `value` at `offset`.
110    ///
111    /// # Panics
112    ///
113    /// Panics if `offset` is invalid (unaligned or out of bounds for `T`).
114    fn fetch_and<T: AtomicOperand>(&self, offset: usize, value: T) -> T {
115        self.try_fetch_and(offset, value).unwrap()
116    }
117
118    /// Atomic fetch_or with `value` at `offset`.
119    ///
120    /// # Panics
121    ///
122    /// Panics if `offset` is invalid (unaligned or out of bounds for `T`).
123    fn fetch_or<T: AtomicOperand>(&self, offset: usize, value: T) -> T {
124        self.try_fetch_or(offset, value).unwrap()
125    }
126
127    /// Try atomic swap of `value` at `offset`.
128    ///
129    /// Returns an error if `offset` is invalid (unaligned or out of bounds for `T`).
130    fn try_swap<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError>;
131
132    /// Try atomic fetch_and with `value` at `offset`.
133    ///
134    /// Returns an error if `offset` is invalid (unaligned or out of bounds for `T`).
135    fn try_fetch_and<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError>;
136
137    /// Try atomic fetch_or with `value` at `offset`.
138    ///
139    /// Returns an error if `offset` is invalid (unaligned or out of bounds for `T`).
140    fn try_fetch_or<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError>;
141}
142
143impl<Impl: AtomicMmioPtr + UnsafeMmio, Owner: Borrow<Impl>> AtomicMmio for MmioRegion<Impl, Owner> {
144    fn try_swap<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError> {
145        let offset = self.resolve_offset::<T>(offset)?;
146        Ok(T::swap(self.unsafe_mmio(), offset, value))
147    }
148
149    fn try_fetch_and<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError> {
150        let offset = self.resolve_offset::<T>(offset)?;
151        Ok(T::fetch_and(self.unsafe_mmio(), offset, value))
152    }
153
154    fn try_fetch_or<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError> {
155        let offset = self.resolve_offset::<T>(offset)?;
156        Ok(T::fetch_or(self.unsafe_mmio(), offset, value))
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::CachedVmoMemory;
164    use crate::operand::MmioOperand;
165    use crate::operand::tests::OperandCallable;
166    use mmio::MmioExt as _;
167
168    const VMO_SIZE: usize = 4096;
169
170    #[test]
171    fn test_atomic_swap() {
172        struct SwapTester;
173
174        impl OperandCallable for SwapTester {
175            fn call<T: MmioOperand>(&mut self) {
176                let mmio = CachedVmoMemory::new_mapped(VMO_SIZE).expect("Failed to map VMO.");
177                let offset = 0;
178
179                assert_eq!(mmio.load::<T>(offset), T::try_from(0).unwrap());
180
181                let val_aa = T::try_from(0xaa).unwrap();
182                let old = mmio.swap::<T>(offset, val_aa);
183                assert_eq!(old, T::try_from(0).unwrap());
184                assert_eq!(mmio.load::<T>(offset), val_aa);
185            }
186        }
187
188        SwapTester.call_all();
189    }
190
191    #[test]
192    fn test_atomic_fetch_and() {
193        struct FetchAndTester;
194
195        impl OperandCallable for FetchAndTester {
196            fn call<T: MmioOperand>(&mut self) {
197                let mmio = CachedVmoMemory::new_mapped(VMO_SIZE).expect("Failed to map VMO.");
198                let offset = 0;
199
200                let val_aa = T::try_from(0xaa).unwrap();
201                let _ = mmio.swap::<T>(offset, val_aa);
202
203                let mask = T::try_from(0x0f).unwrap();
204                let old = mmio.fetch_and::<T>(offset, mask);
205                assert_eq!(old, val_aa);
206                assert_eq!(mmio.load::<T>(offset), T::try_from(0x0a).unwrap());
207            }
208        }
209
210        FetchAndTester.call_all();
211    }
212
213    #[test]
214    fn test_atomic_fetch_or() {
215        struct FetchOrTester;
216
217        impl OperandCallable for FetchOrTester {
218            fn call<T: MmioOperand>(&mut self) {
219                let mmio = CachedVmoMemory::new_mapped(VMO_SIZE).expect("Failed to map VMO.");
220                let offset = 0;
221
222                let val_0a = T::try_from(0x0a).unwrap();
223                let _ = mmio.swap::<T>(offset, val_0a);
224
225                let bits = T::try_from(0x50).unwrap();
226                let old = mmio.fetch_or::<T>(offset, bits);
227                assert_eq!(old, val_0a);
228                assert_eq!(mmio.load::<T>(offset), T::try_from(0x5a).unwrap());
229            }
230        }
231
232        FetchOrTester.call_all();
233    }
234
235    #[test]
236    fn test_atomic_errors() {
237        struct ErrorTester;
238
239        impl OperandCallable for ErrorTester {
240            fn call<T: MmioOperand>(&mut self) {
241                let mmio = CachedVmoMemory::new_mapped(VMO_SIZE).expect("Failed to map VMO.");
242                let val = T::try_from(0x12).unwrap();
243
244                // Out of range.
245                assert_eq!(mmio.try_swap::<T>(VMO_SIZE, val), Err(MmioError::OutOfRange));
246                assert_eq!(mmio.try_fetch_and::<T>(VMO_SIZE, val), Err(MmioError::OutOfRange));
247                assert_eq!(mmio.try_fetch_or::<T>(VMO_SIZE, val), Err(MmioError::OutOfRange));
248
249                // Unaligned access for types with alignment > 1.
250                if std::mem::align_of::<T>() > 1 {
251                    assert_eq!(mmio.try_swap::<T>(1, val), Err(MmioError::Unaligned));
252                    assert_eq!(mmio.try_fetch_and::<T>(1, val), Err(MmioError::Unaligned));
253                    assert_eq!(mmio.try_fetch_or::<T>(1, val), Err(MmioError::Unaligned));
254                }
255            }
256        }
257
258        ErrorTester.call_all();
259    }
260}