cpu_local/pin.rs
1use core::{marker::PhantomData, ptr::NonNull};
2
3use crate::{CpuAreaRef, CpuLocalError, register};
4
5/// Scoped proof that execution cannot migrate away from one validated CPU.
6///
7/// The token can only be created by [`with_cpu_pin`]. Its invariant lifetime
8/// and higher-ranked callback prevent it from escaping the caller's migration
9/// guard or offline-CPU critical section.
10#[must_use = "CPU-local access is valid only while this pin remains in scope"]
11#[derive(Debug)]
12pub struct CpuPin<'scope> {
13 area: CpuAreaRef,
14 _scope: PhantomData<&'scope mut &'scope ()>,
15 _not_send_or_sync: PhantomData<*mut ()>,
16}
17
18impl CpuPin<'_> {
19 /// Returns the initialized CPU area validated when this pin was created.
20 #[inline(always)]
21 pub const fn area(&self) -> CpuAreaRef {
22 self.area
23 }
24}
25
26/// Scoped proof of exclusive local access to CPU-owned mutable state.
27///
28/// In addition to migration exclusion, the caller that creates this token has
29/// excluded local IRQ/re-entry and every conflicting remote access.
30#[must_use = "mutable CPU-local access is valid only while this token remains in scope"]
31#[derive(Debug)]
32pub struct ExclusiveCpu<'pin> {
33 area: CpuAreaRef,
34 _scope: PhantomData<&'pin mut &'pin ()>,
35 _not_send_or_sync: PhantomData<*mut ()>,
36}
37
38/// Scoped selection of the architecture-owned current CPU area.
39///
40/// This capability is intentionally weaker than [`CpuPin`]: it does not
41/// validate current execution-context publication. It exists for low-level
42/// owner boundaries that must select CPU-owned state before a pin can be
43/// constructed.
44#[doc(hidden)]
45#[must_use = "current CPU-area access is valid only while this token remains in scope"]
46#[derive(Debug)]
47pub struct CurrentCpuArea<'scope> {
48 area_base: usize,
49 _scope: PhantomData<&'scope mut &'scope ()>,
50 _not_send_or_sync: PhantomData<*mut ()>,
51}
52
53impl CurrentCpuArea<'_> {
54 /// Calculates a typed symbol address in the selected installed CPU area.
55 ///
56 /// # Errors
57 ///
58 /// Returns [`CpuLocalError::AddressOverflow`] when adding `offset` exceeds
59 /// the address space.
60 ///
61 /// # Safety
62 ///
63 /// `offset` must identify a live, properly aligned `T` in every initialized
64 /// CPU area. The returned pointer may only be dereferenced while the outer
65 /// owner transaction retains the synchronization required by `T`.
66 #[doc(hidden)]
67 #[inline(always)]
68 pub unsafe fn symbol_ptr<T>(&self, offset: usize) -> Result<NonNull<T>, CpuLocalError> {
69 let address = self
70 .area_base
71 .checked_add(offset)
72 .ok_or(CpuLocalError::AddressOverflow)?;
73 NonNull::new(address as *mut T).ok_or(CpuLocalError::InvalidAreaBase { base: address })
74 }
75}
76
77impl ExclusiveCpu<'_> {
78 /// Returns the initialized area covered by this stronger capability.
79 pub const fn area(&self) -> CpuAreaRef {
80 self.area
81 }
82}
83
84/// Runs `operation` with a validated, non-escaping CPU pin.
85///
86/// The higher-ranked callback prevents retaining the token:
87///
88/// ```compile_fail
89/// let retained = unsafe { cpu_local::with_cpu_pin(|pin| pin) }.unwrap();
90/// # let _ = retained;
91/// ```
92///
93/// It also cannot be sent to another execution context:
94///
95/// ```compile_fail
96/// unsafe {
97/// cpu_local::with_cpu_pin(|pin| {
98/// std::thread::scope(|scope| scope.spawn(|| drop(pin)));
99/// })
100/// .unwrap();
101/// }
102/// ```
103///
104/// # Errors
105///
106/// Returns [`CpuLocalError::AreaNotInstalled`] before this CPU has installed
107/// its runtime area.
108///
109/// # Safety
110///
111/// The caller must prevent migration for the complete callback. Offline boot
112/// code may call this while the CPU cannot be scheduled; runtime code must
113/// hold an appropriate preemption or IRQ guard.
114#[inline(always)]
115pub unsafe fn with_cpu_pin<R>(
116 operation: impl for<'scope> FnOnce(&CpuPin<'scope>) -> R,
117) -> Result<R, CpuLocalError> {
118 let area = register::current_area()?;
119 let pin = CpuPin {
120 area,
121 _scope: PhantomData,
122 _not_send_or_sync: PhantomData,
123 };
124 // Installation validates the area, while initial binding and every switch
125 // validate current before publication. Like Linux per-CPU access, this hot
126 // path trusts those owner boundaries instead of re-reading current.
127 Ok(operation(&pin))
128}
129
130/// Runs `operation` with exclusive access to mutable state on the pinned CPU.
131///
132/// # Safety
133///
134/// The caller must prevent migration, local IRQ/re-entry, and conflicting
135/// remote access for the complete callback. `pin` must be covered by the same
136/// guard that establishes those conditions.
137pub unsafe fn with_exclusive_cpu<R>(
138 pin: &CpuPin<'_>,
139 operation: impl for<'exclusive> FnOnce(&ExclusiveCpu<'exclusive>) -> R,
140) -> R {
141 let exclusive = ExclusiveCpu {
142 area: pin.area,
143 _scope: PhantomData,
144 _not_send_or_sync: PhantomData,
145 };
146 operation(&exclusive)
147}
148
149/// Runs `operation` with a non-escaping selection of the current CPU area.
150///
151/// Unlike [`with_cpu_pin`], this boundary does not validate current
152/// execution-context publication or reconstruct the complete area identity.
153/// It is intended for low-level owner code that cannot construct a pin before
154/// accessing CPU-owned state.
155///
156/// # Errors
157///
158/// Returns [`CpuLocalError::AreaNotInstalled`] before the current CPU has an
159/// installed runtime area, or an address error for an invalid base.
160///
161/// # Safety
162///
163/// The caller must prevent migration and context switches for the complete
164/// callback. The installed area must remain mapped until shutdown. Values
165/// mutably selected through this token additionally require local IRQ/re-entry
166/// and every conflicting remote access to be excluded. Offline CPU bootstrap
167/// satisfies these conditions before interrupt publication.
168#[doc(hidden)]
169#[inline(always)]
170pub unsafe fn with_current_cpu_area<R>(
171 operation: impl for<'scope> FnOnce(&CurrentCpuArea<'scope>) -> R,
172) -> Result<R, CpuLocalError> {
173 let area_base = unsafe { register::current_cpu_area_base()? };
174 let area = CurrentCpuArea {
175 area_base,
176 _scope: PhantomData,
177 _not_send_or_sync: PhantomData,
178 };
179 Ok(operation(&area))
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn current_cpu_area_rejects_a_symbol_address_overflow() {
188 let area = CurrentCpuArea {
189 area_base: usize::MAX,
190 _scope: PhantomData,
191 _not_send_or_sync: PhantomData,
192 };
193
194 assert_eq!(
195 // SAFETY: no pointer is dereferenced; the test exercises rejection
196 // before an address can be constructed.
197 unsafe { area.symbol_ptr::<u8>(1) },
198 Err(CpuLocalError::AddressOverflow),
199 );
200 }
201}