Skip to main content

ax_percpu/
value.rs

1use core::{marker::PhantomData, ptr::NonNull};
2
3use cpu_local::{CpuPin, ExclusiveCpu};
4
5use crate::PerCpuArea;
6
7/// Provider generated for one concrete symbol in the per-CPU template.
8///
9/// # Safety
10///
11/// The returned offset must identify a live, properly aligned `T` in every
12/// initialized CPU area. Every returned pointer must address that same `T` in
13/// the selected live area.
14///
15/// Primitive providers must use the matching atomic representation.
16#[doc(hidden)]
17pub unsafe trait PerCpuSymbol<T> {
18    fn offset() -> usize;
19    fn current_ptr(pin: &CpuPin<'_>) -> NonNull<T>;
20    fn remote_ptr(area: PerCpuArea) -> NonNull<T>;
21}
22
23/// Marker implemented only by macro-generated object symbols.
24///
25/// # Safety
26///
27/// The provider's storage must contain a live `T` in every initialized area.
28#[doc(hidden)]
29pub unsafe trait PerCpuObjectSymbol<T>: PerCpuSymbol<T> {}
30
31/// Marker implemented only by macro-generated atomic scalar symbols.
32///
33/// # Safety
34///
35/// The provider's storage must use the atomic representation paired with `T`.
36#[doc(hidden)]
37pub unsafe trait PerCpuPrimitiveSymbol<T>: PerCpuSymbol<T> {}
38
39type PerCpuMarker<T, S> = fn() -> (T, S);
40
41/// Typed descriptor for one symbol replicated in every runtime CPU area.
42pub struct PerCpu<T, S> {
43    _marker: PhantomData<PerCpuMarker<T, S>>,
44}
45
46impl<T, S> PerCpu<T, S>
47where
48    S: PerCpuSymbol<T>,
49{
50    /// Creates the zero-sized descriptor for a macro-generated symbol.
51    #[doc(hidden)]
52    pub const fn new() -> Self {
53        Self {
54            _marker: PhantomData,
55        }
56    }
57
58    /// Returns this symbol's byte offset in one area.
59    pub fn offset(&self) -> usize {
60        S::offset()
61    }
62
63    /// Returns a typed pointer whose address is stable for `pin`.
64    pub fn current_ptr(&self, pin: &CpuPin<'_>) -> NonNull<T> {
65        S::current_ptr(pin)
66    }
67
68    /// Returns a typed pointer in an explicitly selected remote area.
69    ///
70    /// The caller remains responsible for synchronization before dereference.
71    pub fn remote_ptr(&self, area: PerCpuArea) -> NonNull<T> {
72        S::remote_ptr(area)
73    }
74}
75
76impl<T, S> PerCpu<T, S>
77where
78    S: PerCpuObjectSymbol<T>,
79{
80    /// Mutates the current CPU's object without allowing its borrow to escape.
81    pub fn with_current_mut<R>(
82        &self,
83        exclusive: &ExclusiveCpu<'_>,
84        operation: impl for<'value> FnOnce(&'value mut T) -> R,
85    ) -> R {
86        // SAFETY: ExclusiveCpu proves local and remote alias exclusion for the
87        // closure, while its area fixes the generated address.
88        let mut pointer =
89            unsafe { NonNull::new_unchecked((exclusive.area().base() + S::offset()) as *mut T) };
90        operation(unsafe { pointer.as_mut() })
91    }
92
93    /// Mutates the current CPU's object before a [`CpuPin`] exists.
94    ///
95    /// # Errors
96    ///
97    /// Returns [`cpu_local::CpuLocalError::AreaNotInstalled`] before the current
98    /// CPU has installed its runtime area, or an address error for an invalid
99    /// symbol address.
100    ///
101    /// # Safety
102    ///
103    /// The caller must prevent migration, context switches, and local
104    /// IRQ/re-entry for the complete callback, and exclude every conflicting
105    /// remote access to this object. Offline CPU bootstrap satisfies the same
106    /// contract before interrupt publication.
107    #[doc(hidden)]
108    pub unsafe fn with_current_cpu_area_mut<R>(
109        &self,
110        operation: impl for<'value> FnOnce(&'value mut T) -> R,
111    ) -> Result<R, cpu_local::CpuLocalError> {
112        unsafe {
113            cpu_local::with_current_cpu_area(|area| {
114                // SAFETY: the unsafe provider contract guarantees that its
115                // offset addresses a live, aligned T in every CPU area.
116                let mut pointer = area.symbol_ptr::<T>(S::offset())?;
117                Ok(operation(pointer.as_mut()))
118            })?
119        }
120    }
121}
122
123impl<T, S> PerCpu<T, S>
124where
125    T: Sync,
126    S: PerCpuObjectSymbol<T>,
127{
128    /// Borrows a shared current-CPU object for one non-escaping callback.
129    pub fn with_current<R>(
130        &self,
131        pin: &CpuPin<'_>,
132        operation: impl for<'value> FnOnce(&'value T) -> R,
133    ) -> R {
134        // SAFETY: T: Sync permits shared observation and the pin fixes address.
135        operation(unsafe { S::current_ptr(pin).as_ref() })
136    }
137
138    /// Borrows the current CPU's object before a [`CpuPin`] exists.
139    ///
140    /// # Errors
141    ///
142    /// Returns [`cpu_local::CpuLocalError::AreaNotInstalled`] before the current
143    /// CPU has installed its runtime area, or an address error for an invalid
144    /// symbol address.
145    ///
146    /// # Safety
147    ///
148    /// The caller must prevent migration and context switches for the complete
149    /// callback and exclude every conflicting mutation of this object. Offline
150    /// CPU bootstrap satisfies the same contract before interrupt publication.
151    #[doc(hidden)]
152    pub unsafe fn with_current_cpu_area<R>(
153        &self,
154        operation: impl for<'value> FnOnce(&'value T) -> R,
155    ) -> Result<R, cpu_local::CpuLocalError> {
156        unsafe {
157            cpu_local::with_current_cpu_area(|area| {
158                // SAFETY: the unsafe provider contract guarantees that its
159                // offset addresses a live, aligned T in every CPU area.
160                let pointer = area.symbol_ptr::<T>(S::offset())?;
161                Ok(operation(pointer.as_ref()))
162            })?
163        }
164    }
165}
166
167mod primitive {
168    use core::{
169        ptr::NonNull,
170        sync::atomic::{
171            AtomicBool, AtomicU8, AtomicU16, AtomicU32, AtomicU64, AtomicUsize, Ordering,
172        },
173    };
174
175    pub trait Sealed: Copy {
176        unsafe fn load(pointer: NonNull<Self>) -> Self;
177        unsafe fn store(pointer: NonNull<Self>, value: Self);
178    }
179
180    macro_rules! impl_atomic_primitive {
181        ($value:ty, $atomic:ty) => {
182            impl Sealed for $value {
183                unsafe fn load(pointer: NonNull<Self>) -> Self {
184                    unsafe { pointer.cast::<$atomic>().as_ref() }.load(Ordering::Relaxed)
185                }
186
187                unsafe fn store(pointer: NonNull<Self>, value: Self) {
188                    unsafe { pointer.cast::<$atomic>().as_ref() }.store(value, Ordering::Relaxed);
189                }
190            }
191        };
192    }
193
194    impl_atomic_primitive!(bool, AtomicBool);
195    impl_atomic_primitive!(u8, AtomicU8);
196    impl_atomic_primitive!(u16, AtomicU16);
197    impl_atomic_primitive!(u32, AtomicU32);
198    impl_atomic_primitive!(u64, AtomicU64);
199    impl_atomic_primitive!(usize, AtomicUsize);
200}
201
202impl<T, S> PerCpu<T, S>
203where
204    T: primitive::Sealed,
205    S: PerCpuPrimitiveSymbol<T>,
206{
207    /// Loads the current CPU's atomic scalar with relaxed ordering.
208    pub fn read_current(&self, pin: &CpuPin<'_>) -> T {
209        unsafe { T::load(S::current_ptr(pin)) }
210    }
211
212    /// Stores the current CPU's atomic scalar with relaxed ordering.
213    pub fn write_current(&self, pin: &CpuPin<'_>, value: T) {
214        unsafe { T::store(S::current_ptr(pin), value) }
215    }
216}