Turn GS.base validity into a global invariant

This commit is contained in:
Ruihan Li
2024-12-29 23:53:55 +08:00
committed by Tate, Hongliang Tian
parent b52d841ac1
commit 5651b93af0
11 changed files with 243 additions and 242 deletions

View File

@ -28,7 +28,11 @@ _start:
# 2. set sp (BSP only) # 2. set sp (BSP only)
lga sp, boot_stack_top lga sp, boot_stack_top
# 3. jump to rust riscv_boot # 3. set gp (CPU-local address)
.extern __cpu_local_start
lga gp, __cpu_local_start
# 4. jump to rust riscv_boot
lga t0, riscv_boot lga t0, riscv_boot
jr t0 jr t0

View File

@ -2,14 +2,6 @@
//! Architecture dependent CPU-local information utilities. //! Architecture dependent CPU-local information utilities.
pub(crate) unsafe fn set_base(addr: u64) {
core::arch::asm!(
"mv gp, {addr}",
addr = in(reg) addr,
options(preserves_flags, nostack)
);
}
pub(crate) fn get_base() -> u64 { pub(crate) fn get_base() -> u64 {
let mut gp; let mut gp;
unsafe { unsafe {

View File

@ -5,10 +5,6 @@
.global ap_boot_from_real_mode .global ap_boot_from_real_mode
.global ap_boot_from_long_mode .global ap_boot_from_long_mode
.extern boot_gdtr
.extern boot_page_table_start
.extern ap_early_entry
.section ".ap_boot", "awx" .section ".ap_boot", "awx"
.align 4096 .align 4096
@ -18,6 +14,7 @@ MMIO_XAPIC_APICID = 0xFEE00020
.macro setup_64bit_gdt_and_page_table eax .macro setup_64bit_gdt_and_page_table eax
// Use the 64-bit GDT. // Use the 64-bit GDT.
.extern boot_gdtr
lgdt [boot_gdtr] lgdt [boot_gdtr]
// Enable PAE and PGE. // Enable PAE and PGE.
@ -62,7 +59,7 @@ ap_boot_from_long_mode:
retf // 32-bit far return retf // 32-bit far return
.align 8 .align 8
retf_stack_bottom: retf_stack_bottom:
.long ap_long_mode .long ap_long_mode_in_low_address
.long 0x8 .long 0x8
retf_stack_top: retf_stack_top:
@ -161,10 +158,10 @@ ap_protect:
or eax, 1 << 31 or eax, 1 << 31
mov cr0, eax mov cr0, eax
ljmp 0x8, offset ap_long_mode ljmp 0x8, offset ap_long_mode_in_low_address
.code64 .code64
ap_long_mode: ap_long_mode_in_low_address:
mov ax, 0 mov ax, 0
mov ds, ax mov ds, ax
mov ss, ax mov ss, ax
@ -172,26 +169,40 @@ ap_long_mode:
mov fs, ax mov fs, ax
mov gs, ax mov gs, ax
// Update RIP to use the virtual address.
mov rax, offset ap_long_mode
jmp rax
.data
// This is a pointer to be filled by the BSP when boot information
// of all APs are allocated and initialized.
.global __ap_boot_info_array_pointer
.align 8
__ap_boot_info_array_pointer:
.skip 8
.text
.code64
ap_long_mode:
// The local APIC ID is in the RDI. // The local APIC ID is in the RDI.
mov rax, rdi mov rax, rdi
shl rax, 3 shl rax, 4 // 16-byte `PerApRawInfo`
mov rbx, [rip + __ap_boot_info_array_pointer]
// Setup the stack. // Setup the stack.
mov rbx, [__ap_boot_stack_array_pointer] mov rsp, [rbx + rax - 16] // raw_info[cpu_id - 1].stack_top
mov rsp, [rbx + rax] // Setup the GS base (the CPU-local address).
xor rbp, rbp mov rax, [rbx + rax - 8] // raw_info[cpu_id - 1].cpu_local
mov rdx, rax
shr rdx, 32 // EDX:EAX = raw_info.cpu_local
mov ecx, 0xC0000101 // ECX = GS.base
wrmsr
// Go to Rust code. // Go to Rust code.
.extern ap_early_entry
xor rbp, rbp
mov rax, offset ap_early_entry mov rax, offset ap_early_entry
call rax call rax
.extern halt # bsp_boot.S .extern halt # bsp_boot.S
jmp halt jmp halt
.data
// This is a pointer to be filled by the BSP when boot stacks
// of all APs are allocated and initialized.
.global __ap_boot_stack_array_pointer
.align 8
__ap_boot_stack_array_pointer:
.skip 8

View File

@ -309,7 +309,18 @@ long_mode:
sub rcx, rdi sub rcx, rdi
rep stosb rep stosb
// Call the corresponding Rust entrypoint according to the boot entrypoint // Clear RBP to stop the backtrace.
xor rbp, rbp
// Initialize the GS base to the CPU-local start address.
.extern __cpu_local_start
lea rax, [rip + __cpu_local_start]
mov rdx, rax
shr rdx, 32 // EDX:EAX = __cpu_local_start
mov ecx, 0xC0000101 // ECX = GS.base
wrmsr
// Call the corresponding Rust entrypoint according to the boot entrypoint.
pop rax pop rax
cmp rax, ENTRYTYPE_MULTIBOOT cmp rax, ENTRYTYPE_MULTIBOOT
je entry_type_multiboot je entry_type_multiboot
@ -329,8 +340,6 @@ long_mode:
entry_type_linux: entry_type_linux:
pop rdi // boot_params ptr pop rdi // boot_params ptr
xor rbp, rbp
lea rax, [rip + __linux_boot] // jump into Rust code lea rax, [rip + __linux_boot] // jump into Rust code
call rax call rax
jmp halt jmp halt
@ -339,8 +348,6 @@ entry_type_multiboot:
pop rsi // the address of multiboot info pop rsi // the address of multiboot info
pop rdi // multiboot magic pop rdi // multiboot magic
xor rbp, rbp
lea rax, [rip + __multiboot_entry] // jump into Rust code lea rax, [rip + __multiboot_entry] // jump into Rust code
call rax call rax
jmp halt jmp halt
@ -349,8 +356,6 @@ entry_type_multiboot2:
pop rsi // the address of multiboot info pop rsi // the address of multiboot info
pop rdi // multiboot magic pop rdi // multiboot magic
xor rbp, rbp
lea rax, [rip + __multiboot2_entry] // jump into Rust code lea rax, [rip + __multiboot2_entry] // jump into Rust code
call rax call rax
jmp halt jmp halt

View File

@ -37,9 +37,12 @@ use crate::{
Level, TriggerMode, Level, TriggerMode,
}, },
}, },
boot::memory_region::{MemoryRegion, MemoryRegionType}, boot::{
memory_region::{MemoryRegion, MemoryRegionType},
smp::PerApRawInfo,
},
if_tdx_enabled, if_tdx_enabled,
mm::{paddr_to_vaddr, PAGE_SIZE}, mm::{Paddr, PAGE_SIZE},
}; };
/// Counts the number of processors. /// Counts the number of processors.
@ -105,15 +108,16 @@ pub(crate) fn count_processors() -> Option<u32> {
/// # Safety /// # Safety
/// ///
/// The caller must ensure that /// The caller must ensure that
/// 1. we're in the boot context of the BSP, and /// 1. we're in the boot context of the BSP,
/// 2. all APs have not yet been booted. /// 2. all APs have not yet been booted, and
pub(crate) unsafe fn bringup_all_aps(num_cpus: u32) { /// 3. the arguments are valid to boot APs.
pub(crate) unsafe fn bringup_all_aps(info_ptr: *mut PerApRawInfo, pt_ptr: Paddr, num_cpus: u32) {
// SAFETY: The code and data to boot AP is valid to write because // SAFETY: The code and data to boot AP is valid to write because
// there are no readers and we are the only writer at this point. // there are no readers and we are the only writer at this point.
unsafe { unsafe {
copy_ap_boot_code(); copy_ap_boot_code();
fill_boot_stack_array_ptr(); fill_boot_info_ptr(info_ptr);
fill_boot_pt_ptr(); fill_boot_pt_ptr(pt_ptr);
} }
// SAFETY: We've properly prepared all the resources to boot APs. // SAFETY: We've properly prepared all the resources to boot APs.
@ -166,39 +170,30 @@ unsafe fn copy_ap_boot_code() {
/// # Safety /// # Safety
/// ///
/// The caller must ensure the pointer to be filled is valid to write. /// The caller must ensure the pointer to be filled is valid to write.
unsafe fn fill_boot_stack_array_ptr() { unsafe fn fill_boot_info_ptr(info_ptr: *mut PerApRawInfo) {
let pages = &crate::boot::smp::AP_BOOT_INFO
.get()
.unwrap()
.boot_stack_array;
let vaddr = paddr_to_vaddr(pages.start_paddr());
extern "C" { extern "C" {
static mut __ap_boot_stack_array_pointer: usize; static mut __ap_boot_info_array_pointer: *mut PerApRawInfo;
} }
// SAFETY: The safety is upheld by the caller. // SAFETY: The safety is upheld by the caller.
unsafe { unsafe {
__ap_boot_stack_array_pointer = vaddr; __ap_boot_info_array_pointer = info_ptr;
} }
} }
/// # Safety /// # Safety
/// ///
/// The caller must ensure the pointer to be filled is valid to write. /// The caller must ensure the pointer to be filled is valid to write.
unsafe fn fill_boot_pt_ptr() { unsafe fn fill_boot_pt_ptr(pt_ptr: Paddr) {
extern "C" { extern "C" {
static mut __boot_page_table_pointer: u32; static mut __boot_page_table_pointer: u32;
} }
let boot_pt = crate::mm::page_table::boot_pt::with_borrow(|pt| pt.root_address()) let pt_ptr32 = pt_ptr.try_into().unwrap();
.unwrap()
.try_into()
.unwrap();
// SAFETY: The safety is upheld by the caller. // SAFETY: The safety is upheld by the caller.
unsafe { unsafe {
__boot_page_table_pointer = boot_pt; __boot_page_table_pointer = pt_ptr32;
} }
} }

View File

@ -4,20 +4,6 @@
use x86_64::registers::segmentation::{Segment64, GS}; use x86_64::registers::segmentation::{Segment64, GS};
/// Sets the base address for the CPU local storage by writing to the GS base model-specific register.
/// This operation is marked as `unsafe` because it directly interfaces with low-level CPU registers.
///
/// # Safety
///
/// - This function is safe to call provided that the GS register is dedicated entirely for CPU local storage
/// and is not concurrently accessed for other purposes.
/// - The caller must ensure that `addr` is a valid address and properly aligned, as required by the CPU.
/// - This function should only be called in contexts where the CPU is in a state to accept such changes,
/// such as during processor initialization.
pub(crate) unsafe fn set_base(addr: u64) {
GS::write_base(x86_64::addr::VirtAddr::new(addr));
}
/// Gets the base address for the CPU local storage by reading the GS base model-specific register. /// Gets the base address for the CPU local storage by reading the GS base model-specific register.
pub(crate) fn get_base() -> u64 { pub(crate) fn get_base() -> u64 {
GS::read_base().as_u64() GS::read_base().as_u64()

View File

@ -2,14 +2,14 @@
//! Symmetric multiprocessing (SMP) boot support. //! Symmetric multiprocessing (SMP) boot support.
use alloc::collections::BTreeMap; use alloc::{boxed::Box, vec::Vec};
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use spin::Once; use spin::Once;
use crate::{ use crate::{
arch::boot::smp::bringup_all_aps, arch::boot::smp::bringup_all_aps,
cpu::{self, num_cpus}, cpu,
mm::{ mm::{
frame::{meta::KernelMeta, Segment}, frame::{meta::KernelMeta, Segment},
paddr_to_vaddr, FrameAllocOptions, PAGE_SIZE, paddr_to_vaddr, FrameAllocOptions, PAGE_SIZE,
@ -17,15 +17,15 @@ use crate::{
task::Task, task::Task,
}; };
pub(crate) static AP_BOOT_INFO: Once<ApBootInfo> = Once::new(); static AP_BOOT_INFO: Once<ApBootInfo> = Once::new();
const AP_BOOT_STACK_SIZE: usize = PAGE_SIZE * 64; const AP_BOOT_STACK_SIZE: usize = PAGE_SIZE * 64;
pub(crate) struct ApBootInfo { struct ApBootInfo {
/// It holds the boot stack top pointers used by all APs. /// Raw boot information for each AP.
pub(crate) boot_stack_array: Segment<KernelMeta>, per_ap_raw_info: Segment<KernelMeta>,
/// `per_ap_info` maps each AP's ID to its associated boot information. /// Boot information for each AP.
per_ap_info: BTreeMap<u32, PerApInfo>, per_ap_info: Box<[PerApInfo]>,
} }
struct PerApInfo { struct PerApInfo {
@ -38,6 +38,18 @@ struct PerApInfo {
boot_stack_pages: Segment<KernelMeta>, boot_stack_pages: Segment<KernelMeta>,
} }
/// Raw boot information for APs.
///
/// This is "raw" information that the assembly code (run by APs at startup,
/// before ever entering the Rust entry point) will directly access. So the
/// layout is important. **Update the assembly code if the layout is changed!**
#[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct PerApRawInfo {
stack_top: *mut u8,
cpu_local: *mut u8,
}
static AP_LATE_ENTRY: Once<fn()> = Once::new(); static AP_LATE_ENTRY: Once<fn()> = Once::new();
/// Boots all application processors. /// Boots all application processors.
@ -50,57 +62,70 @@ static AP_LATE_ENTRY: Once<fn()> = Once::new();
/// ///
/// This function can only be called in the boot context of the BSP where APs have /// This function can only be called in the boot context of the BSP where APs have
/// not yet been booted. /// not yet been booted.
pub fn boot_all_aps() { pub(crate) unsafe fn boot_all_aps() {
let num_cpus = num_cpus() as u32; let num_cpus = crate::cpu::num_cpus();
if num_cpus == 1 { if num_cpus == 1 {
return; return;
} }
log::info!("Booting {} processors.", num_cpus - 1); log::info!("Booting {} processors", num_cpus - 1);
// We currently assumes that bootstrap processor (BSP) have always the // We currently assume that
// processor ID 0. And the processor ID starts from 0 to `num_cpus - 1`. // 1. the bootstrap processor (BSP) has the processor ID 0;
// 2. the processor ID starts from `0` to `num_cpus - 1`.
AP_BOOT_INFO.call_once(|| { let mut per_ap_info = Vec::new();
let mut per_ap_info = BTreeMap::new();
// Use two pages to place stack pointers of all APs, thus support up to 1024 APs. let per_ap_raw_info = FrameAllocOptions::new()
let boot_stack_array = FrameAllocOptions::new() .zeroed(false)
.alloc_segment_with(
num_cpus
.saturating_sub(1)
.checked_mul(core::mem::size_of::<PerApRawInfo>())
.unwrap()
.div_ceil(PAGE_SIZE),
|_| KernelMeta,
)
.unwrap();
let raw_info_ptr = paddr_to_vaddr(per_ap_raw_info.start_paddr()) as *mut PerApRawInfo;
for ap in 1..num_cpus {
let boot_stack_pages = FrameAllocOptions::new()
.zeroed(false) .zeroed(false)
.alloc_segment_with(2, |_| KernelMeta) .alloc_segment_with(AP_BOOT_STACK_SIZE / PAGE_SIZE, |_| KernelMeta)
.unwrap(); .unwrap();
assert!(num_cpus < 1024);
for ap in 1..num_cpus { let raw_info = PerApRawInfo {
let boot_stack_pages = FrameAllocOptions::new() stack_top: paddr_to_vaddr(boot_stack_pages.end_paddr()) as *mut u8,
.zeroed(false) cpu_local: paddr_to_vaddr(crate::cpu::local::get_ap(ap.try_into().unwrap())) as *mut u8,
.alloc_segment_with(AP_BOOT_STACK_SIZE / PAGE_SIZE, |_| KernelMeta) };
.unwrap();
let boot_stack_ptr = paddr_to_vaddr(boot_stack_pages.end_paddr());
let stack_array_ptr = paddr_to_vaddr(boot_stack_array.start_paddr()) as *mut u64;
// SAFETY: The `stack_array_ptr` is valid and aligned.
unsafe {
stack_array_ptr
.add(ap as usize)
.write_volatile(boot_stack_ptr as u64);
}
per_ap_info.insert(
ap,
PerApInfo {
is_started: AtomicBool::new(false),
boot_stack_pages,
},
);
}
ApBootInfo { // SAFETY: The index is in range because we allocated enough memory.
boot_stack_array, let ptr = unsafe { raw_info_ptr.add(ap - 1) };
per_ap_info, // SAFETY: The memory is valid for writing because it was just allocated.
} unsafe { ptr.write(raw_info) };
per_ap_info.push(PerApInfo {
is_started: AtomicBool::new(false),
boot_stack_pages,
});
}
assert!(!AP_BOOT_INFO.is_completed());
AP_BOOT_INFO.call_once(move || ApBootInfo {
per_ap_raw_info,
per_ap_info: per_ap_info.into_boxed_slice(),
}); });
log::info!("Booting all application processors..."); log::info!("Booting all application processors...");
// SAFETY: The safety is upheld by the caller. let info_ptr = paddr_to_vaddr(AP_BOOT_INFO.get().unwrap().per_ap_raw_info.start_paddr())
unsafe { bringup_all_aps(num_cpus) }; as *mut PerApRawInfo;
let pt_ptr = crate::mm::page_table::boot_pt::with_borrow(|pt| pt.root_address()).unwrap();
// SAFETY: It's the right time to boot APs (guaranteed by the caller) and
// the arguments are valid to boot APs (generated above).
unsafe { bringup_all_aps(info_ptr, pt_ptr, num_cpus as u32) };
wait_for_all_aps_started(); wait_for_all_aps_started();
log::info!("All application processors started. The BSP continues to run."); log::info!("All application processors started. The BSP continues to run.");
@ -121,7 +146,6 @@ fn ap_early_entry(local_apic_id: u32) -> ! {
// SAFETY: we are on the AP and they are only called once with the correct // SAFETY: we are on the AP and they are only called once with the correct
// CPU ID. // CPU ID.
unsafe { unsafe {
cpu::local::init_on_ap(local_apic_id);
cpu::set_this_cpu_id(local_apic_id); cpu::set_this_cpu_id(local_apic_id);
} }
@ -145,10 +169,7 @@ fn ap_early_entry(local_apic_id: u32) -> ! {
// Mark the AP as started. // Mark the AP as started.
let ap_boot_info = AP_BOOT_INFO.get().unwrap(); let ap_boot_info = AP_BOOT_INFO.get().unwrap();
ap_boot_info ap_boot_info.per_ap_info[local_apic_id as usize - 1]
.per_ap_info
.get(&local_apic_id)
.unwrap()
.is_started .is_started
.store(true, Ordering::Release); .store(true, Ordering::Release);
@ -166,7 +187,7 @@ fn wait_for_all_aps_started() {
let ap_boot_info = AP_BOOT_INFO.get().unwrap(); let ap_boot_info = AP_BOOT_INFO.get().unwrap();
ap_boot_info ap_boot_info
.per_ap_info .per_ap_info
.values() .iter()
.all(|info| info.is_started.load(Ordering::Acquire)) .all(|info| info.is_started.load(Ordering::Acquire))
} }

View File

@ -110,7 +110,7 @@ impl<T: 'static> CpuLocalCell<T> {
/// must be taken to ensure that the borrowing rules are correctly /// must be taken to ensure that the borrowing rules are correctly
/// enforced, since the interrupts may come asynchronously. /// enforced, since the interrupts may come asynchronously.
pub fn as_mut_ptr(&'static self) -> *mut T { pub fn as_mut_ptr(&'static self) -> *mut T {
super::has_init::assert_true(); super::is_used::debug_set_true();
let offset = { let offset = {
let bsp_va = self as *const _ as usize; let bsp_va = self as *const _ as usize;

View File

@ -109,7 +109,7 @@ impl<T: 'static> CpuLocal<T> {
/// ///
/// The caller must ensure that the reference to `self` is static. /// The caller must ensure that the reference to `self` is static.
pub(crate) unsafe fn as_ptr(&'static self) -> *const T { pub(crate) unsafe fn as_ptr(&'static self) -> *const T {
super::has_init::assert_true(); super::is_used::debug_set_true();
let offset = self.get_offset(); let offset = self.get_offset();
@ -140,16 +140,23 @@ impl<T: 'static + Sync> CpuLocal<T> {
/// ///
/// Panics if the CPU ID is out of range. /// Panics if the CPU ID is out of range.
pub fn get_on_cpu(&'static self, cpu_id: CpuId) -> &'static T { pub fn get_on_cpu(&'static self, cpu_id: CpuId) -> &'static T {
super::has_init::assert_true(); super::is_used::debug_set_true();
let cpu_id = cpu_id.as_usize();
// If on the BSP, just use the statically linked storage. // If on the BSP, just use the statically linked storage.
if cpu_id.as_usize() == 0 { if cpu_id == 0 {
return &self.0; return &self.0;
} }
// SAFETY: Here we use `Once::get_unchecked` to make getting the CPU- // SAFETY: Here we use `Once::get_unchecked` to make getting the CPU-
// local base faster. The storages must be initialized here (since this // local base faster. The storages must be initialized here (since this
// is not the BSP) so it is safe to do so. // is not the BSP) so it is safe to do so.
let base = unsafe { super::CPU_LOCAL_STORAGES.get_unchecked().get(cpu_id) }; let base = unsafe {
*super::CPU_LOCAL_STORAGES
.get_unchecked()
.get_unchecked(cpu_id - 1)
};
let base = crate::mm::paddr_to_vaddr(base); let base = crate::mm::paddr_to_vaddr(base);
let offset = self.get_offset(); let offset = self.get_offset();

View File

@ -42,10 +42,7 @@ pub use cpu_local::{CpuLocal, CpuLocalDerefGuard};
use spin::Once; use spin::Once;
use super::CpuId; use super::CpuId;
use crate::{ use crate::mm::{frame::allocator, paddr_to_vaddr, Paddr, PAGE_SIZE};
arch,
mm::{frame::allocator, paddr_to_vaddr, Paddr, PAGE_SIZE},
};
// These symbols are provided by the linker script. // These symbols are provided by the linker script.
extern "C" { extern "C" {
@ -53,147 +50,128 @@ extern "C" {
fn __cpu_local_end(); fn __cpu_local_end();
} }
/// The BSP initializes the CPU-local areas for APs. /// The CPU-local areas for APs.
static CPU_LOCAL_STORAGES: Once<CpuLocalStoragePointers> = Once::new(); static CPU_LOCAL_STORAGES: Once<&'static [Paddr]> = Once::new();
struct CpuLocalStoragePointers(&'static mut [Paddr]); /// Copies the CPU-local data on the bootstrap processor (BSP)
/// for application processors (APs).
///
/// # Safety
///
/// This function must be called in the boot context of the BSP, at a time
/// when the APs have not yet booted.
///
/// The CPU-local data on the BSP must not be used before calling this
/// function to copy it for the APs. Otherwise, the copied data will
/// contain non-constant (also non-`Copy`) data, resulting in undefined
/// behavior when it's loaded on the APs.
pub(crate) unsafe fn copy_bsp_for_ap() {
let num_aps = super::num_cpus() - 1; // BSP does not need allocated storage.
if num_aps == 0 {
return;
}
impl CpuLocalStoragePointers { // Allocate a region to store the pointers to the CPU-local storage segments.
/// Allocates frames for storing CPU-local data for each AP. let res = {
/// let size = core::mem::size_of::<Paddr>()
/// # Safety .checked_mul(num_aps)
/// .unwrap()
/// The caller must ensure that the `num_cpus` matches the number of all .align_up(PAGE_SIZE);
/// CPUs that will access CPU local storage.
///
/// The BSP's CPU-local storage should not be touched before calling
/// this method, since the CPU-local storage for APs is copied from the
/// BSP's CPU-local storage.
unsafe fn allocate(num_cpus: usize) -> Self {
let num_aps = num_cpus - 1; // BSP does not need allocated storage.
assert!(num_aps > 0, "No APs to allocate CPU-local storage");
// Allocate a region to store the pointers to the CPU-local storage segments.
let size = (core::mem::size_of::<Paddr>() * num_aps).align_up(PAGE_SIZE);
let addr = let addr =
allocator::early_alloc(Layout::from_size_align(size, PAGE_SIZE).unwrap()).unwrap(); allocator::early_alloc(Layout::from_size_align(size, PAGE_SIZE).unwrap()).unwrap();
let ptr = paddr_to_vaddr(addr) as *mut Paddr; let ptr = paddr_to_vaddr(addr) as *mut Paddr;
// SAFETY: The memory is allocated and the pointer is valid.
// SAFETY: The memory is properly allocated. We exclusively own it. So it's valid to write.
unsafe { unsafe {
core::ptr::write_bytes(ptr as *mut u8, 0, size); core::ptr::write_bytes(ptr as *mut u8, 0, size);
} }
// SAFETY: The memory would not be deallocated. And it is valid. // SAFETY: The memory is properly allocated and initialized. We exclusively own it. We
let res = Self(unsafe { core::slice::from_raw_parts_mut(ptr, num_aps) }); // never deallocate it so it lives for '`static'. So we can create a mutable slice on it.
unsafe { core::slice::from_raw_parts_mut(ptr, num_aps) }
};
// Allocate the CPU-local storage segments for APs. let bsp_base_va = __cpu_local_start as usize;
let bsp_base_va = __cpu_local_start as usize; let bsp_end_va = __cpu_local_end as usize;
let bsp_end_va = __cpu_local_end as usize;
for id in 0..num_aps {
let ap_pages = {
let nbytes = (bsp_end_va - bsp_base_va).align_up(PAGE_SIZE);
allocator::early_alloc(Layout::from_size_align(nbytes, PAGE_SIZE).unwrap()).unwrap()
};
let ap_pages_ptr = paddr_to_vaddr(ap_pages) as *mut u8;
// SAFETY: The BSP has not initialized the CPU-local area, so the objects in // Allocate the CPU-local storage segments for APs.
// in the `.cpu_local` section can be bitwise bulk copied to the AP's local for res_addr_mut in res.iter_mut() {
// storage. The destination memory is allocated so it is valid to write to. let nbytes = (bsp_end_va - bsp_base_va).align_up(PAGE_SIZE);
unsafe { let ap_pages =
core::ptr::copy_nonoverlapping( allocator::early_alloc(Layout::from_size_align(nbytes, PAGE_SIZE).unwrap()).unwrap();
bsp_base_va as *const u8, let ap_pages_ptr = paddr_to_vaddr(ap_pages) as *mut u8;
ap_pages_ptr,
bsp_end_va - bsp_base_va,
);
}
res.0[id] = ap_pages; // SAFETY:
// 1. The source is valid to read because it has not been used before,
// so it contains only constants.
// 2. The destination is valid to write because it is just allocated.
// 3. The memory is aligned because the alignment of `u8` is 1.
// 4. The two memory regions do not overlap because allocated memory
// regions never overlap with the kernel data.
unsafe {
core::ptr::copy_nonoverlapping(bsp_base_va as *const u8, ap_pages_ptr, nbytes);
} }
res *res_addr_mut = ap_pages;
} }
fn get(&self, cpu_id: CpuId) -> Paddr { is_used::debug_assert_false();
let offset = cpu_id
.as_usize()
.checked_sub(1)
.expect("The BSP does not have allocated CPU-local storage");
let paddr = self.0[offset]; assert!(!CPU_LOCAL_STORAGES.is_completed());
assert!( CPU_LOCAL_STORAGES.call_once(|| res);
paddr != 0,
"The CPU-local storage for CPU {} is not allocated",
cpu_id.as_usize()
);
paddr
}
} }
/// Initializes the CPU local data for the bootstrap processor (BSP). /// Gets the pointer to the CPU-local storage for the given AP.
/// ///
/// # Safety /// # Panics
/// ///
/// This function can only called on the BSP, for once. /// This method will panic if the `cpu_id` does not represent an AP or the AP's CPU-local storage
/// /// has not been allocated.
/// It must be guaranteed that the BSP will not access local data before pub(crate) fn get_ap(cpu_id: CpuId) -> Paddr {
/// this function being called, otherwise copying non-constant values let offset = cpu_id
/// will result in pretty bad undefined behavior. .as_usize()
pub unsafe fn init_on_bsp() { .checked_sub(1)
let num_cpus = super::num_cpus(); .expect("The BSP does not have allocated CPU-local storage");
if num_cpus > 1 { let paddr = CPU_LOCAL_STORAGES
// SAFETY: The number of CPUs is correct. And other conditions are
// the caller's safety conditions.
let cpu_local_storages = unsafe { CpuLocalStoragePointers::allocate(num_cpus) };
CPU_LOCAL_STORAGES.call_once(|| cpu_local_storages);
}
arch::cpu::local::set_base(__cpu_local_start as usize as u64);
has_init::set_true();
}
/// Initializes the CPU local data for the application processor (AP).
///
/// # Safety
///
/// This function can only called on the AP.
pub unsafe fn init_on_ap(cpu_id: u32) {
let ap_pages = CPU_LOCAL_STORAGES
.get() .get()
.unwrap() .expect("No CPU-local storage has been allocated")[offset];
.get(CpuId::try_from(cpu_id as usize).unwrap()); assert_ne!(
paddr,
let ap_pages_ptr = paddr_to_vaddr(ap_pages) as *mut u32; 0,
"The CPU-local storage for CPU {} is not allocated",
// SAFETY: the memory will be dedicated to the AP. And we are on the AP. cpu_id.as_usize(),
unsafe { );
arch::cpu::local::set_base(ap_pages_ptr as u64); paddr
}
} }
mod has_init { mod is_used {
//! This module is used to detect the programming error of using the CPU-local //! This module tracks whether any CPU-local variables are used.
//! mechanism before it is initialized. Such bugs have been found before and we //!
//! do not want to repeat this error again. This module is only incurs runtime //! [`copy_bsp_for_ap`] copies the CPU local data from the BSP
//! overhead if debug assertions are enabled. //! to the APs, so it requires as a safety condition that the
//! CPU-local data has not been accessed before the copy. This
//! module provides utilities to check if the safety condition
//! is met, but only if debug assertions are enabled.
//!
//! [`copy_bsp_for_ap`]: super::copy_bsp_for_ap
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(debug_assertions)] { if #[cfg(debug_assertions)] {
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
static IS_INITIALIZED: AtomicBool = AtomicBool::new(false); static IS_USED: AtomicBool = AtomicBool::new(false);
pub fn assert_true() { pub fn debug_set_true() {
debug_assert!(IS_INITIALIZED.load(Ordering::Relaxed)); IS_USED.store(true, Ordering::Relaxed);
} }
pub fn set_true() { pub fn debug_assert_false() {
IS_INITIALIZED.store(true, Ordering::Relaxed); debug_assert!(!IS_USED.load(Ordering::Relaxed));
} }
} else { } else {
pub fn assert_true() {} pub fn debug_set_true() {}
pub fn set_true() {} pub fn debug_assert_false() {}
} }
} }
} }

View File

@ -84,11 +84,13 @@ unsafe fn init() {
logger::init(); logger::init();
// SAFETY: They are only called once on BSP and ACPI has been initialized. // SAFETY:
// No CPU local objects have been accessed by this far. // 1. They are only called once in the boot context of the BSP.
// 2. The number of CPUs are available because ACPI has been initialized.
// 3. No CPU-local objects have been accessed yet.
unsafe { unsafe {
cpu::init_num_cpus(); cpu::init_num_cpus();
cpu::local::init_on_bsp(); cpu::local::copy_bsp_for_ap();
cpu::set_this_cpu_id(0); cpu::set_this_cpu_id(0);
} }