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