Skip to main content

ostd/cpu/local/
mod.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! CPU local storage.
4//!
5//! This module provides a mechanism to define CPU-local objects. Users can
6//! define a statically-allocated CPU-local object by the macro
7//! [`crate::cpu_local!`], or allocate a dynamically-allocated CPU-local
8//! object with the function `osdk_heap_allocator::alloc_cpu_local`.
9//!
10//! The mechanism for statically-allocated CPU-local objects exploits the fact
11//! that constant values of non-[`Copy`] types can be bitwise copied. For
12//! example, a [`Option<T>`] object, though being not [`Copy`], have a constant
13//! constructor [`Option::None`] that produces a value that can be bitwise
14//! copied to create a new instance. [`alloc::sync::Arc`] however, don't have
15//! such a constructor, and thus cannot be directly used as a statically-
16//! allocated CPU-local object. Wrapping it in a type that has a constant
17//! constructor, like [`Option<T>`], can make it statically-allocated CPU-local.
18//!
19//! # Implementation
20//!
21//! These APIs are implemented by the methods as follows:
22//! 1. For statically-allocated CPU-local objects, we place them in a special
23//!    section `.cpu_local`. The bootstrap processor (BSP) uses the objects
24//!    linked in this section, and these objects are copied to dynamically
25//!    allocated local storage of each application processors (AP) during the
26//!    initialization process.
27//! 2. For dynamically-allocated CPU-local objects, we prepare a fixed-size
28//!    chunk for each CPU. These per-CPU memory chunks are laid out contiguously
29//!    in memory in the order of the CPU IDs. A dynamically-allocated CPU-local
30//!    object can be allocated by occupying the same offset in each per-CPU
31//!    memory chunk.
32
33// This module also, provide CPU-local cell objects that have inner mutability.
34//
35// The difference between statically-allocated CPU-local objects (defined by
36// [`crate::cpu_local!`]) and CPU-local cell objects (defined by
37// [`crate::cpu_local_cell!`]) is that the CPU-local objects can be shared
38// across CPUs. While through a CPU-local cell object you can only access the
39// value on the current CPU, therefore enabling inner mutability without locks.
40
41mod cell;
42mod dyn_cpu_local;
43mod static_cpu_local;
44
45pub(crate) mod single_instr;
46
47use core::{alloc::Layout, marker::PhantomData, ops::Deref};
48
49use align_ext::AlignExt;
50pub use cell::CpuLocalCell;
51pub use dyn_cpu_local::DynCpuLocalChunk;
52use dyn_cpu_local::DynamicStorage;
53use spin::Once;
54use static_cpu_local::StaticStorage;
55
56use super::CpuId;
57use crate::{
58    irq::DisabledLocalIrqGuard,
59    mm::{PAGE_SIZE, Paddr, frame::allocator, paddr_to_vaddr},
60    util::id_set::Id,
61};
62
63/// Dynamically-allocated CPU-local objects.
64pub type DynamicCpuLocal<T> = CpuLocal<T, DynamicStorage<T>>;
65
66/// Statically-allocated CPU-local objects.
67pub type StaticCpuLocal<T> = CpuLocal<T, StaticStorage<T>>;
68
69// These symbols are provided by the linker script.
70unsafe extern "C" {
71    fn __cpu_local_start();
72    fn __cpu_local_end();
73}
74
75/// A trait to abstract any type that can be used as a slot for a CPU-local
76/// variable of type `T`.
77///
78/// Each slot provides the memory space for storing `num_cpus` instances
79/// of type `T`.
80///
81/// # Safety
82///
83/// The implementor must ensure that the returned pointer refers to the
84/// variable on the correct CPU.
85pub unsafe trait AnyStorage<T> {
86    /// Gets the `const` pointer for the object on the current CPU.
87    fn get_ptr_on_current(&self, guard: &DisabledLocalIrqGuard) -> *const T;
88
89    /// Gets the `const` pointer for the object on a target CPU.
90    fn get_ptr_on_target(&self, cpu: CpuId) -> *const T;
91}
92
93/// A CPU-local variable for type `T`, backed by a storage of type `S`.
94///
95/// CPU-local objects are instantiated once per CPU core. They can be shared to
96/// other cores. In the context of a preemptible kernel task, when holding the
97/// reference to the inner object, the object is always the one in the original
98/// core (when the reference is created), no matter which core the code is
99/// currently running on.
100pub struct CpuLocal<T, S: AnyStorage<T>> {
101    storage: S,
102    phantom: PhantomData<T>,
103}
104
105impl<T: 'static, S: AnyStorage<T>> CpuLocal<T, S> {
106    /// Gets access to the underlying value on the current CPU with a
107    /// provided IRQ guard.
108    ///
109    /// By this method, you can borrow a reference to the underlying value
110    /// on the current CPU even if `T` is not `Sync`.
111    pub fn get_with<'a>(
112        &'a self,
113        guard: &'a DisabledLocalIrqGuard,
114    ) -> CpuLocalDerefGuard<'a, T, S> {
115        CpuLocalDerefGuard {
116            cpu_local: self,
117            guard,
118        }
119    }
120}
121
122impl<T: 'static + Sync, S: AnyStorage<T>> CpuLocal<T, S> {
123    /// Gets access to the CPU-local value on a specific CPU.
124    ///
125    /// This allows the caller to access CPU-local data from a remote CPU,
126    /// so the data type must be `Sync`.
127    pub fn get_on_cpu(&self, target_cpu_id: CpuId) -> &T {
128        let ptr = self.storage.get_ptr_on_target(target_cpu_id);
129        // SAFETY: `ptr` represents CPU-local data on a remote CPU. It
130        // contains valid data, the type is `Sync`, and no one will mutably
131        // borrow it, so creating an immutable borrow here is valid.
132        unsafe { &*ptr }
133    }
134}
135
136/// A guard for accessing the CPU-local object.
137///
138/// It ensures that the CPU-local object is accessed with IRQs disabled.
139/// It is created by [`CpuLocal::get_with`].
140#[must_use]
141pub struct CpuLocalDerefGuard<'a, T: 'static, S: AnyStorage<T>> {
142    cpu_local: &'a CpuLocal<T, S>,
143    guard: &'a DisabledLocalIrqGuard,
144}
145
146impl<'a, T: 'static, S: AnyStorage<T>> CpuLocalDerefGuard<'a, T, S> {
147    /// Dereferences `self` to get a reference to `T` with the lifetime `'a`.
148    pub fn deref_target(&self) -> &'a T {
149        is_used::debug_set_true();
150
151        let ptr = self.cpu_local.storage.get_ptr_on_current(self.guard);
152        // SAFETY: `ptr` represents CPU-local data on the current CPU. It
153        // contains valid data, only the current task can reference the data
154        // (due to `self.guard`), and no one will mutably borrow it, so
155        // creating an immutable borrow here is valid.
156        unsafe { &*ptr }
157    }
158}
159
160impl<'a, T: 'static, S: AnyStorage<T>> Deref for CpuLocalDerefGuard<'a, T, S> {
161    type Target = T;
162
163    fn deref(&self) -> &Self::Target {
164        self.deref_target()
165    }
166}
167
168// SAFETY: Although multiple tasks may access the inner value `T` of a CPU-local
169// variable at different times, only one task can access it at any given moment.
170// We guarantee it by disabling the reference to the inner value, or turning off
171// preemptions when creating the reference. Therefore, if `T` is `Send`, marking
172// `CpuLocal<T, S>` with `Sync` and `Send` only safely transfer ownership of the
173// entire `T` instance between tasks.
174unsafe impl<T: Send + 'static, S: AnyStorage<T>> Sync for CpuLocal<T, S> {}
175unsafe impl<T: Send + 'static> Send for CpuLocal<T, DynamicStorage<T>> {}
176
177// Implement `!Copy` and `!Clone` for `CpuLocal` to ensure memory safety:
178// - Prevent valid instances of `CpuLocal<T, StaticStorage<T>>` from being copied
179//   to any memory areas outside the `.cpu_local` section.
180// - Prevent multiple valid instances of `CpuLocal<T, DynamicStorage<T>>` from
181//   referring to the same CPU-local object, avoiding double deallocation.
182impl<T: 'static, S: AnyStorage<T>> !Copy for CpuLocal<T, S> {}
183impl<T: 'static, S: AnyStorage<T>> !Clone for CpuLocal<T, S> {}
184
185/// The static CPU-local areas for APs.
186static CPU_LOCAL_STORAGES: Once<&'static [Paddr]> = Once::new();
187
188/// Copies the static CPU-local data on the bootstrap processor (BSP)
189/// for application processors (APs).
190///
191/// # Safety
192///
193/// This function must be called in the boot context of the BSP, at a time
194/// when the APs have not yet booted.
195///
196/// The CPU-local data on the BSP must not be used before calling this
197/// function to copy it for the APs. Otherwise, the copied data will
198/// contain non-constant (also non-`Copy`) data, resulting in undefined
199/// behavior when it's loaded on the APs.
200///
201/// The caller must ensure that the `num_cpus` matches the number of all
202/// CPUs that will access the CPU-local storage.
203pub(crate) unsafe fn copy_bsp_for_ap(num_cpus: usize) {
204    let num_aps = num_cpus - 1; // BSP does not need allocated storage.
205    if num_aps == 0 {
206        return;
207    }
208
209    // Allocate a region to store the pointers to the CPU-local storage segments.
210    let res = {
211        let size = size_of::<Paddr>()
212            .checked_mul(num_aps)
213            .unwrap()
214            .align_up(PAGE_SIZE);
215        let addr =
216            allocator::early_alloc(Layout::from_size_align(size, PAGE_SIZE).unwrap()).unwrap();
217        let ptr = paddr_to_vaddr(addr) as *mut Paddr;
218
219        // SAFETY: The memory is properly allocated. We exclusively own it. So it's valid to write.
220        unsafe {
221            core::ptr::write_bytes(ptr as *mut u8, 0, size);
222        }
223        // SAFETY: The memory is properly allocated and initialized. We exclusively own it. We
224        // never deallocate it so it lives for '`static'. So we can create a mutable slice on it.
225        unsafe { core::slice::from_raw_parts_mut(ptr, num_aps) }
226    };
227
228    let bsp_base_va = __cpu_local_start as *const () as usize;
229    let bsp_end_va = __cpu_local_end as *const () as usize;
230
231    // Allocate the CPU-local storage segments for APs.
232    for res_addr_mut in res.iter_mut() {
233        let nbytes = (bsp_end_va - bsp_base_va).align_up(PAGE_SIZE);
234        let ap_pages =
235            allocator::early_alloc(Layout::from_size_align(nbytes, PAGE_SIZE).unwrap()).unwrap();
236        let ap_pages_ptr = paddr_to_vaddr(ap_pages) as *mut u8;
237
238        // SAFETY:
239        // 1. The source is valid to read because it has not been used before,
240        //    so it contains only constants.
241        // 2. The destination is valid to write because it is just allocated.
242        // 3. The memory is aligned because the alignment of `u8` is 1.
243        // 4. The two memory regions do not overlap because allocated memory
244        //    regions never overlap with the kernel data.
245        unsafe {
246            core::ptr::copy_nonoverlapping(bsp_base_va as *const u8, ap_pages_ptr, nbytes);
247        }
248
249        *res_addr_mut = ap_pages;
250    }
251
252    is_used::debug_assert_false();
253
254    assert!(!CPU_LOCAL_STORAGES.is_completed());
255    CPU_LOCAL_STORAGES.call_once(|| res);
256}
257
258/// Gets the pointer to the static CPU-local storage for the given AP.
259///
260/// # Panics
261///
262/// This method will panic if the `cpu_id` does not represent an AP or the AP's CPU-local storage
263/// has not been allocated.
264pub(crate) fn get_ap(cpu_id: CpuId) -> Paddr {
265    let offset = cpu_id
266        .as_usize()
267        .checked_sub(1)
268        .expect("The BSP does not have allocated CPU-local storage");
269
270    let paddr = CPU_LOCAL_STORAGES
271        .get()
272        .expect("No CPU-local storage has been allocated")[offset];
273    assert_ne!(
274        paddr,
275        0,
276        "The CPU-local storage for CPU {} is not allocated",
277        cpu_id.as_usize(),
278    );
279    paddr
280}
281
282mod is_used {
283    //! This module tracks whether any statically-allocated CPU-local
284    //! variables are used.
285    //!
286    //! [`copy_bsp_for_ap`] copies the CPU local data from the BSP
287    //! to the APs, so it requires as a safety condition that the
288    //! CPU-local data has not been accessed before the copy. This
289    //! module provides utilities to check if the safety condition
290    //! is met, but only if debug assertions are enabled.
291    //!
292    //! [`copy_bsp_for_ap`]: super::copy_bsp_for_ap
293
294    cfg_select! {
295        debug_assertions => {
296            use core::sync::atomic::{AtomicBool, Ordering};
297
298            static IS_USED: AtomicBool = AtomicBool::new(false);
299
300            pub(super) fn debug_set_true() {
301                IS_USED.store(true, Ordering::Relaxed);
302            }
303
304            pub(super) fn debug_assert_false() {
305                debug_assert!(!IS_USED.load(Ordering::Relaxed));
306            }
307        }
308        _ => {
309            pub(super) fn debug_set_true() {}
310
311            pub(super) fn debug_assert_false() {}
312        }
313    }
314}
315
316#[cfg(ktest)]
317mod test {
318    use core::cell::RefCell;
319
320    use ostd_macros::ktest;
321
322    #[ktest]
323    fn cpu_local() {
324        crate::cpu_local! {
325            static FOO: RefCell<usize> = RefCell::new(1);
326        }
327        let irq_guard = crate::irq::disable_local();
328        let foo_guard = FOO.get_with(&irq_guard);
329        assert_eq!(*foo_guard.borrow(), 1);
330        *foo_guard.borrow_mut() = 2;
331        assert_eq!(*foo_guard.borrow(), 2);
332        drop(foo_guard);
333    }
334
335    #[ktest]
336    fn cpu_local_cell() {
337        crate::cpu_local_cell! {
338            static BAR: usize = 3;
339        }
340        let _guard = crate::irq::disable_local();
341        assert_eq!(BAR.load(), 3);
342        BAR.store(4);
343        assert_eq!(BAR.load(), 4);
344    }
345}