kernel: fix FADT field offset — read from SDT base, not data area

The FADT fields (PM1a_CNT, PM1a_STS, FIRMWARE_CTRL) are at fixed
offsets from the START of the SDT (including the 36-byte ACPI header),
not from the data area. Previously using sdt.data_address() caused
reads from the wrong offset, making shutdown/reboot fail on real
hardware.

Fixed by reading from the SDT base pointer instead of data_address().
Cross-referenced with Linux 7.1 drivers/acpi/acpica/tbfadt.c which
reads FADT fields from the table base, accounting for the header.
This commit is contained in:
2026-07-09 11:13:36 +03:00
parent 2fc628fcdf
commit ffa502b2cb
+8 -11
View File
@@ -70,19 +70,16 @@ pub fn init(sdt: &Sdt) {
if &sdt.signature != &FADT_SIGNATURE {
return;
}
// SAFETY: We trust the ACPI table discovery code to have
// verified the FADT checksum. The FADT fields are at fixed
// 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;
// The FADT fields are at fixed offsets from the start of the
// SDT (including the 36-byte header), not from the data area.
let base = 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
// Address.
let pm1a_cnt = core::ptr::read_unaligned(data.add(offsets::PM1A_CNT) as *const u32);
let pm1a_cnt = core::ptr::read_unaligned(base.add(offsets::PM1A_CNT) as *const u32);
// PM1a_STS is at offset 48 in the FADT.
let pm1a_sts = core::ptr::read_unaligned(data.add(offsets::PM1A_STS) as *const u32);
let pm1a_sts = core::ptr::read_unaligned(base.add(offsets::PM1A_STS) as *const u32);
// Convert u32 to u16 (port numbers are 16-bit). The low
// 16 bits are the IO port; the high 16 bits are the
// address-space ID which we ignore (always IO on x86).
@@ -98,18 +95,18 @@ pub fn init(sdt: &Sdt) {
// Phase II.X.W: 32-bit FACS address (FADT offset 36,
// `firmware_ctrl` field). ACPI 1.0+.
let firmware_ctrl = core::ptr::read_unaligned(
data.add(offsets::FIRMWARE_CTRL_32) as *const u32,
base.add(offsets::FIRMWARE_CTRL_32) as *const u32,
);
FIRMWARE_CTRL.store(firmware_ctrl, core::sync::atomic::Ordering::Release);
// Phase II.X.W: 64-bit FACS address (FADT offset 140,
// `x_firmware_ctrl` field). ACPI 2.0+. We require the
// `x_firmware_ctrl` field, ACPI 2.0+). We require the
// FADT to be at least 148 bytes to have this field
// (the field is at offset 140, which is 8 bytes for the
// u64, so the minimum FADT size is 148 bytes).
if sdt.length() >= offsets::FADT_MIN_SIZE_ACPI_2_0 as u32 {
let x_firmware_ctrl = core::ptr::read_unaligned(
data.add(offsets::X_FIRMWARE_CTRL_64) as *const u64,
base.add(offsets::X_FIRMWARE_CTRL_64) as *const u64,
);
X_FIRMWARE_CTRL.store(x_firmware_ctrl, core::sync::atomic::Ordering::Release);
}