1use 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
12pub trait AtomicOperand: sealed::AtomicOperand {
16 type Atomic;
18 fn get_atomic<P: AtomicMmioPtr>(ptr: &P, offset: usize) -> &Self::Atomic;
20 fn swap<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self;
22 fn fetch_and<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self;
24 fn fetch_or<P: AtomicMmioPtr>(ptr: &P, offset: usize, val: Self) -> Self;
26}
27
28const 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 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 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 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
79pub trait AtomicMmioPtr {
84 fn ptr<T: AtomicOperand>(&self, offset: usize) -> NonNull<T>;
94}
95
96pub trait AtomicMmio {
100 fn swap<T: AtomicOperand>(&self, offset: usize, value: T) -> T {
106 self.try_swap(offset, value).unwrap()
107 }
108
109 fn fetch_and<T: AtomicOperand>(&self, offset: usize, value: T) -> T {
115 self.try_fetch_and(offset, value).unwrap()
116 }
117
118 fn fetch_or<T: AtomicOperand>(&self, offset: usize, value: T) -> T {
124 self.try_fetch_or(offset, value).unwrap()
125 }
126
127 fn try_swap<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError>;
131
132 fn try_fetch_and<T: AtomicOperand>(&self, offset: usize, value: T) -> Result<T, MmioError>;
136
137 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 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 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}