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:
+8
-11
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user