chore: remove kernel patch artifacts
This commit is contained in:
@@ -1,591 +0,0 @@
|
||||
use core::{
|
||||
hint,
|
||||
sync::atomic::{AtomicU8, Ordering},
|
||||
};
|
||||
|
||||
use x86::time::rdtsc;
|
||||
|
||||
use crate::{
|
||||
arch::{
|
||||
device::local_apic::the_local_apic,
|
||||
start::{kstart_ap, KernelArgsAp},
|
||||
},
|
||||
cpu_set::LogicalCpuId,
|
||||
memory::{
|
||||
allocate_p2frame, map_device_memory, Frame, KernelMapper, Page, PageFlags,
|
||||
PhysicalAddress, RmmA, RmmArch, VirtualAddress, PAGE_SIZE,
|
||||
},
|
||||
startup::AP_READY,
|
||||
};
|
||||
|
||||
use super::{Madt, MadtEntry};
|
||||
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Maximum number of APIC→CPU mappings we track for NUMA topology.
|
||||
const MAX_APIC_MAPPINGS: usize = 256;
|
||||
|
||||
struct ApicMapping {
|
||||
apic_id: u32,
|
||||
cpu_id: LogicalCpuId,
|
||||
}
|
||||
|
||||
const UNINIT_MAPPING: ApicMapping = ApicMapping { apic_id: u32::MAX, cpu_id: LogicalCpuId::new(0) };
|
||||
|
||||
static mut APIC_MAPPINGS: [ApicMapping; MAX_APIC_MAPPINGS] = [UNINIT_MAPPING; MAX_APIC_MAPPINGS];
|
||||
static mut APIC_MAPPING_COUNT: usize = 0;
|
||||
|
||||
unsafe fn record_apic_mapping(apic_id: u32, cpu_id: LogicalCpuId) {
|
||||
let count = APIC_MAPPING_COUNT;
|
||||
if count < MAX_APIC_MAPPINGS {
|
||||
APIC_MAPPINGS[count] = ApicMapping { apic_id, cpu_id };
|
||||
APIC_MAPPING_COUNT = count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const AP_SPIN_LIMIT: u32 = 1_000_000;
|
||||
const TRAMPOLINE: usize = 0x8000;
|
||||
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
|
||||
|
||||
/// Estimate TSC frequency in MHz from CPUID.
|
||||
///
|
||||
/// Tries CPUID leaf 0x16 (Processor Frequency Information) first,
|
||||
/// then CPUID leaf 0x15 (TSC/Core Crystal Clock Ratio).
|
||||
/// Returns None if frequency cannot be determined.
|
||||
fn tsc_freq_mhz_cpuid() -> Option<u64> {
|
||||
let max_leaf = unsafe { core::arch::x86_64::__cpuid(0).eax as u32 };
|
||||
|
||||
// CPUID leaf 0x16: EAX = Core Base Frequency in MHz (Intel)
|
||||
if max_leaf >= 0x16 {
|
||||
let mhz = unsafe { core::arch::x86_64::__cpuid(0x16) }.eax as u64;
|
||||
if mhz > 0 {
|
||||
return Some(mhz);
|
||||
}
|
||||
}
|
||||
|
||||
// CPUID leaf 0x15: EAX = denominator, EBX = numerator, ECX = crystal Hz
|
||||
if max_leaf >= 0x15 {
|
||||
let res = unsafe { core::arch::x86_64::__cpuid(0x15) };
|
||||
let denom = res.eax as u64;
|
||||
let numer = res.ebx as u64;
|
||||
let crystal_hz = res.ecx as u64;
|
||||
if denom > 0 && numer > 0 && crystal_hz > 0 {
|
||||
// TSC freq = crystal_hz * numer / denom
|
||||
let tsc_hz = crystal_hz * numer / denom;
|
||||
return Some(tsc_hz / 1_000_000); // Hz → MHz
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Early-boot microsecond delay using the Time Stamp Counter.
|
||||
///
|
||||
/// Uses CPUID-based TSC frequency estimation when available.
|
||||
/// Falls back to a conservative spin loop calibrated for the
|
||||
/// minimum expected CPU speed (1 GHz).
|
||||
///
|
||||
/// # Safety
|
||||
/// Must only be called after the BSP TSC is running (always true
|
||||
/// after CPU reset on x86).
|
||||
fn early_udelay(us: u64) {
|
||||
if let Some(mhz) = tsc_freq_mhz_cpuid() {
|
||||
// TSC-based delay: precise on invariant TSC (all modern x86).
|
||||
// MHz = cycles per µs.
|
||||
let target = unsafe { rdtsc() } + us * mhz;
|
||||
while unsafe { rdtsc() } < target {
|
||||
hint::spin_loop();
|
||||
}
|
||||
} else {
|
||||
// Fallback: conservative spin loop.
|
||||
// spin_loop() (PAUSE) is ~40 cycles on modern Intel, ~1 on AMD.
|
||||
// At 1 GHz minimum: 1000 cycles/µs ÷ 40 cycles/iter = 25 iters/µs.
|
||||
// Use 50 iters/µs for safety margin on slower/variable CPUs.
|
||||
let iters = us.saturating_mul(50);
|
||||
for _ in 0..iters {
|
||||
hint::spin_loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_x2apic_processor_uid(madt: &Madt, apic_id: u32) -> Option<u32> {
|
||||
madt.iter().find_map(|entry| match entry {
|
||||
MadtEntry::LocalX2Apic(x2apic) if x2apic.x2apic_id == apic_id => Some(x2apic.processor_uid),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_lapic_address_override(
|
||||
local_apic: &mut crate::arch::device::local_apic::LocalApic,
|
||||
address: u64,
|
||||
) {
|
||||
if local_apic.x2 || address == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(physaddr) = usize::try_from(address) else {
|
||||
warn!(
|
||||
"Ignoring LAPIC address override {:#x}: does not fit host usize",
|
||||
address
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
let mapped = unsafe { map_device_memory(PhysicalAddress::new(physaddr), 4096) }.data();
|
||||
local_apic.address = mapped;
|
||||
debug!("Applied LAPIC address override: {:#x}", address);
|
||||
}
|
||||
|
||||
pub(super) fn init(madt: Madt) {
|
||||
let local_apic = unsafe { the_local_apic() };
|
||||
let me = local_apic.id();
|
||||
|
||||
if local_apic.x2 {
|
||||
debug!(" X2APIC {}", me.get());
|
||||
} else {
|
||||
debug!(" XAPIC {}: {:>08X}", me.get(), local_apic.address);
|
||||
}
|
||||
|
||||
if cfg!(not(feature = "multi_core")) {
|
||||
unsafe {
|
||||
record_apic_mapping(me.get(), LogicalCpuId::new(0));
|
||||
}
|
||||
crate::numa::init_default();
|
||||
return;
|
||||
}
|
||||
|
||||
// Map trampoline
|
||||
let trampoline_frame = Frame::containing(PhysicalAddress::new(TRAMPOLINE));
|
||||
let trampoline_page = Page::containing_address(VirtualAddress::new(TRAMPOLINE));
|
||||
let (result, page_table_physaddr) = unsafe {
|
||||
//TODO: do not have writable and executable!
|
||||
let mut mapper = KernelMapper::lock_rw();
|
||||
|
||||
let result = match mapper.map_phys(
|
||||
trampoline_page.start_address(),
|
||||
trampoline_frame.base(),
|
||||
PageFlags::new().execute(true).write(true),
|
||||
) {
|
||||
Some(result) => result,
|
||||
None => {
|
||||
println!("KERNEL AP: failed to map trampoline page, AP bring-up disabled");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
(result, mapper.table().phys().data())
|
||||
};
|
||||
result.flush();
|
||||
|
||||
// Write trampoline, make sure TRAMPOLINE page is free for use
|
||||
for (i, val) in TRAMPOLINE_DATA.iter().enumerate() {
|
||||
unsafe {
|
||||
(*((TRAMPOLINE as *mut u8).add(i) as *const AtomicU8)).store(*val, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let preliminary_cpu_count = madt
|
||||
.iter()
|
||||
.filter(|entry| match entry {
|
||||
MadtEntry::LocalApic(local) => u32::from(local.id) == me.get() || local.flags & 1 == 1,
|
||||
MadtEntry::LocalX2Apic(local) => local.x2apic_id == me.get() || local.flags & 1 == 1,
|
||||
_ => false,
|
||||
})
|
||||
.count();
|
||||
crate::profiling::allocate(preliminary_cpu_count as u32);
|
||||
}
|
||||
|
||||
// Firmware bug detection: check for duplicate APIC IDs in MADT.
|
||||
// Some firmware (especially on early BIOS/UEFI) may list the same
|
||||
// processor multiple times. Keep first occurrence, warn on duplicates.
|
||||
let mut seen_apic_ids: BTreeSet<u32> = BTreeSet::new();
|
||||
{
|
||||
let _ = seen_apic_ids.insert(me.get()); // BSP
|
||||
for entry in madt.iter() {
|
||||
match entry {
|
||||
MadtEntry::LocalApic(local) if local.flags & 1 == 1 => {
|
||||
let id = u32::from(local.id);
|
||||
if !seen_apic_ids.insert(id) {
|
||||
warn!("MADT: duplicate APIC ID {} in LocalApic entry, firmware bug", id);
|
||||
}
|
||||
}
|
||||
MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 => {
|
||||
let id = local.x2apic_id;
|
||||
if !seen_apic_ids.insert(id) {
|
||||
warn!("MADT: duplicate x2APIC ID {} in LocalX2Apic entry, firmware bug", id);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for madt_entry in madt.iter() {
|
||||
debug!(" {:x?}", madt_entry);
|
||||
if let MadtEntry::LocalApic(ap_local_apic) = madt_entry {
|
||||
if u32::from(ap_local_apic.id) == me.get() {
|
||||
debug!(" This is my local APIC");
|
||||
} else if ap_local_apic.flags & 1 == 1 {
|
||||
// Allocate a stack
|
||||
let alloc = match allocate_p2frame(4) {
|
||||
Some(frame) => frame,
|
||||
None => {
|
||||
println!("KERNEL AP: CPU {} no memory for stack, skipping", ap_local_apic.id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let stack_start = RmmA::phys_to_virt(alloc.base()).data();
|
||||
let stack_end = stack_start + (PAGE_SIZE << 4);
|
||||
|
||||
// Atomically allocate a CPU ID — fetch_add is SeqCst so that
|
||||
// all later stores (PercpuBlock, NUMA node) are ordered after.
|
||||
let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
|
||||
if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
|
||||
println!(
|
||||
"KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
|
||||
ap_local_apic.id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
|
||||
|
||||
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
|
||||
|
||||
let args = KernelArgsAp {
|
||||
stack_end: stack_end as *mut u8,
|
||||
cpu_id,
|
||||
pcr_ptr,
|
||||
idt_ptr,
|
||||
};
|
||||
|
||||
let ap_ready = (TRAMPOLINE + 8) as *mut u64;
|
||||
let ap_args_ptr = unsafe { ap_ready.add(1) };
|
||||
let ap_page_table = unsafe { ap_ready.add(2) };
|
||||
let ap_code = unsafe { ap_ready.add(3) };
|
||||
|
||||
// Set the ap_ready to 0, volatile
|
||||
unsafe {
|
||||
ap_ready.write(0);
|
||||
ap_args_ptr.write(&args as *const _ as u64);
|
||||
ap_page_table.write(page_table_physaddr as u64);
|
||||
#[expect(clippy::fn_to_numeric_cast)]
|
||||
ap_code.write(kstart_ap as u64);
|
||||
|
||||
// Ensure all trampoline writes are visible to the AP before
|
||||
// it starts executing. asm!("") is only a compiler barrier;
|
||||
// fence(SeqCst) is a full hardware memory barrier.
|
||||
core::sync::atomic::fence(Ordering::SeqCst);
|
||||
};
|
||||
AP_READY.store(false, Ordering::SeqCst);
|
||||
|
||||
// Clear APIC Error Status Register before starting AP.
|
||||
// Intel SDM §8.4.4: ESR should be cleared before sending SIPI.
|
||||
unsafe { local_apic.esr(); }
|
||||
|
||||
// Send INIT IPI (Assert)
|
||||
{
|
||||
// ICR: Delivery Mode=INIT(101), Level=Assert, Trigger=Edge
|
||||
let mut icr = 0x4500u64;
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(ap_local_apic.id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(ap_local_apic.id) << 56;
|
||||
}
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Intel SDM Vol 3A §8.4.4: wait 10ms after INIT deassert
|
||||
// before sending first SIPI. Modern CPUs may need less,
|
||||
// but 10ms is the safe specification-compliant value.
|
||||
early_udelay(10_000);
|
||||
|
||||
// Send START IPI #1
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
// ICR: Delivery Mode=StartUp(110), Vector=ap_segment
|
||||
// Note: bit 14 (Level) must be 0 for SIPI per Intel SDM.
|
||||
let mut icr = 0x0600 | ap_segment as u64;
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(ap_local_apic.id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(ap_local_apic.id) << 56;
|
||||
}
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Intel SDM: wait 200µs between SIPIs
|
||||
early_udelay(200);
|
||||
|
||||
// Send START IPI #2 (recommended for compatibility)
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x0600 | ap_segment as u64;
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(ap_local_apic.id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(ap_local_apic.id) << 56;
|
||||
}
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Wait briefly for SIPI to be accepted
|
||||
early_udelay(200);
|
||||
|
||||
// Check ESR for delivery errors after SIPI sequence.
|
||||
// Bit 5 = Send Accept Error, Bit 6 = Send Illegal Vector.
|
||||
let esr_val = unsafe { local_apic.esr() };
|
||||
if esr_val != 0 {
|
||||
println!(
|
||||
"KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
|
||||
ap_local_apic.id, esr_val
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for trampoline ready with timeout
|
||||
let mut trampoline_ready = false;
|
||||
for _ in 0..AP_SPIN_LIMIT {
|
||||
if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
|
||||
trampoline_ready = true;
|
||||
break;
|
||||
}
|
||||
hint::spin_loop();
|
||||
}
|
||||
if !trampoline_ready {
|
||||
println!("KERNEL AP: CPU {} trampoline timeout, skipping", ap_local_apic.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut kernel_ready = false;
|
||||
for _ in 0..AP_SPIN_LIMIT {
|
||||
if AP_READY.load(Ordering::SeqCst) {
|
||||
kernel_ready = true;
|
||||
break;
|
||||
}
|
||||
hint::spin_loop();
|
||||
}
|
||||
if !kernel_ready {
|
||||
println!("KERNEL AP: CPU {} AP_READY timeout, skipping", ap_local_apic.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record APIC→CPU mapping for NUMA topology.
|
||||
unsafe {
|
||||
record_apic_mapping(u32::from(ap_local_apic.id), cpu_id);
|
||||
}
|
||||
// Set NUMA node from SRAT data.
|
||||
if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
|
||||
if let Some(node) = crate::acpi::srat::numa_node_for_apic(u32::from(ap_local_apic.id)) {
|
||||
percpu.numa_node.set(node);
|
||||
}
|
||||
}
|
||||
|
||||
RmmA::invalidate_all();
|
||||
} else {
|
||||
debug!("KERNEL AP: LAPIC CPU {} disabled in MADT, skipping", u32::from(ap_local_apic.id));
|
||||
}
|
||||
} else if let MadtEntry::LocalX2Apic(ap_x2apic) = madt_entry {
|
||||
let apic_id = ap_x2apic.x2apic_id;
|
||||
let flags = ap_x2apic.flags;
|
||||
|
||||
if apic_id == me.get() {
|
||||
debug!(" This is my local x2APIC");
|
||||
} else if flags & 1 == 1 {
|
||||
let alloc = match allocate_p2frame(4) {
|
||||
Some(frame) => frame,
|
||||
None => {
|
||||
println!("KERNEL AP: CPU {} no memory for stack, skipping", apic_id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let stack_start = RmmA::phys_to_virt(alloc.base()).data();
|
||||
let stack_end = stack_start + (PAGE_SIZE << 4);
|
||||
|
||||
// Atomically allocate a CPU ID — fetch_add is SeqCst so that
|
||||
// all later stores (PercpuBlock, NUMA node) are ordered after.
|
||||
let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
|
||||
if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
|
||||
println!(
|
||||
"KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
|
||||
apic_id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
|
||||
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
|
||||
|
||||
let args = KernelArgsAp {
|
||||
stack_end: stack_end as *mut u8,
|
||||
cpu_id,
|
||||
pcr_ptr,
|
||||
idt_ptr,
|
||||
};
|
||||
|
||||
let ap_ready = (TRAMPOLINE + 8) as *mut u64;
|
||||
let ap_args_ptr = unsafe { ap_ready.add(1) };
|
||||
let ap_page_table = unsafe { ap_ready.add(2) };
|
||||
let ap_code = unsafe { ap_ready.add(3) };
|
||||
|
||||
unsafe {
|
||||
ap_ready.write(0);
|
||||
ap_args_ptr.write(&args as *const _ as u64);
|
||||
ap_page_table.write(page_table_physaddr as u64);
|
||||
#[expect(clippy::fn_to_numeric_cast)]
|
||||
ap_code.write(kstart_ap as u64);
|
||||
// Ensure all trampoline writes are visible to the AP.
|
||||
core::sync::atomic::fence(Ordering::SeqCst);
|
||||
}
|
||||
AP_READY.store(false, Ordering::SeqCst);
|
||||
|
||||
// Clear APIC Error Status Register before starting AP.
|
||||
unsafe { local_apic.esr(); }
|
||||
|
||||
// Send INIT IPI (Assert)
|
||||
{
|
||||
let mut icr = 0x4500u64;
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(apic_id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(apic_id as u8) << 56;
|
||||
}
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Intel SDM Vol 3A §8.4.4: wait 10ms after INIT
|
||||
early_udelay(10_000);
|
||||
|
||||
// Send START IPI #1
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x0600u64 | ap_segment as u64;
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(apic_id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(apic_id as u8) << 56;
|
||||
}
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Intel SDM: wait 200µs between SIPIs
|
||||
early_udelay(200);
|
||||
|
||||
// Send START IPI #2 (recommended for compatibility)
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x0600u64 | ap_segment as u64;
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(apic_id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(apic_id as u8) << 56;
|
||||
}
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Wait briefly for SIPI acceptance
|
||||
early_udelay(200);
|
||||
|
||||
// Check ESR for delivery errors.
|
||||
let esr_val = unsafe { local_apic.esr() };
|
||||
if esr_val != 0 {
|
||||
println!(
|
||||
"KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
|
||||
apic_id, esr_val
|
||||
);
|
||||
}
|
||||
|
||||
let mut trampoline_ready = false;
|
||||
for _ in 0..AP_SPIN_LIMIT {
|
||||
if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
|
||||
trampoline_ready = true;
|
||||
break;
|
||||
}
|
||||
hint::spin_loop();
|
||||
}
|
||||
if !trampoline_ready {
|
||||
println!("KERNEL AP: CPU {} trampoline timeout, skipping", apic_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut kernel_ready = false;
|
||||
for _ in 0..AP_SPIN_LIMIT {
|
||||
if AP_READY.load(Ordering::SeqCst) {
|
||||
kernel_ready = true;
|
||||
break;
|
||||
}
|
||||
hint::spin_loop();
|
||||
}
|
||||
if !kernel_ready {
|
||||
println!("KERNEL AP: CPU {} AP_READY timeout, skipping", apic_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record APIC→CPU mapping for NUMA topology.
|
||||
unsafe {
|
||||
record_apic_mapping(apic_id, cpu_id);
|
||||
}
|
||||
// Set NUMA node from SRAT data.
|
||||
if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
|
||||
if let Some(node) = crate::acpi::srat::numa_node_for_apic(apic_id) {
|
||||
percpu.numa_node.set(node);
|
||||
}
|
||||
}
|
||||
|
||||
RmmA::invalidate_all();
|
||||
} else {
|
||||
debug!("KERNEL AP: x2APIC CPU {} disabled in MADT (flags={:#x}), skipping", apic_id, flags);
|
||||
}
|
||||
} else if let MadtEntry::LocalApicNmi(nmi) = madt_entry {
|
||||
let target_apic = nmi.processor;
|
||||
if target_apic == 0xFF || target_apic == local_apic.id().get() as u8 {
|
||||
unsafe { local_apic.set_lvt_nmi(nmi.nmi_pin, nmi.flags) };
|
||||
}
|
||||
} else if let MadtEntry::LocalX2ApicNmi(nmi) = madt_entry {
|
||||
let current_uid = current_x2apic_processor_uid(&madt, me.get());
|
||||
if nmi.processor_uid == u32::MAX || current_uid == Some(nmi.processor_uid) {
|
||||
unsafe { local_apic.set_lvt_nmi(nmi.nmi_pin, nmi.flags) };
|
||||
}
|
||||
} else if let MadtEntry::LapicAddressOverride(override_entry) = madt_entry {
|
||||
apply_lapic_address_override(local_apic, override_entry.local_apic_address);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize NUMA topology from APIC→CPU mappings and SRAT.
|
||||
{
|
||||
let mappings = unsafe { &APIC_MAPPINGS[..APIC_MAPPING_COUNT] };
|
||||
let mappings_ref: Vec<(u32, LogicalCpuId)> = mappings
|
||||
.iter()
|
||||
.map(|m| (m.apic_id, m.cpu_id))
|
||||
.collect();
|
||||
crate::numa::init_from_srat(&mappings_ref);
|
||||
}
|
||||
// Set BSP's NUMA node from SRAT.
|
||||
if let Some(node) = crate::acpi::srat::numa_node_for_apic(me.get()) {
|
||||
crate::percpu::PercpuBlock::current().numa_node.set(node);
|
||||
}
|
||||
|
||||
// Log final CPU count vs maximum
|
||||
let cpu_count = crate::CPU_COUNT.load(Ordering::SeqCst);
|
||||
info!(
|
||||
"SMP: {} CPUs online (max {})",
|
||||
cpu_count, crate::cpu_set::MAX_CPU_COUNT
|
||||
);
|
||||
if cpu_count > crate::cpu_set::MAX_CPU_COUNT * 80 / 100 {
|
||||
warn!(
|
||||
"SMP: CPU count approaching MAX_CPU_COUNT limit ({}/{})",
|
||||
cpu_count, crate::cpu_set::MAX_CPU_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
// Unmap trampoline
|
||||
if let Some((_frame, _, flush)) = unsafe {
|
||||
KernelMapper::lock_rw()
|
||||
.unmap_phys(trampoline_page.start_address())
|
||||
} {
|
||||
flush.flush();
|
||||
} else {
|
||||
println!("KERNEL AP: failed to unmap trampoline page (non-fatal)");
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
use core::{
|
||||
hint,
|
||||
sync::atomic::{AtomicU8, Ordering},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
arch::{
|
||||
device::local_apic::the_local_apic,
|
||||
start::{kstart_ap, KernelArgsAp},
|
||||
},
|
||||
cpu_set::LogicalCpuId,
|
||||
memory::{
|
||||
allocate_p2frame, Frame, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch,
|
||||
VirtualAddress, PAGE_SIZE,
|
||||
},
|
||||
startup::AP_READY,
|
||||
};
|
||||
|
||||
use super::{Madt, MadtEntry};
|
||||
|
||||
const TRAMPOLINE: usize = 0x8000;
|
||||
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
|
||||
|
||||
pub(super) fn init(madt: Madt) {
|
||||
let local_apic = unsafe { the_local_apic() };
|
||||
let me = local_apic.id();
|
||||
|
||||
if local_apic.x2 {
|
||||
debug!(" X2APIC {}", me.get());
|
||||
} else {
|
||||
debug!(" XAPIC {}: {:>08X}", me.get(), local_apic.address);
|
||||
}
|
||||
|
||||
if cfg!(not(feature = "multi_core")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Map trampoline
|
||||
let trampoline_frame = Frame::containing(PhysicalAddress::new(TRAMPOLINE));
|
||||
let trampoline_page = Page::containing_address(VirtualAddress::new(TRAMPOLINE));
|
||||
let (result, page_table_physaddr) = unsafe {
|
||||
//TODO: do not have writable and executable!
|
||||
let mut mapper = KernelMapper::lock_rw();
|
||||
|
||||
let result = mapper
|
||||
.map_phys(
|
||||
trampoline_page.start_address(),
|
||||
trampoline_frame.base(),
|
||||
PageFlags::new().execute(true).write(true),
|
||||
)
|
||||
.expect("failed to map trampoline");
|
||||
|
||||
(result, mapper.table().phys().data())
|
||||
};
|
||||
result.flush();
|
||||
|
||||
// Write trampoline, make sure TRAMPOLINE page is free for use
|
||||
for (i, val) in TRAMPOLINE_DATA.iter().enumerate() {
|
||||
unsafe {
|
||||
(*((TRAMPOLINE as *mut u8).add(i) as *const AtomicU8)).store(*val, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let preliminary_cpu_count = madt.iter().filter(|e| matches!(e, MadtEntry::LocalApic(entry) if u32::from(entry.id) == me.get() || entry.flags & 1 == 1)).count();
|
||||
crate::profiling::allocate(preliminary_cpu_count as u32);
|
||||
}
|
||||
|
||||
for madt_entry in madt.iter() {
|
||||
debug!(" {:x?}", madt_entry);
|
||||
if let MadtEntry::LocalApic(ap_local_apic) = madt_entry {
|
||||
if u32::from(ap_local_apic.id) == me.get() {
|
||||
debug!(" This is my local APIC");
|
||||
} else if ap_local_apic.flags & 1 == 1 {
|
||||
let cpu_id = LogicalCpuId::next();
|
||||
|
||||
// Allocate a stack
|
||||
let stack_start = RmmA::phys_to_virt(
|
||||
allocate_p2frame(4)
|
||||
.expect("no more frames in acpi stack_start")
|
||||
.base(),
|
||||
)
|
||||
.data();
|
||||
let stack_end = stack_start + (PAGE_SIZE << 4);
|
||||
|
||||
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
|
||||
|
||||
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
|
||||
|
||||
let args = KernelArgsAp {
|
||||
stack_end: stack_end as *mut u8,
|
||||
cpu_id,
|
||||
pcr_ptr,
|
||||
idt_ptr,
|
||||
};
|
||||
|
||||
let ap_ready = (TRAMPOLINE + 8) as *mut u64;
|
||||
let ap_args_ptr = unsafe { ap_ready.add(1) };
|
||||
let ap_page_table = unsafe { ap_ready.add(2) };
|
||||
let ap_code = unsafe { ap_ready.add(3) };
|
||||
|
||||
// Set the ap_ready to 0, volatile
|
||||
unsafe {
|
||||
ap_ready.write(0);
|
||||
ap_args_ptr.write(&args as *const _ as u64);
|
||||
ap_page_table.write(page_table_physaddr as u64);
|
||||
#[expect(clippy::fn_to_numeric_cast)]
|
||||
ap_code.write(kstart_ap as u64);
|
||||
|
||||
// TODO: Is this necessary (this fence)?
|
||||
core::arch::asm!("");
|
||||
};
|
||||
AP_READY.store(false, Ordering::SeqCst);
|
||||
|
||||
// Send INIT IPI
|
||||
{
|
||||
let mut icr = 0x4500;
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(ap_local_apic.id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(ap_local_apic.id) << 56;
|
||||
}
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Send START IPI
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x4600 | ap_segment as u64;
|
||||
|
||||
if local_apic.x2 {
|
||||
icr |= u64::from(ap_local_apic.id) << 32;
|
||||
} else {
|
||||
icr |= u64::from(ap_local_apic.id) << 56;
|
||||
}
|
||||
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
// Wait for trampoline ready
|
||||
while unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } == 0 {
|
||||
hint::spin_loop();
|
||||
}
|
||||
while !AP_READY.load(Ordering::SeqCst) {
|
||||
hint::spin_loop();
|
||||
}
|
||||
|
||||
RmmA::invalidate_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unmap trampoline
|
||||
let (_frame, _, flush) = unsafe {
|
||||
KernelMapper::lock_rw()
|
||||
.unmap_phys(trampoline_page.start_address())
|
||||
.expect("failed to unmap trampoline page")
|
||||
};
|
||||
flush.flush();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
--- src/acpi/madt/arch/x86.rs
|
||||
+++ src/acpi/madt/arch/x86.rs
|
||||
@@ -446,11 +446,7 @@
|
||||
// Send INIT IPI (Assert)
|
||||
{
|
||||
let mut icr = 0x4500u64;
|
||||
- if local_apic.x2 {
|
||||
- icr |= u64::from(apic_id) << 32;
|
||||
- } else {
|
||||
- icr |= u64::from(apic_id as u8) << 56;
|
||||
- }
|
||||
+ icr |= u64::from(apic_id) << 32;
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
@@ -460,11 +456,7 @@
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x0600u64 | ap_segment as u64;
|
||||
- if local_apic.x2 {
|
||||
- icr |= u64::from(apic_id) << 32;
|
||||
- } else {
|
||||
- icr |= u64::from(apic_id as u8) << 56;
|
||||
- }
|
||||
+ icr |= u64::from(apic_id) << 32;
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
@@ -476,11 +468,7 @@
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x0600u64 | ap_segment as u64;
|
||||
- if local_apic.x2 {
|
||||
- icr |= u64::from(apic_id) << 32;
|
||||
- } else {
|
||||
- icr |= u64::from(apic_id as u8) << 56;
|
||||
- }
|
||||
+ icr |= u64::from(apic_id) << 32;
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
use core::{
|
||||
cell::SyncUnsafeCell,
|
||||
ptr::{read_volatile, write_volatile},
|
||||
};
|
||||
use x86::msr::*;
|
||||
|
||||
use crate::{
|
||||
arch::{cpuid::cpuid, ipi::IpiKind},
|
||||
memory::{map_device_memory, PhysicalAddress},
|
||||
percpu::PercpuBlock,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ApicId(u32);
|
||||
|
||||
impl ApicId {
|
||||
pub fn new(inner: u32) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
static LOCAL_APIC: SyncUnsafeCell<LocalApic> = SyncUnsafeCell::new(LocalApic {
|
||||
address: 0,
|
||||
x2: false,
|
||||
});
|
||||
pub unsafe fn the_local_apic() -> &'static mut LocalApic {
|
||||
unsafe { &mut *LOCAL_APIC.get() }
|
||||
}
|
||||
|
||||
pub unsafe fn init() {
|
||||
unsafe {
|
||||
the_local_apic().init();
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn init_ap() {
|
||||
unsafe {
|
||||
the_local_apic().init_ap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Local APIC
|
||||
pub struct LocalApic {
|
||||
pub address: usize,
|
||||
pub x2: bool,
|
||||
}
|
||||
|
||||
impl LocalApic {
|
||||
unsafe fn init(&mut self) {
|
||||
unsafe {
|
||||
let physaddr = PhysicalAddress::new(rdmsr(IA32_APIC_BASE) as usize & 0xFFFF_0000);
|
||||
|
||||
self.x2 = cpuid()
|
||||
.get_feature_info()
|
||||
.is_some_and(|feature_info| feature_info.has_x2apic());
|
||||
|
||||
if !self.x2 {
|
||||
info!("Detected xAPIC at {:#x}", physaddr.data());
|
||||
self.address = map_device_memory(physaddr, 4096).data();
|
||||
} else {
|
||||
info!("Detected x2APIC");
|
||||
}
|
||||
|
||||
self.init_ap();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init_ap(&mut self) {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_APIC_BASE, rdmsr(IA32_APIC_BASE) | (1 << 10));
|
||||
wrmsr(IA32_X2APIC_SIVR, 0x100);
|
||||
} else {
|
||||
self.write(0xF0, 0x100);
|
||||
}
|
||||
self.setup_error_int();
|
||||
//self.setup_timer();
|
||||
|
||||
PercpuBlock::current()
|
||||
.misc_arch_info
|
||||
.apic_id_opt
|
||||
.set(Some(self.id()));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read(&self, reg: u32) -> u32 {
|
||||
debug_assert!(!self.x2);
|
||||
unsafe { read_volatile((self.address + reg as usize) as *const u32) }
|
||||
}
|
||||
|
||||
unsafe fn write(&mut self, reg: u32, value: u32) {
|
||||
debug_assert!(!self.x2);
|
||||
unsafe {
|
||||
write_volatile((self.address + reg as usize) as *mut u32, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ApicId {
|
||||
ApicId::new(if self.x2 {
|
||||
unsafe { rdmsr(IA32_X2APIC_APICID) as u32 }
|
||||
} else {
|
||||
unsafe { self.read(0x20) >> 24 }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u32 {
|
||||
if self.x2 {
|
||||
unsafe { rdmsr(IA32_X2APIC_VERSION) as u32 }
|
||||
} else {
|
||||
unsafe { self.read(0x30) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icr(&self) -> u64 {
|
||||
if self.x2 {
|
||||
unsafe { rdmsr(IA32_X2APIC_ICR) }
|
||||
} else {
|
||||
unsafe { ((self.read(0x310) as u64) << 32) | self.read(0x300) as u64 }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_icr(&mut self, value: u64) {
|
||||
if self.x2 {
|
||||
unsafe {
|
||||
const PENDING: u32 = 1 << 12;
|
||||
while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
wrmsr(IA32_X2APIC_ICR, value);
|
||||
while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
const PENDING: u32 = 1 << 12;
|
||||
while self.read(0x300) & PENDING == PENDING {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
self.write(0x310, (value >> 32) as u32);
|
||||
self.write(0x300, value as u32);
|
||||
while self.read(0x300) & PENDING == PENDING {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ipi(&mut self, apic_id: ApicId, kind: IpiKind) {
|
||||
let shift = if self.x2 { 32 } else { 56 };
|
||||
self.set_icr((u64::from(apic_id.get()) << shift) | 0x40 | kind as u64);
|
||||
}
|
||||
pub fn ipi_nmi(&mut self, apic_id: ApicId) {
|
||||
let shift = if self.x2 { 32 } else { 56 };
|
||||
self.set_icr((u64::from(apic_id.get()) << shift) | (1 << 14) | (0b100 << 8));
|
||||
}
|
||||
|
||||
pub unsafe fn eoi(&mut self) {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_X2APIC_EOI, 0);
|
||||
} else {
|
||||
self.write(0xB0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Reads the Error Status Register.
|
||||
pub unsafe fn esr(&mut self) -> u32 {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
// update the ESR to the current state of the local apic.
|
||||
wrmsr(IA32_X2APIC_ESR, 0);
|
||||
// read the updated value
|
||||
rdmsr(IA32_X2APIC_ESR) as u32
|
||||
} else {
|
||||
self.write(0x280, 0);
|
||||
self.read(0x280)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn lvt_timer(&mut self) -> u32 {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
rdmsr(IA32_X2APIC_LVT_TIMER) as u32
|
||||
} else {
|
||||
self.read(0x320)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn set_lvt_timer(&mut self, value: u32) {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_X2APIC_LVT_TIMER, u64::from(value));
|
||||
} else {
|
||||
self.write(0x320, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn init_count(&mut self) -> u32 {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
rdmsr(IA32_X2APIC_INIT_COUNT) as u32
|
||||
} else {
|
||||
self.read(0x380)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn set_init_count(&mut self, initial_count: u32) {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_X2APIC_INIT_COUNT, u64::from(initial_count));
|
||||
} else {
|
||||
self.write(0x380, initial_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn cur_count(&mut self) -> u32 {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
rdmsr(IA32_X2APIC_CUR_COUNT) as u32
|
||||
} else {
|
||||
self.read(0x390)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn div_conf(&mut self) -> u32 {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
rdmsr(IA32_X2APIC_DIV_CONF) as u32
|
||||
} else {
|
||||
self.read(0x3E0)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn set_div_conf(&mut self, div_conf: u32) {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_X2APIC_DIV_CONF, u64::from(div_conf));
|
||||
} else {
|
||||
self.write(0x3E0, div_conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn lvt_error(&mut self) -> u32 {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
rdmsr(IA32_X2APIC_LVT_ERROR) as u32
|
||||
} else {
|
||||
self.read(0x370)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub unsafe fn set_lvt_error(&mut self, lvt_error: u32) {
|
||||
unsafe {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_X2APIC_LVT_ERROR, u64::from(lvt_error));
|
||||
} else {
|
||||
self.write(0x370, lvt_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set_lvt_nmi(&mut self, pin: u8, flags: u16) {
|
||||
let polarity = match flags & 0b11 {
|
||||
0b11 => 1 << 13,
|
||||
_ => 0,
|
||||
};
|
||||
let trigger_mode = match (flags >> 2) & 0b11 {
|
||||
0b11 => 1 << 15,
|
||||
_ => 0,
|
||||
};
|
||||
let lvt_value = (0b100 << 8) | polarity | trigger_mode;
|
||||
|
||||
unsafe {
|
||||
match pin {
|
||||
0 => {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_X2APIC_LVT_LINT0, u64::from(lvt_value));
|
||||
} else {
|
||||
self.write(0x350, lvt_value);
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
if self.x2 {
|
||||
wrmsr(IA32_X2APIC_LVT_LINT1, u64::from(lvt_value));
|
||||
} else {
|
||||
self.write(0x360, lvt_value);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn setup_error_int(&mut self) {
|
||||
unsafe {
|
||||
let vector = 49u32;
|
||||
self.set_lvt_error(vector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum LvtTimerMode {
|
||||
OneShot = 0b00,
|
||||
Periodic = 0b01,
|
||||
TscDeadline = 0b10,
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
--- src/arch/x86_shared/device/local_apic.rs
|
||||
+++ src/arch/x86_shared/device/local_apic.rs
|
||||
@@ -61,9 +61,9 @@
|
||||
|
||||
if !self.x2 {
|
||||
- info!("Detected xAPIC at {:#x}", physaddr.data());
|
||||
+ debug!("Detected xAPIC at {:#x}", physaddr.data());
|
||||
self.address = map_device_memory(physaddr, 4096).data();
|
||||
} else {
|
||||
- info!("Detected x2APIC");
|
||||
+ debug!("Detected x2APIC");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user