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 /// Gets the `mut` pointer for the object on a target CPU.
93 ///
94 /// This method is intended for use when initializing or dropping the storage.
95 fn get_mut_ptr_on_target(&mut self, cpu: CpuId) -> *mut T;
96}
97
98/// A CPU-local variable for type `T`, backed by a storage of type `S`.
99///
100/// CPU-local objects are instantiated once per CPU core. They can be shared to
101/// other cores. In the context of a preemptible kernel task, when holding the
102/// reference to the inner object, the object is always the one in the original
103/// core (when the reference is created), no matter which core the code is
104/// currently running on.
105pub struct CpuLocal<T, S: AnyStorage<T>> {
106 storage: S,
107 phantom: PhantomData<T>,
108}
109
110impl<T: 'static, S: AnyStorage<T>> CpuLocal<T, S> {
111 /// Gets access to the underlying value on the current CPU with a
112 /// provided IRQ guard.
113 ///
114 /// By this method, you can borrow a reference to the underlying value
115 /// on the current CPU even if `T` is not `Sync`.
116 pub fn get_with<'a>(
117 &'a self,
118 guard: &'a DisabledLocalIrqGuard,
119 ) -> CpuLocalDerefGuard<'a, T, S> {
120 CpuLocalDerefGuard {
121 cpu_local: self,
122 guard,
123 }
124 }
125}
126
127impl<T: 'static + Sync, S: AnyStorage<T>> CpuLocal<T, S> {
128 /// Gets access to the CPU-local value on a specific CPU.
129 ///
130 /// This allows the caller to access CPU-local data from a remote CPU,
131 /// so the data type must be `Sync`.
132 pub fn get_on_cpu(&self, target_cpu_id: CpuId) -> &T {
133 let ptr = self.storage.get_ptr_on_target(target_cpu_id);
134 // SAFETY: `ptr` represents CPU-local data on a remote CPU. It
135 // contains valid data, the type is `Sync`, and no one will mutably
136 // borrow it, so creating an immutable borrow here is valid.
137 unsafe { &*ptr }
138 }
139}
140
141/// A guard for accessing the CPU-local object.
142///
143/// It ensures that the CPU-local object is accessed with IRQs disabled.
144/// It is created by [`CpuLocal::get_with`].
145#[must_use]
146pub struct CpuLocalDerefGuard<'a, T: 'static, S: AnyStorage<T>> {
147 cpu_local: &'a CpuLocal<T, S>,
148 guard: &'a DisabledLocalIrqGuard,
149}
150
151impl<'a, T: 'static, S: AnyStorage<T>> Deref for CpuLocalDerefGuard<'a, T, S> {
152 type Target = T;
153
154 fn deref(&self) -> &'a Self::Target {
155 is_used::debug_set_true();
156
157 let ptr = self.cpu_local.storage.get_ptr_on_current(self.guard);
158 // SAFETY: `ptr` represents CPU-local data on the current CPU. It
159 // contains valid data, only the current task can reference the data
160 // (due to `self.guard`), and no one will mutably borrow it, so
161 // creating an immutable borrow here is valid.
162 unsafe { &*ptr }
163 }
164}
165
166// SAFETY: Although multiple tasks may access the inner value `T` of a CPU-local
167// variable at different times, only one task can access it at any given moment.
168// We guarantee it by disabling the reference to the inner value, or turning off
169// preemptions when creating the reference. Therefore, if `T` is `Send`, marking
170// `CpuLocal<T, S>` with `Sync` and `Send` only safely transfer ownership of the
171// entire `T` instance between tasks.
172unsafe impl<T: Send + 'static, S: AnyStorage<T>> Sync for CpuLocal<T, S> {}
173unsafe impl<T: Send + 'static> Send for CpuLocal<T, DynamicStorage<T>> {}
174
175// Implement `!Copy` and `!Clone` for `CpuLocal` to ensure memory safety:
176// - Prevent valid instances of `CpuLocal<T, StaticStorage<T>>` from being copied
177// to any memory areas outside the `.cpu_local` section.
178// - Prevent multiple valid instances of `CpuLocal<T, DynamicStorage<T>>` from
179// referring to the same CPU-local object, avoiding double deallocation.
180impl<T: 'static, S: AnyStorage<T>> !Copy for CpuLocal<T, S> {}
181impl<T: 'static, S: AnyStorage<T>> !Clone for CpuLocal<T, S> {}
182
183/// The static CPU-local areas for APs.
184static CPU_LOCAL_STORAGES: Once<&'static [Paddr]> = Once::new();
185
186/// Copies the static CPU-local data on the bootstrap processor (BSP)
187/// for application processors (APs).
188///
189/// # Safety
190///
191/// This function must be called in the boot context of the BSP, at a time
192/// when the APs have not yet booted.
193///
194/// The CPU-local data on the BSP must not be used before calling this
195/// function to copy it for the APs. Otherwise, the copied data will
196/// contain non-constant (also non-`Copy`) data, resulting in undefined
197/// behavior when it's loaded on the APs.
198///
199/// The caller must ensure that the `num_cpus` matches the number of all
200/// CPUs that will access the CPU-local storage.
201pub(crate) unsafe fn copy_bsp_for_ap(num_cpus: usize) {
202 let num_aps = num_cpus - 1; // BSP does not need allocated storage.
203 if num_aps == 0 {
204 return;
205 }
206
207 // Allocate a region to store the pointers to the CPU-local storage segments.
208 let res = {
209 let size = size_of::<Paddr>()
210 .checked_mul(num_aps)
211 .unwrap()
212 .align_up(PAGE_SIZE);
213 let addr =
214 allocator::early_alloc(Layout::from_size_align(size, PAGE_SIZE).unwrap()).unwrap();
215 let ptr = paddr_to_vaddr(addr) as *mut Paddr;
216
217 // SAFETY: The memory is properly allocated. We exclusively own it. So it's valid to write.
218 unsafe {
219 core::ptr::write_bytes(ptr as *mut u8, 0, size);
220 }
221 // SAFETY: The memory is properly allocated and initialized. We exclusively own it. We
222 // never deallocate it so it lives for '`static'. So we can create a mutable slice on it.
223 unsafe { core::slice::from_raw_parts_mut(ptr, num_aps) }
224 };
225
226 let bsp_base_va = __cpu_local_start as *const () as usize;
227 let bsp_end_va = __cpu_local_end as *const () as usize;
228
229 // Allocate the CPU-local storage segments for APs.
230 for res_addr_mut in res.iter_mut() {
231 let nbytes = (bsp_end_va - bsp_base_va).align_up(PAGE_SIZE);
232 let ap_pages =
233 allocator::early_alloc(Layout::from_size_align(nbytes, PAGE_SIZE).unwrap()).unwrap();
234 let ap_pages_ptr = paddr_to_vaddr(ap_pages) as *mut u8;
235
236 // SAFETY:
237 // 1. The source is valid to read because it has not been used before,
238 // so it contains only constants.
239 // 2. The destination is valid to write because it is just allocated.
240 // 3. The memory is aligned because the alignment of `u8` is 1.
241 // 4. The two memory regions do not overlap because allocated memory
242 // regions never overlap with the kernel data.
243 unsafe {
244 core::ptr::copy_nonoverlapping(bsp_base_va as *const u8, ap_pages_ptr, nbytes);
245 }
246
247 *res_addr_mut = ap_pages;
248 }
249
250 is_used::debug_assert_false();
251
252 assert!(!CPU_LOCAL_STORAGES.is_completed());
253 CPU_LOCAL_STORAGES.call_once(|| res);
254}
255
256/// Gets the pointer to the static CPU-local storage for the given AP.
257///
258/// # Panics
259///
260/// This method will panic if the `cpu_id` does not represent an AP or the AP's CPU-local storage
261/// has not been allocated.
262pub(crate) fn get_ap(cpu_id: CpuId) -> Paddr {
263 let offset = cpu_id
264 .as_usize()
265 .checked_sub(1)
266 .expect("The BSP does not have allocated CPU-local storage");
267
268 let paddr = CPU_LOCAL_STORAGES
269 .get()
270 .expect("No CPU-local storage has been allocated")[offset];
271 assert_ne!(
272 paddr,
273 0,
274 "The CPU-local storage for CPU {} is not allocated",
275 cpu_id.as_usize(),
276 );
277 paddr
278}
279
280mod is_used {
281 //! This module tracks whether any statically-allocated CPU-local
282 //! variables are used.
283 //!
284 //! [`copy_bsp_for_ap`] copies the CPU local data from the BSP
285 //! to the APs, so it requires as a safety condition that the
286 //! CPU-local data has not been accessed before the copy. This
287 //! module provides utilities to check if the safety condition
288 //! is met, but only if debug assertions are enabled.
289 //!
290 //! [`copy_bsp_for_ap`]: super::copy_bsp_for_ap
291
292 cfg_if::cfg_if! {
293 if #[cfg(debug_assertions)] {
294 use core::sync::atomic::{AtomicBool, Ordering};
295
296 static IS_USED: AtomicBool = AtomicBool::new(false);
297
298 pub fn debug_set_true() {
299 IS_USED.store(true, Ordering::Relaxed);
300 }
301
302 pub fn debug_assert_false() {
303 debug_assert!(!IS_USED.load(Ordering::Relaxed));
304 }
305 } else {
306 pub fn debug_set_true() {}
307
308 pub fn debug_assert_false() {}
309 }
310 }
311}
312
313#[cfg(ktest)]
314mod test {
315 use core::cell::RefCell;
316
317 use ostd_macros::ktest;
318
319 #[ktest]
320 fn test_cpu_local() {
321 crate::cpu_local! {
322 static FOO: RefCell<usize> = RefCell::new(1);
323 }
324 let irq_guard = crate::irq::disable_local();
325 let foo_guard = FOO.get_with(&irq_guard);
326 assert_eq!(*foo_guard.borrow(), 1);
327 *foo_guard.borrow_mut() = 2;
328 assert_eq!(*foo_guard.borrow(), 2);
329 drop(foo_guard);
330 }
331
332 #[ktest]
333 fn test_cpu_local_cell() {
334 crate::cpu_local_cell! {
335 static BAR: usize = 3;
336 }
337 let _guard = crate::irq::disable_local();
338 assert_eq!(BAR.load(), 3);
339 BAR.store(4);
340 assert_eq!(BAR.load(), 4);
341 }
342}