Skip to main content

cpu_local/
thread.rs

1use core::{
2    mem::{offset_of, size_of},
3    pin::Pin,
4    ptr::NonNull,
5    sync::atomic::{AtomicUsize, Ordering},
6};
7
8use crate::{ContextSwitchError, CpuAreaRef, preempt::PreemptionState};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub(crate) struct CpuBindingEpoch(usize);
12
13#[derive(Clone, Copy, Debug)]
14pub(crate) struct CurrentCpuBinding {
15    pub(crate) area: CpuAreaRef,
16    pub(crate) epoch: CpuBindingEpoch,
17}
18
19const CPU_PHASE_MASK: usize = 0b11;
20const CPU_UNBOUND: usize = 0b00;
21const CPU_BINDING: usize = 0b01;
22const CPU_BOUND: usize = 0b10;
23const CPU_UNBINDING: usize = 0b11;
24
25const fn execution_context_reserved_size() -> usize {
26    64 - 4 * size_of::<usize>() - size_of::<PreemptionState>()
27}
28
29/// Pinned architecture header for one execution context.
30///
31/// CPU binding uses a four-phase publication word:
32/// `Unbound -> Binding -> Bound -> Unbinding -> next Unbound`. The epoch is
33/// retained solely to reject a stale incoming switch tail.
34#[repr(C, align(64))]
35pub struct ExecutionContextHeader {
36    cpu_area: AtomicUsize,
37    binding_epoch: AtomicUsize,
38    architecture_state: [AtomicUsize; 2],
39    preemption_state: PreemptionState,
40    reserved: [u8; execution_context_reserved_size()],
41}
42
43impl ExecutionContextHeader {
44    /// Creates an unbound header before placing it in stable pinned storage.
45    pub const fn new() -> Self {
46        Self {
47            cpu_area: AtomicUsize::new(0),
48            binding_epoch: AtomicUsize::new(CPU_UNBOUND),
49            architecture_state: [const { AtomicUsize::new(0) }; 2],
50            preemption_state: PreemptionState::new(),
51            reserved: [0; execution_context_reserved_size()],
52        }
53    }
54
55    pub(crate) const fn boot(area_base: usize) -> Self {
56        Self {
57            cpu_area: AtomicUsize::new(area_base),
58            binding_epoch: AtomicUsize::new(CPU_BOUND),
59            architecture_state: [const { AtomicUsize::new(0) }; 2],
60            preemption_state: PreemptionState::bootstrap_disabled(),
61            reserved: [0; execution_context_reserved_size()],
62        }
63    }
64
65    /// Creates the execution owner's continuation of the boot context.
66    ///
67    /// Its initial depth retains bootstrap exclusion until the owner has
68    /// published all state required by its external safe point.
69    #[doc(hidden)]
70    pub const fn new_bootstrap() -> Self {
71        Self {
72            cpu_area: AtomicUsize::new(0),
73            binding_epoch: AtomicUsize::new(CPU_UNBOUND),
74            architecture_state: [const { AtomicUsize::new(0) }; 2],
75            preemption_state: PreemptionState::bootstrap_disabled(),
76            reserved: [0; execution_context_reserved_size()],
77        }
78    }
79
80    /// Returns the stable CPU area while this header is fully bound.
81    pub fn cpu_area(&self) -> Option<CpuAreaRef> {
82        self.cpu_binding().map(|binding| binding.area)
83    }
84
85    /// Returns the raw bound area base used by architecture trap entry.
86    pub fn cpu_area_base(&self) -> Option<usize> {
87        self.cpu_binding().map(|binding| binding.area.base())
88    }
89
90    pub(crate) unsafe fn bind_cpu(
91        self: Pin<&Self>,
92        area: CpuAreaRef,
93    ) -> Result<CpuBindingEpoch, ContextSwitchError> {
94        let this = self.get_ref();
95        let unbound = this.binding_epoch.load(Ordering::Acquire);
96        if unbound & CPU_PHASE_MASK != CPU_UNBOUND {
97            return Err(ContextSwitchError::NextContextAlreadyBound);
98        }
99        this.binding_epoch
100            .compare_exchange(
101                unbound,
102                unbound | CPU_BINDING,
103                Ordering::AcqRel,
104                Ordering::Acquire,
105            )
106            .map_err(|_| ContextSwitchError::NextContextAlreadyBound)?;
107        this.cpu_area.store(area.base(), Ordering::Relaxed);
108        let bound = (unbound & !CPU_PHASE_MASK) | CPU_BOUND;
109        this.binding_epoch.store(bound, Ordering::Release);
110        Ok(CpuBindingEpoch(bound))
111    }
112
113    pub(crate) unsafe fn unbind_cpu(
114        self: Pin<&Self>,
115        expected: CpuBindingEpoch,
116    ) -> Result<(), ContextSwitchError> {
117        if expected.0 & CPU_PHASE_MASK != CPU_BOUND {
118            return Err(ContextSwitchError::StalePreviousBinding);
119        }
120        let this = self.get_ref();
121        let unbinding = (expected.0 & !CPU_PHASE_MASK) | CPU_UNBINDING;
122        this.binding_epoch
123            .compare_exchange(expected.0, unbinding, Ordering::AcqRel, Ordering::Acquire)
124            .map_err(|_| ContextSwitchError::StalePreviousBinding)?;
125        this.cpu_area.store(0, Ordering::Relaxed);
126        let next_unbound = (expected.0 & !CPU_PHASE_MASK).wrapping_add(4);
127        this.binding_epoch.store(next_unbound, Ordering::Release);
128        Ok(())
129    }
130
131    pub(crate) fn cpu_binding(&self) -> Option<CurrentCpuBinding> {
132        let (area_base, epoch) = self.raw_cpu_binding()?;
133        // SAFETY: only bind_cpu can publish this field, and it accepts an
134        // already validated shutdown-lifetime CpuAreaRef.
135        let area = unsafe { CpuAreaRef::from_initialized_base(area_base) }.ok()?;
136        Some(CurrentCpuBinding { area, epoch })
137    }
138
139    pub(crate) fn raw_cpu_binding(&self) -> Option<(usize, CpuBindingEpoch)> {
140        loop {
141            let before = self.binding_epoch.load(Ordering::Acquire);
142            if before & CPU_PHASE_MASK != CPU_BOUND {
143                return None;
144            }
145            let area_base = self.cpu_area.load(Ordering::Relaxed);
146            let after = self.binding_epoch.load(Ordering::Acquire);
147            if before == after {
148                return Some((area_base, CpuBindingEpoch(after)));
149            }
150            core::hint::spin_loop();
151        }
152    }
153
154    /// Returns the stable pointer installed in the current-context source.
155    pub fn as_non_null(self: Pin<&Self>) -> NonNull<Self> {
156        NonNull::from(self.get_ref())
157    }
158
159    #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
160    pub(crate) const fn preemption_state(&self) -> &PreemptionState {
161        &self.preemption_state
162    }
163}
164
165impl Default for ExecutionContextHeader {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171/// Byte offset of the execution context's bound CPU-area base.
172pub const EXECUTION_CONTEXT_CPU_BASE_OFFSET: usize = offset_of!(ExecutionContextHeader, cpu_area);
173/// Byte offset of architecture-owned execution-context trap state.
174pub const EXECUTION_CONTEXT_ARCH_STATE_OFFSET: usize =
175    offset_of!(ExecutionContextHeader, architecture_state);
176/// Reserved bytes available to architecture-owned execution-context trap state.
177pub const EXECUTION_CONTEXT_ARCH_STATE_SIZE: usize = 2 * size_of::<usize>();
178
179const _: () = {
180    assert!(EXECUTION_CONTEXT_CPU_BASE_OFFSET == 0);
181    assert!(size_of::<ExecutionContextHeader>() == 64);
182    assert!(core::mem::align_of::<ExecutionContextHeader>() == 64);
183};
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn execution_context_header_starts_with_cpu_binding() {
191        assert_eq!(EXECUTION_CONTEXT_CPU_BASE_OFFSET, 0);
192    }
193}