ostd/boot/
smp.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! Symmetric multiprocessing (SMP) boot support.
4
5use alloc::{boxed::Box, collections::btree_map::BTreeMap, vec::Vec};
6
7use spin::Once;
8
9use crate::{
10    arch::irq::HwCpuId,
11    mm::{
12        FrameAllocOptions, HasPaddrRange, PAGE_SIZE,
13        frame::{Segment, meta::KernelMeta},
14        paddr_to_vaddr,
15    },
16    sync::SpinLock,
17    task::Task,
18    util::id_set::Id,
19};
20
21static AP_BOOT_INFO: Once<ApBootInfo> = Once::new();
22
23const AP_BOOT_STACK_SIZE: usize = PAGE_SIZE * 64;
24
25struct ApBootInfo {
26    /// Raw boot information for each AP.
27    per_ap_raw_info: Box<[PerApRawInfo]>,
28    /// Boot information for each AP.
29    #[expect(dead_code)]
30    per_ap_info: Box<[PerApInfo]>,
31}
32
33struct PerApInfo {
34    // TODO: When the AP starts up and begins executing tasks, the boot stack will
35    // no longer be used, and the `Segment` can be deallocated (this problem also
36    // exists in the boot processor, but the memory it occupies should be returned
37    // to the frame allocator).
38    #[expect(dead_code)]
39    boot_stack_pages: Segment<KernelMeta>,
40}
41
42/// Raw boot information for APs.
43///
44/// This is "raw" information that the assembly code (run by APs at startup,
45/// before ever entering the Rust entry point) will directly access. So the
46/// layout is important. **Update the assembly code if the layout is changed!**
47#[repr(C)]
48#[derive(Clone, Copy)]
49pub(crate) struct PerApRawInfo {
50    stack_top: *mut u8,
51    cpu_local: *mut u8,
52}
53
54// SAFETY: This information (i.e., the pointer addresses) can be shared safely
55// among multiple threads. However, it is the responsibility of the user to
56// ensure that the contained pointers are used safely.
57unsafe impl Send for PerApRawInfo {}
58unsafe impl Sync for PerApRawInfo {}
59
60static HW_CPU_ID_MAP: SpinLock<BTreeMap<u32, HwCpuId>> = SpinLock::new(BTreeMap::new());
61
62/// Boots all application processors.
63///
64/// This function should be called late in the system startup. The system must at
65/// least ensure that the scheduler, ACPI table, memory allocation, and IPI module
66/// have been initialized.
67///
68/// # Safety
69///
70/// This function can only be called in the boot context of the BSP where APs have
71/// not yet been booted.
72pub(crate) unsafe fn boot_all_aps() {
73    // Mark the BSP as started.
74    report_online_and_hw_cpu_id(crate::cpu::CpuId::bsp().as_usize().try_into().unwrap());
75
76    let num_cpus = crate::cpu::num_cpus();
77
78    if num_cpus == 1 {
79        return;
80    }
81    log::info!("Booting {} processors", num_cpus - 1);
82
83    let mut per_ap_raw_info = Vec::with_capacity(num_cpus);
84    let mut per_ap_info = Vec::with_capacity(num_cpus);
85
86    for ap in 1..num_cpus {
87        let boot_stack_pages = FrameAllocOptions::new()
88            .zeroed(false)
89            .alloc_segment_with(AP_BOOT_STACK_SIZE / PAGE_SIZE, |_| KernelMeta)
90            .unwrap();
91
92        per_ap_raw_info.push(PerApRawInfo {
93            stack_top: paddr_to_vaddr(boot_stack_pages.end_paddr()) as *mut u8,
94            cpu_local: paddr_to_vaddr(crate::cpu::local::get_ap(ap.try_into().unwrap())) as *mut u8,
95        });
96        per_ap_info.push(PerApInfo { boot_stack_pages });
97    }
98
99    assert!(!AP_BOOT_INFO.is_completed());
100    AP_BOOT_INFO.call_once(move || ApBootInfo {
101        per_ap_raw_info: per_ap_raw_info.into_boxed_slice(),
102        per_ap_info: per_ap_info.into_boxed_slice(),
103    });
104
105    log::info!("Booting all application processors...");
106
107    let info_ptr = AP_BOOT_INFO.get().unwrap().per_ap_raw_info.as_ptr();
108    let pt_ptr = crate::mm::page_table::boot_pt::with_borrow(|pt| pt.root_address()).unwrap();
109    // SAFETY: It's the right time to boot APs (guaranteed by the caller) and
110    // the arguments are valid to boot APs (generated above).
111    unsafe { crate::arch::boot::smp::bringup_all_aps(info_ptr, pt_ptr, num_cpus as u32) };
112
113    wait_for_all_aps_started(num_cpus);
114
115    log::info!("All application processors started. The BSP continues to run.");
116}
117
118static AP_LATE_ENTRY: Once<fn()> = Once::new();
119
120/// Registers the entry function for the application processor.
121///
122/// Once the entry function is registered, all the application processors
123/// will jump to the entry function immediately.
124pub fn register_ap_entry(entry: fn()) {
125    AP_LATE_ENTRY.call_once(|| entry);
126}
127
128/// The AP's entry point of the Rust code portion of Asterinas.
129///
130/// # Safety
131///
132/// - This function must be called only once on each AP at a proper timing in the AP's boot
133///   assembly code, or via a thin Rust wrapper that does not access uninitialized AP states.
134/// - The caller must follow C calling conventions and put the right arguments in registers.
135// SAFETY: The name does not collide with other symbols.
136#[unsafe(no_mangle)]
137pub(crate) unsafe extern "C" fn ap_early_entry(cpu_id: u32) -> ! {
138    // SAFETY:
139    // 1. We're in the boot context of an AP.
140    // 2. The CPU ID of the AP is correct.
141    unsafe { crate::cpu::init_on_ap(cpu_id) };
142
143    crate::arch::enable_cpu_features();
144
145    // SAFETY: This is called only once on this AP in the boot context.
146    unsafe { crate::arch::trap::init_on_cpu() };
147
148    // SAFETY: This function is only called once on this AP.
149    unsafe { crate::mm::kspace::activate_kernel_page_table() };
150
151    // SAFETY: This function is only called once on this AP, after the BSP has
152    // done the architecture-specific initialization.
153    unsafe { crate::arch::init_on_ap() };
154
155    crate::arch::irq::enable_local();
156
157    // SAFETY:
158    // 1. The kernel page table is activated on this AP.
159    // 2. The function is called only once on this AP.
160    // 3. No remaining `with_borrow` invocations on this CPU from now on.
161    unsafe { crate::mm::page_table::boot_pt::dismiss() };
162
163    // Mark the AP as started.
164    report_online_and_hw_cpu_id(cpu_id);
165
166    log::info!("Processor {} started. Spinning for tasks.", cpu_id);
167
168    let ap_late_entry = AP_LATE_ENTRY.wait();
169    ap_late_entry();
170
171    Task::yield_now();
172    unreachable!("`yield_now` in the boot context should not return");
173}
174
175fn report_online_and_hw_cpu_id(cpu_id: u32) {
176    // There are no races because this method will only be called in the boot
177    // context, where preemption won't occur.
178    let hw_cpu_id = HwCpuId::read_current(&crate::task::disable_preempt());
179
180    let old_val = HW_CPU_ID_MAP.lock().insert(cpu_id, hw_cpu_id);
181    assert!(old_val.is_none());
182}
183
184fn wait_for_all_aps_started(num_cpus: usize) {
185    fn is_all_aps_started(num_cpus: usize) -> bool {
186        HW_CPU_ID_MAP.lock().len() == num_cpus
187    }
188
189    while !is_all_aps_started(num_cpus) {
190        core::hint::spin_loop();
191    }
192}
193
194/// Constructs a boxed slice that maps [`CpuId`] to [`HwCpuId`].
195///
196/// # Panics
197///
198/// This method will panic if it is called either before all APs have booted or more than once.
199///
200/// [`CpuId`]: crate::cpu::CpuId
201pub(crate) fn construct_hw_cpu_id_mapping() -> Box<[HwCpuId]> {
202    let mut hw_cpu_id_map = HW_CPU_ID_MAP.lock();
203    assert_eq!(hw_cpu_id_map.len(), crate::cpu::num_cpus());
204
205    let result = hw_cpu_id_map
206        .values()
207        .cloned()
208        .collect::<Vec<_>>()
209        .into_boxed_slice();
210    hw_cpu_id_map.clear();
211
212    result
213}