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    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}