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:
2026-07-06 02:00:53 +03:00
parent 1b22d324ab
commit 51f6a7712e
5 changed files with 32 additions and 4 deletions
+4 -1
View File
@@ -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
+8
View File
@@ -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
+7
View File
@@ -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()
+4 -3
View File
@@ -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);
+9
View File
@@ -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();