x86_64: fix FADT field offsets and harden GS base around early boot
- fadt.rs was using sdt.data_address() (after the 36-byte SDT header) with absolute ACPI table offsets, causing PM1a_CNT/STS and FACS addresses to be read 36 bytes past their real location. Under KVM this produced a garbage x_firmware_ctrl value (0x604) and a page fault inside acpi::init. Use the SDT start address instead. - Load GS with the kernel data selector in install_pcr instead of a NULL selector, avoiding undefined GSBASE behavior on KVM. - Add GSBASE diagnostics through start.rs, acpi::init, and tsc::init to catch future GS base corruption early. Author: vasilito <adminpupkin@gmail.com>
This commit is contained in:
+4
-1
@@ -77,7 +77,10 @@ pub fn init(sdt: &Sdt) {
|
||||
// offsets (per the ACPI spec); reading them as u32/u64 is
|
||||
// safe because all of them are at 4-byte or 8-byte aligned
|
||||
// offsets on x86_64.
|
||||
let data = sdt.data_address() as *const u8;
|
||||
// Use the SDT start address, not data_address() (which points past the
|
||||
// 36-byte SDT header). The offsets below are absolute offsets from the
|
||||
// start of the FADT table per ACPI 6.5 §5.2.9.
|
||||
let data = sdt as *const _ as *const u8;
|
||||
unsafe {
|
||||
// PM1a_CNT is at offset 56 in the FADT (ACPI 6.5 §5.2.9
|
||||
// Table 5.6). 32-bit General-Purpose Event Register Block 0
|
||||
|
||||
@@ -150,25 +150,31 @@ pub unsafe fn init(already_supplied_rsdp: Option<NonNull<u8>>) {
|
||||
|
||||
// TODO: Enumerate processors in userspace, and then provide an ACPI-independent interface
|
||||
// to initialize enumerated processors to userspace?
|
||||
info!("GSBASE before Madt::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
Madt::init();
|
||||
info!("GSBASE after Madt::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
//TODO: support this on any arch
|
||||
// SPCR must be initialized after MADT for interrupt controllers
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
spcr::Spcr::init();
|
||||
// TODO: Let userspace setup HPET, and then provide an interface to specify which timer to
|
||||
// use?
|
||||
info!("GSBASE before Hpet::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
Hpet::init();
|
||||
info!("GSBASE after Hpet::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
gtdt::Gtdt::init();
|
||||
// Phase II: parse the FADT to extract the PM1a_CNT
|
||||
// and PM1a_STS port addresses used by the S3 entry
|
||||
// path. Hardware-agnostic — works on any platform
|
||||
// with a working FADT.
|
||||
info!("GSBASE before fadt::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
if let Some(fadt_sdts) = find_sdt("FACP").first() {
|
||||
fadt::init(fadt_sdts);
|
||||
} else {
|
||||
warn!("ACPI: no FADT (FACP) found, S3 entry path disabled");
|
||||
}
|
||||
info!("GSBASE after fadt::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
// Phase II.X.W: parse the FACS to extract the
|
||||
// xfirmware_waking_vector. This is the address the
|
||||
// platform firmware jumps to on S3 wake. The kernel's
|
||||
@@ -180,7 +186,9 @@ pub unsafe fn init(already_supplied_rsdp: Option<NonNull<u8>>) {
|
||||
// field (64-bit) or firmware_ctrl field (32-bit).
|
||||
// The FADT parser caches the FACS address. We use
|
||||
// the FADT's x_firmware_ctrl to find the FACS SDT.
|
||||
info!("GSBASE before facs::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
let facs_addr = fadt::x_firmware_ctrl();
|
||||
info!("FACS x_firmware_ctrl = {:#x}", facs_addr);
|
||||
if facs_addr != 0 {
|
||||
// SAFETY: The FACS address is a physical
|
||||
// address stored in the FADT. The boot-time page
|
||||
|
||||
@@ -134,6 +134,13 @@ pub unsafe fn init() -> bool {
|
||||
.supp_feats
|
||||
.contains(KvmFeatureBits::CLOCKSOURCE2 | KvmFeatureBits::CLOCKSOURCE_STABLE)
|
||||
{
|
||||
let gsbase = x86::msr::rdmsr(x86::msr::IA32_GS_BASE);
|
||||
debug!("tsc::init: IA32_GS_BASE = {:#x}", gsbase);
|
||||
if gsbase == 0 {
|
||||
debug!("tsc::init: GSBASE is zero, skipping KVM TSC");
|
||||
return false;
|
||||
}
|
||||
|
||||
let frame = allocate_frame().expect("failed to allocate timer page");
|
||||
x86::msr::wrmsr(MSR_KVM_SYSTEM_TIME_NEW, (frame.base().data() as u64) | 1);
|
||||
let ptr = crate::memory::RmmA::phys_to_virt(frame.base()).data()
|
||||
|
||||
@@ -332,9 +332,10 @@ pub unsafe fn install_pcr(pcr_ptr: *mut ProcessorControlRegion) {
|
||||
segmentation::load_es(SegmentSelector::from_raw(0));
|
||||
segmentation::load_fs(SegmentSelector::from_raw(0));
|
||||
|
||||
// What happens when GS is loaded with a NULL selector, is undefined on Intel CPUs. However,
|
||||
// GSBASE is set later.
|
||||
segmentation::load_gs(SegmentSelector::from_raw(0));
|
||||
// Loading GS with a NULL selector leaves the resulting GSBASE undefined on some CPUs and
|
||||
// hypervisors (e.g. KVM). Use the kernel data selector so the segment is present, then set
|
||||
// the actual base via IA32_GS_BASE.
|
||||
segmentation::load_gs(SegmentSelector::new(GDT_KERNEL_DATA as u16, Ring::Ring0));
|
||||
|
||||
// Ensure that GSBASE always points to the PCR in kernel space.
|
||||
x86::msr::wrmsr(x86::msr::IA32_GS_BASE, pcr as *mut _ as usize as u64);
|
||||
|
||||
@@ -96,6 +96,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
|
||||
|
||||
// Set up GDT
|
||||
gdt::init_bsp(stack_end);
|
||||
info!("GSBASE after init_bsp: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
|
||||
// Set up IDT
|
||||
idt::init_bsp();
|
||||
@@ -108,12 +109,15 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
|
||||
|
||||
// Initialize paging
|
||||
paging::init();
|
||||
info!("GSBASE after paging::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
crate::arch::alternative::early_init(true);
|
||||
info!("GSBASE after alternative::early_init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
|
||||
// Set up syscall instruction
|
||||
interrupt::syscall::init();
|
||||
info!("GSBASE after syscall::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
|
||||
// Setup kernel heap
|
||||
allocator::init();
|
||||
@@ -124,17 +128,22 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
|
||||
// Initialize miscellaneous processor features
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
crate::arch::misc::init(LogicalCpuId::BSP);
|
||||
info!("GSBASE after misc::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
|
||||
// Initialize devices
|
||||
device::init();
|
||||
info!("GSBASE after device::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
|
||||
// Read ACPI tables, starts APs
|
||||
if cfg!(feature = "acpi") {
|
||||
info!("GSBASE before acpi::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
crate::acpi::init(args.acpi_rsdp());
|
||||
info!("GSBASE after acpi::init: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
device::init_after_acpi();
|
||||
}
|
||||
crate::profiling::init();
|
||||
|
||||
info!("GSBASE before init_noncore: {:#x}", x86::msr::rdmsr(x86::msr::IA32_GS_BASE));
|
||||
// Initialize all of the non-core devices not otherwise needed to complete initialization
|
||||
device::init_noncore();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user