Skip to main content

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}