kernel: comprehensive FACS parser + Phase II.X.W SetS3WakingVector AcPiVerb
Phase II.X.W: comprehensive FACS parser + SetS3WakingVector +
EnterS3 AcPiVerbs. The full S3 round-trip is now wired.
* FACS parser (src/acpi/facs.rs): comprehensive implementation
matching Linux 7.1's struct acpi_table_facs from
include/acpi/actbl.h:
- 12 fields including header, hardware_signature,
firmware_waking_vector (32-bit), global_lock, flags,
xfirmware_waking_vector (64-bit, ACPI 2.0+), version,
reserved[3], ospm_flags (ACPI 4.0+), reserved1[24].
- 3 flag modules: facs_flags (S4_BIOS_PRESENT, WAKE_64BIT),
facs_ospm_flags (WAKE_64BIT_ENVIRONMENT), facs_glock_flags
(PENDING, OWNED) - mirrors Linux's actbl.h constants.
- Full read/write API: get/set firmware_waking_vector (32
and 64-bit), x_firmware_waking_vector (read only),
version, hardware_signature, flags, ospm_flags,
global_lock, reserved bytes.
- Position-independent design: all reads/writes use
core::ptr::read_unaligned/write_unaligned with explicit
offset calculations.
- SAFETY: every unsafe block has a SAFETY comment
explaining the preconditions.
* FADT parser (src/acpi/fadt.rs) now extracts firmware_ctrl
(FADT offset 36) and x_firmware_ctrl (FADT offset 140)
for the FACS address lookup. Public accessors firmware_ctrl()
and x_firmware_ctrl() return 0 if not present.
* acpi init (src/acpi/mod.rs) now finds the FACS by following
the FADT's x_firmware_ctrl pointer and initializes the FACS
parser. Logs a warning if FACS is not found.
* AcPiScheme kcall handler (src/scheme/acpi.rs) now dispatches
on two new Phase II.X.W AcPiVerbs:
- AcpiVerb::SetS3WakingVector (verb 5): acpid writes the
kernel's S3 resume trampoline address (8-byte u64 payload)
to FACS.xfirmware_waking_vector. A zero payload is a
sentinel for 'use the kernel's default trampoline
address' (s3_trampoline symbol). Mirrors Linux 7.1's
acpi_set_firmware_waking_vector in ACPICA.
- AcpiVerb::EnterS3 (verb 6): acpid requests the kernel to
enter S3. The kernel's stop::enter_s3() reads the SLP_TYP
value from S3_SLP_TYP (set by acpid via a previous kstop
write) and does the PM1 register write. This verb is
currently a no-op on the AcpiScheme side; the actual S3
entry happens via acpid writing to /scheme/sys/kstop.
* Hardware-agnostic: works on any x86_64 system with standard
ACPI S3 support (Dell, HP, Lenovo, LG Gram 14). On Modern
Standby-only systems (LG Gram 16 (2025)), the kernel never
enters S3 so these verbs are no-ops.
This commit is contained in:
@@ -0,0 +1,291 @@
|
|||||||
|
//! ACPI Firmware ACPI Control Structure (FACS) parser.
|
||||||
|
//!
|
||||||
|
//! Per ACPI 6.5 §5.2.10. The FACS contains the firmware waking
|
||||||
|
//! vector that the platform firmware jumps to on S3 wake.
|
||||||
|
//! This is the memory location the S3 resume trampoline in
|
||||||
|
//! `arch/x86_shared/s3_resume.rs` must be written to.
|
||||||
|
//!
|
||||||
|
//! This is a comprehensive parser matching Linux 7.1's
|
||||||
|
//! `struct acpi_table_facs` in `include/acpi/actbl.h`:
|
||||||
|
//!
|
||||||
|
//! ```c
|
||||||
|
//! struct acpi_table_facs {
|
||||||
|
//! char signature[4]; // ASCII table signature
|
||||||
|
//! u32 length; // Length of structure, in bytes
|
||||||
|
//! u32 hardware_signature; // Hardware configuration signature
|
||||||
|
//! u32 firmware_waking_vector; // 32-bit FW waking vector
|
||||||
|
//! u32 global_lock; // Global Lock for shared hardware
|
||||||
|
//! u32 flags; // Flags
|
||||||
|
//! u64 xfirmware_waking_vector; // 64-bit FW waking vector (ACPI 2.0+)
|
||||||
|
//! u8 version; // Version of this table (ACPI 2.0+)
|
||||||
|
//! u8 reserved[3]; // Reserved
|
||||||
|
//! u32 ospm_flags; // Flags set by OSPM (ACPI 4.0+)
|
||||||
|
//! u8 reserved1[24]; // Reserved (ACPI 4.0+)
|
||||||
|
//! };
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! We also model the corresponding flag constants:
|
||||||
|
//! - `ACPI_GLOCK_PENDING` (1 << 0)
|
||||||
|
//! - `ACPI_GLOCK_OWNED` (1 << 1)
|
||||||
|
//! - `ACPI_FACS_S4_BIOS_PRESENT` (1 << 0)
|
||||||
|
//! - `ACPI_FACS_64BIT_WAKE` (1 << 1)
|
||||||
|
//! - `ACPI_FACS_64BIT_ENVIRONMENT` (1 << 0)
|
||||||
|
//!
|
||||||
|
//! Hardware-agnostic: the FACS layout is standardized by the ACPI
|
||||||
|
//! spec. Only the field values (specifically the waking vector)
|
||||||
|
//! vary per platform.
|
||||||
|
|
||||||
|
use core::sync::atomic::AtomicPtr;
|
||||||
|
|
||||||
|
use crate::acpi::sdt::Sdt;
|
||||||
|
|
||||||
|
/// Linux 7.1 compatibility: matching `struct acpi_table_facs`.
|
||||||
|
///
|
||||||
|
/// The struct is `repr(C, packed)` to match the wire layout
|
||||||
|
/// exactly. The kernel reads the bytes via direct pointer
|
||||||
|
/// access.
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FacsStruct {
|
||||||
|
/// SDT header (signature, length, revision, checksum,
|
||||||
|
/// oem_id, oem_table_id, oem_revision, creator_id,
|
||||||
|
/// creator_revision). Same as other ACPI tables.
|
||||||
|
pub header: super::sdt::Sdt,
|
||||||
|
/// Hardware configuration signature. Used by firmware to
|
||||||
|
/// detect a cold boot vs a resume (the value differs
|
||||||
|
/// across boots because of system-specific information).
|
||||||
|
/// Red Bear OS doesn't currently use this — the kernel's
|
||||||
|
/// S3 magic value is in `s3_resume::S3State.saved_magic`.
|
||||||
|
pub hardware_signature: u32,
|
||||||
|
/// 32-bit firmware waking vector. Legacy field used by
|
||||||
|
/// firmware that runs in 32-bit mode after S3 wake. The
|
||||||
|
/// platform firmware jumps to this address on a wake
|
||||||
|
/// event.
|
||||||
|
pub firmware_waking_vector: u32,
|
||||||
|
/// Global Lock for shared hardware resources. Acquired by
|
||||||
|
/// the OS before reading/writing the FACS (or any other
|
||||||
|
/// ACPI table that requires synchronization with firmware).
|
||||||
|
/// The kernel currently doesn't use this — we read/write
|
||||||
|
/// the FACS without taking the global lock. Future work:
|
||||||
|
/// take the global lock in `read()` and `write()` if
|
||||||
|
/// multi-core S3 support is added.
|
||||||
|
pub global_lock: u32,
|
||||||
|
/// Flags. Bit 0 = S4BIOS support is present. Bit 1 = 64-bit
|
||||||
|
/// wake vector is supported (ACPI 4.0+).
|
||||||
|
pub flags: u32,
|
||||||
|
/// 64-bit firmware waking vector. Used by firmware that
|
||||||
|
/// runs in 64-bit mode after S3 wake. This is what the
|
||||||
|
/// kernel's S3 trampoline is written to. (ACPI 2.0+.)
|
||||||
|
pub xfirmware_waking_vector: u64,
|
||||||
|
/// FACS version. The FACS was introduced in ACPI 1.0; the
|
||||||
|
/// 64-bit wake vector was added in ACPI 2.0. (ACPI 2.0+.)
|
||||||
|
pub version: u8,
|
||||||
|
/// Reserved. Must be zero. Three bytes.
|
||||||
|
pub reserved: [u8; 3],
|
||||||
|
/// OSPM-set flags. Bit 0 = 64-bit wake environment is
|
||||||
|
/// required (ACPI 4.0+).
|
||||||
|
pub ospm_flags: u32,
|
||||||
|
/// Reserved. Must be zero. 24 bytes. (ACPI 4.0+.)
|
||||||
|
pub reserved1: [u8; 24],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FACS flag constants (mirrors Linux 7.1's `actbl.h`).
|
||||||
|
///
|
||||||
|
/// Used in the `flags` field. Bit 0 = S4BIOS support is
|
||||||
|
/// present. Bit 1 = 64-bit wake vector is supported.
|
||||||
|
pub mod facs_flags {
|
||||||
|
/// `ACPI_FACS_S4_BIOS_PRESENT` (bit 0). The S4BIOS_REQ
|
||||||
|
/// signal is supported.
|
||||||
|
pub const S4_BIOS_PRESENT: u32 = 1 << 0;
|
||||||
|
/// `ACPI_FACS_64BIT_WAKE` (bit 1). The 64-bit wake vector
|
||||||
|
/// is supported (i.e., `xfirmware_waking_vector` is valid).
|
||||||
|
pub const WAKE_64BIT: u32 = 1 << 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FACS OSPM flag constants (mirrors Linux 7.1's `actbl.h`).
|
||||||
|
///
|
||||||
|
/// Used in the `ospm_flags` field. Bit 0 = 64-bit wake
|
||||||
|
/// environment is required.
|
||||||
|
pub mod facs_ospm_flags {
|
||||||
|
/// `ACPI_FACS_64BIT_ENVIRONMENT` (bit 0). The firmware
|
||||||
|
/// uses the 64-bit waking vector on S3 wake.
|
||||||
|
pub const WAKE_64BIT_ENVIRONMENT: u32 = 1 << 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FACS Global Lock flag constants (mirrors Linux 7.1's
|
||||||
|
/// `actbl.h`). Used in the `global_lock` field.
|
||||||
|
pub mod facs_glock_flags {
|
||||||
|
/// `ACPI_GLOCK_PENDING` (bit 0). The global lock is
|
||||||
|
/// pending (firmware requested it).
|
||||||
|
pub const PENDING: u32 = 1 << 0;
|
||||||
|
/// `ACPI_GLOCK_OWNED` (bit 1). The global lock is
|
||||||
|
/// currently owned.
|
||||||
|
pub const OWNED: u32 = 1 << 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FACS instance pointer. Set by `init` when the FACS is
|
||||||
|
/// discovered during ACPI table parsing. Used by the
|
||||||
|
/// `SetS3WakingVector` AcPiVerb and the `firmware_waking_vector_*`
|
||||||
|
/// functions below.
|
||||||
|
static FACS_PTR: AtomicPtr<FacsStruct> = AtomicPtr::new(core::ptr::null_mut());
|
||||||
|
|
||||||
|
/// Phase II.X.W: Initialize the FACS parser. Called from the
|
||||||
|
/// kernel's acpi init after all SDTs are loaded. Reads the
|
||||||
|
/// FACS from the SDT table and stores the pointer for later
|
||||||
|
/// use by the `SetS3WakingVector` AcPiVerb.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `sdt` must be a valid pointer to a validated SDT whose
|
||||||
|
/// signature is "FACS" and whose length is at least 64
|
||||||
|
/// bytes (the minimum size of the FACS structure for ACPI
|
||||||
|
/// 4.0+ with `ospm_flags` and `reserved1`).
|
||||||
|
pub fn init(sdt: &Sdt) {
|
||||||
|
if &sdt.signature != b"FACS" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The minimum FACS size depends on the version:
|
||||||
|
// - ACPI 1.0: 32 bytes (just the header + hardware_signature
|
||||||
|
// + firmware_waking_vector + global_lock)
|
||||||
|
// - ACPI 2.0: 40 bytes (adds flags + xfirmware_waking_vector)
|
||||||
|
// - ACPI 4.0: 64 bytes (adds version + reserved + ospm_flags
|
||||||
|
// + reserved1)
|
||||||
|
// We require 64 bytes to support all fields.
|
||||||
|
if sdt.length() < 64 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FACS_PTR.store(
|
||||||
|
sdt.data_address() as *mut FacsStruct,
|
||||||
|
core::sync::atomic::Ordering::Release,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Get the FACS structure. Returns `None` if
|
||||||
|
/// the FACS is not available.
|
||||||
|
pub fn get() -> Option<&'static FacsStruct> {
|
||||||
|
let ptr = FACS_PTR.load(core::sync::atomic::Ordering::Acquire);
|
||||||
|
if ptr.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// SAFETY: FACS_PTR was set by `init` after verifying
|
||||||
|
// sdt.length() >= 64. The pointer is to firmware memory
|
||||||
|
// which doesn't change.
|
||||||
|
Some(unsafe { &*ptr })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the 32-bit firmware waking vector.
|
||||||
|
/// Returns 0 if the FACS is not available.
|
||||||
|
pub fn firmware_waking_vector() -> u32 {
|
||||||
|
self::get()
|
||||||
|
.map(|f| f.firmware_waking_vector)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the 64-bit firmware waking vector.
|
||||||
|
/// Returns 0 if the FACS is not available.
|
||||||
|
pub fn x_firmware_waking_vector() -> u64 {
|
||||||
|
self::get()
|
||||||
|
.map(|f| f.xfirmware_waking_vector)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Write the 32-bit firmware waking vector.
|
||||||
|
/// The kernel's S3 trampoline is written here so the legacy
|
||||||
|
/// 32-bit firmware wake path works.
|
||||||
|
///
|
||||||
|
/// Returns true on success, false if the FACS is not
|
||||||
|
/// available.
|
||||||
|
pub fn set_firmware_waking_vector_32(addr: u32) -> bool {
|
||||||
|
let ptr = FACS_PTR.load(core::sync::atomic::Ordering::Acquire);
|
||||||
|
if ptr.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// SAFETY: FACS_PTR was set by `init` after verifying
|
||||||
|
// sdt.length() >= 64. The `firmware_waking_vector` field
|
||||||
|
// is at offset 36 (after the 36-byte SDT header). The
|
||||||
|
// memory is page-aligned (firmware memory) and writable.
|
||||||
|
unsafe {
|
||||||
|
let waking_vector_ptr = (ptr as *mut u8).add(36) as *mut u32;
|
||||||
|
core::ptr::write_unaligned(waking_vector_ptr, addr);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Write the 64-bit firmware waking vector.
|
||||||
|
/// The kernel's S3 trampoline is written here so the modern
|
||||||
|
/// 64-bit firmware wake path works.
|
||||||
|
///
|
||||||
|
/// Returns true on success, false if the FACS is not
|
||||||
|
/// available.
|
||||||
|
pub fn set_x_firmware_waking_vector_64(addr: u64) -> bool {
|
||||||
|
let ptr = FACS_PTR.load(core::sync::atomic::Ordering::Acquire);
|
||||||
|
if ptr.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// SAFETY: FACS_PTR was set by `init` after verifying
|
||||||
|
// sdt.length() >= 64. The `xfirmware_waking_vector` field
|
||||||
|
// is at offset 40. The memory is page-aligned and
|
||||||
|
// writable.
|
||||||
|
unsafe {
|
||||||
|
let x_waking_vector_ptr = (ptr as *mut u8).add(40) as *mut u64;
|
||||||
|
core::ptr::write_unaligned(x_waking_vector_ptr, addr);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Write the 64-bit firmware waking vector
|
||||||
|
/// (preferred over the 32-bit version on 64-bit systems).
|
||||||
|
/// Equivalent to `set_x_firmware_waking_vector_64`.
|
||||||
|
pub fn set_waking_vector(addr: u64) -> bool {
|
||||||
|
set_x_firmware_waking_vector_64(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the FACS version. Returns 0 if the
|
||||||
|
/// FACS is not available.
|
||||||
|
pub fn version() -> u8 {
|
||||||
|
self::get().map(|f| f.version).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the FACS hardware signature. Returns 0
|
||||||
|
/// if the FACS is not available.
|
||||||
|
pub fn hardware_signature() -> u32 {
|
||||||
|
self::get()
|
||||||
|
.map(|f| f.hardware_signature)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the FACS flags. Returns 0 if the FACS
|
||||||
|
/// is not available.
|
||||||
|
///
|
||||||
|
/// See `facs_flags::*` for the bit definitions.
|
||||||
|
pub fn flags() -> u32 {
|
||||||
|
self::get().map(|f| f.flags).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the FACS OSPM flags. Returns 0 if the
|
||||||
|
/// FACS is not available.
|
||||||
|
///
|
||||||
|
/// See `facs_ospm_flags::*` for the bit definitions.
|
||||||
|
pub fn ospm_flags() -> u32 {
|
||||||
|
self::get().map(|f| f.ospm_flags).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the FACS global lock. Returns 0 if the
|
||||||
|
/// FACS is not available.
|
||||||
|
///
|
||||||
|
/// See `facs_glock_flags::*` for the bit definitions.
|
||||||
|
pub fn global_lock() -> u32 {
|
||||||
|
self::get().map(|f| f.global_lock).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the reserved bytes. Returns `None` if
|
||||||
|
/// the FACS is not available.
|
||||||
|
pub fn reserved() -> Option<[u8; 3]> {
|
||||||
|
self::get().map(|f| f.reserved)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: Read the ACPI 4.0+ reserved bytes. Returns
|
||||||
|
/// `None` if the FACS is not available.
|
||||||
|
pub fn reserved1() -> Option<[u8; 24]> {
|
||||||
|
self::get().map(|f| f.reserved1)
|
||||||
|
}
|
||||||
+88
-12
@@ -2,13 +2,14 @@
|
|||||||
//!
|
//!
|
||||||
//! Per ACPI 6.5 §5.2.9. The FADT contains the hardware register
|
//! Per ACPI 6.5 §5.2.9. The FADT contains the hardware register
|
||||||
//! addresses used by the kernel for ACPI sleep state entry (S3/S5)
|
//! addresses used by the kernel for ACPI sleep state entry (S3/S5)
|
||||||
//! and the SCI interrupt. This module only parses the fields the
|
//! and the SCI interrupt. This module parses the fields the
|
||||||
//! kernel needs (PM1a_CNT, PM1a_STS for the sleep entry path).
|
//! kernel needs (PM1a_CNT, PM1a_STS for the sleep entry path,
|
||||||
|
//! and x_firmware_ctrl / firmware_ctrl for the FACS address).
|
||||||
//!
|
//!
|
||||||
//! Hardware-agnostic: the FADT layout is standardized by the ACPI
|
//! Hardware-agnostic: the FADT layout is standardized by the ACPI
|
||||||
//! spec; only the field values vary per platform.
|
//! spec; only the field values vary per platform.
|
||||||
|
|
||||||
use core::sync::atomic::AtomicU16;
|
use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU64};
|
||||||
|
|
||||||
use crate::acpi::sdt::Sdt;
|
use crate::acpi::sdt::Sdt;
|
||||||
|
|
||||||
@@ -21,15 +22,49 @@ pub static PM1A_CONTROL_PORT: AtomicU16 = AtomicU16::new(0);
|
|||||||
/// WAK_STS (bit 15) before writing SLP_TYP|SLP_EN.
|
/// WAK_STS (bit 15) before writing SLP_TYP|SLP_EN.
|
||||||
pub static PM1A_STATUS_PORT: AtomicU16 = AtomicU16::new(0);
|
pub static PM1A_STATUS_PORT: AtomicU16 = AtomicU16::new(0);
|
||||||
|
|
||||||
|
/// Phase II.X.W: 32-bit FACS address (FADT offset 36,
|
||||||
|
/// `firmware_ctrl` field). Used as a fallback when
|
||||||
|
/// `x_firmware_ctrl` (offset 140, ACPI 2.0+) is not present
|
||||||
|
/// (i.e., for ACPI 1.0 systems).
|
||||||
|
pub static FIRMWARE_CTRL: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
|
/// Phase II.X.W: 64-bit FACS address (FADT offset 140,
|
||||||
|
/// `x_firmware_ctrl` field, ACPI 2.0+). The kernel's FACS
|
||||||
|
/// parser uses this to find the FACS for writing the
|
||||||
|
/// `xfirmware_waking_vector` on S3 entry.
|
||||||
|
pub static X_FIRMWARE_CTRL: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
/// FADT signature bytes ("FACP").
|
/// FADT signature bytes ("FACP").
|
||||||
const FADT_SIGNATURE: [u8; 4] = *b"FACP";
|
const FADT_SIGNATURE: [u8; 4] = *b"FACP";
|
||||||
|
|
||||||
|
/// FADT fixed offsets for the fields we read. These match
|
||||||
|
/// the ACPI 6.5 §5.2.9 Table 5.6 layout.
|
||||||
|
mod offsets {
|
||||||
|
/// PM1a_STS (PM1 Status Register) Block 0 Address.
|
||||||
|
/// 32-bit General-Purpose Event Register Block 0 Address.
|
||||||
|
pub const PM1A_STS: usize = 48;
|
||||||
|
/// PM1a_CNT (PM1 Control Register) Block 0 Address.
|
||||||
|
/// 32-bit General-Purpose Event Register Block 0 Address.
|
||||||
|
pub const PM1A_CNT: usize = 56;
|
||||||
|
/// `firmware_ctrl`: 32-bit Firmware ACPI Control
|
||||||
|
/// Structure address. ACPI 1.0+.
|
||||||
|
pub const FIRMWARE_CTRL_32: usize = 36;
|
||||||
|
/// `x_firmware_ctrl`: 64-bit Firmware ACPI Control
|
||||||
|
/// Structure address. ACPI 2.0+.
|
||||||
|
pub const X_FIRMWARE_CTRL_64: usize = 140;
|
||||||
|
/// FADT minimum size for ACPI 2.0+ (i.e., enough to
|
||||||
|
/// include `x_firmware_ctrl` at offset 140).
|
||||||
|
pub const FADT_MIN_SIZE_ACPI_2_0: usize = 148;
|
||||||
|
/// FADT minimum size for ACPI 1.0.
|
||||||
|
pub const FADT_MIN_SIZE_ACPI_1_0: usize = 76;
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the FADT from the given SDT bytes and extract the
|
/// Parse the FADT from the given SDT bytes and extract the
|
||||||
/// PM1a_CNT and PM1a_STS ports. Called once at boot after
|
/// PM1a_CNT, PM1a_STS, and FACS-address fields. Called once at
|
||||||
/// the ACPI table discovery finds the FADT.
|
/// boot after the ACPI table discovery finds the FADT.
|
||||||
///
|
///
|
||||||
/// The FADT layout is variable (different sizes for ACPI 1.0 vs
|
/// The FADT layout is variable (different sizes for ACPI 1.0 vs
|
||||||
/// 6.5+). We only need the first ~80 bytes which contain the
|
/// 6.5+). We only need the first ~148 bytes which contain the
|
||||||
/// fixed-register addresses. Reference: ACPI 6.5 §5.2.9.
|
/// fixed-register addresses. Reference: ACPI 6.5 §5.2.9.
|
||||||
pub fn init(sdt: &Sdt) {
|
pub fn init(sdt: &Sdt) {
|
||||||
if &sdt.signature != &FADT_SIGNATURE {
|
if &sdt.signature != &FADT_SIGNATURE {
|
||||||
@@ -37,20 +72,61 @@ pub fn init(sdt: &Sdt) {
|
|||||||
}
|
}
|
||||||
// SAFETY: We trust the ACPI table discovery code to have
|
// SAFETY: We trust the ACPI table discovery code to have
|
||||||
// verified the FADT checksum. The FADT fields are at fixed
|
// verified the FADT checksum. The FADT fields are at fixed
|
||||||
// offsets (per the ACPI spec); reading them as u32 is safe
|
// offsets (per the ACPI spec); reading them as u32/u64 is
|
||||||
// because all of them are at 4-byte aligned offsets on x86_64.
|
// 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;
|
let data = sdt.data_address() as *const u8;
|
||||||
unsafe {
|
unsafe {
|
||||||
// PM1a_CNT is at offset 56 in the FADT (ACPI 6.5 §5.2.9
|
// 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
|
// Table 5.6). 32-bit General-Purpose Event Register Block 0
|
||||||
// Address.
|
// Address.
|
||||||
let pm1a_cnt = core::ptr::read_unaligned(data.add(56) as *const u32);
|
let pm1a_cnt = core::ptr::read_unaligned(data.add(offsets::PM1A_CNT) as *const u32);
|
||||||
// PM1a_STS is at offset 48 in the FADT.
|
// PM1a_STS is at offset 48 in the FADT.
|
||||||
let pm1a_sts = core::ptr::read_unaligned(data.add(48) as *const u32);
|
let pm1a_sts = core::ptr::read_unaligned(data.add(offsets::PM1A_STS) as *const u32);
|
||||||
// Convert u32 to u16 (port numbers are 16-bit). The low
|
// Convert u32 to u16 (port numbers are 16-bit). The low
|
||||||
// 16 bits are the IO port; the high 16 bits are the
|
// 16 bits are the IO port; the high 16 bits are the
|
||||||
// address-space ID which we ignore (always IO on x86).
|
// address-space ID which we ignore (always IO on x86).
|
||||||
PM1A_CONTROL_PORT.store((pm1a_cnt & 0xFFFF) as u16, core::sync::atomic::Ordering::Release);
|
PM1A_CONTROL_PORT.store(
|
||||||
PM1A_STATUS_PORT.store((pm1a_sts & 0xFFFF) as u16, core::sync::atomic::Ordering::Release);
|
(pm1a_cnt & 0xFFFF) as u16,
|
||||||
|
core::sync::atomic::Ordering::Release,
|
||||||
|
);
|
||||||
|
PM1A_STATUS_PORT.store(
|
||||||
|
(pm1a_sts & 0xFFFF) as u16,
|
||||||
|
core::sync::atomic::Ordering::Release,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
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
|
||||||
|
// 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 {
|
||||||
|
let x_firmware_ctrl = core::ptr::read_unaligned(
|
||||||
|
data.add(offsets::X_FIRMWARE_CTRL_64) as *const u64,
|
||||||
|
);
|
||||||
|
X_FIRMWARE_CTRL.store(x_firmware_ctrl, core::sync::atomic::Ordering::Release);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: 32-bit FACS address (FADT offset 36,
|
||||||
|
/// `firmware_ctrl` field). Returns 0 if the FADT has not
|
||||||
|
/// been initialized.
|
||||||
|
pub fn firmware_ctrl() -> u32 {
|
||||||
|
FIRMWARE_CTRL.load(core::sync::atomic::Ordering::Acquire)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase II.X.W: 64-bit FACS address (FADT offset 140,
|
||||||
|
/// `x_firmware_ctrl` field). Returns 0 if the FADT has not
|
||||||
|
/// been initialized or the FADT is too short to have the
|
||||||
|
/// field.
|
||||||
|
pub fn x_firmware_ctrl() -> u64 {
|
||||||
|
X_FIRMWARE_CTRL.load(core::sync::atomic::Ordering::Acquire)
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use self::{hpet::Hpet, madt::Madt, rsdp::Rsdp, rsdt::Rsdt, rxsdt::Rxsdt, sdt::Sd
|
|||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
mod gtdt;
|
mod gtdt;
|
||||||
pub mod fadt;
|
pub mod fadt;
|
||||||
|
pub mod facs;
|
||||||
pub mod hpet;
|
pub mod hpet;
|
||||||
pub mod madt;
|
pub mod madt;
|
||||||
mod rsdp;
|
mod rsdp;
|
||||||
@@ -168,6 +169,34 @@ pub unsafe fn init(already_supplied_rsdp: Option<NonNull<u8>>) {
|
|||||||
} else {
|
} else {
|
||||||
warn!("ACPI: no FADT (FACP) found, S3 entry path disabled");
|
warn!("ACPI: no FADT (FACP) found, S3 entry path disabled");
|
||||||
}
|
}
|
||||||
|
// 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
|
||||||
|
// S3 resume trampoline in arch/x86_shared/s3_resume.rs
|
||||||
|
// is written to this address by acpid via the
|
||||||
|
// SetS3WakingVector AcPiVerb.
|
||||||
|
//
|
||||||
|
// The FACS is found via the FADT's x_firmware_ctrl
|
||||||
|
// 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.
|
||||||
|
if let Some(facs_addr) = fadt::x_firmware_ctrl() {
|
||||||
|
// SAFETY: The FACS address is a physical address
|
||||||
|
// stored in the FADT. The boot-time page table
|
||||||
|
// maps the FACS into the kernel's address space
|
||||||
|
// (firmware tables are below 4GB on x86_64).
|
||||||
|
if facs_addr != 0 {
|
||||||
|
let facs_sdt = unsafe { &*(facs_addr as *const Sdt) };
|
||||||
|
facs::init(facs_sdt);
|
||||||
|
}
|
||||||
|
} else if let Some(facs_addr) = fadt::firmware_ctrl() {
|
||||||
|
if facs_addr != 0 {
|
||||||
|
let facs_sdt = unsafe { &*(facs_addr as *const Sdt) };
|
||||||
|
facs::init(facs_sdt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("ACPI: no FACS found, S3 resume path disabled");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("NO RSDP FOUND");
|
error!("NO RSDP FOUND");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,6 +284,84 @@ impl KernelScheme for AcpiScheme {
|
|||||||
s2idle_signal_wake();
|
s2idle_signal_wake();
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
AcpiVerb::SetS3WakingVector => {
|
||||||
|
// Phase II.X.W: acpid writes the kernel's S3
|
||||||
|
// resume trampoline address (from s3_resume::
|
||||||
|
// s3_trampoline) to FACS.xfirmware_waking_vector
|
||||||
|
// so the platform firmware jumps to it on S3
|
||||||
|
// wake. The 8-byte write payload is the
|
||||||
|
// trampoline address in little-endian.
|
||||||
|
//
|
||||||
|
// A payload of all-zeros is a sentinel for
|
||||||
|
// "use the kernel's default trampoline
|
||||||
|
// address" (the in-kernel s3_trampoline symbol).
|
||||||
|
// The acpid doesn't need to know the address
|
||||||
|
// of the trampoline — it just signals "go".
|
||||||
|
//
|
||||||
|
// The 8-byte payload requirement is a usage
|
||||||
|
// contract with acpid; smaller payloads are a
|
||||||
|
// protocol error.
|
||||||
|
if payload.len() != 8 {
|
||||||
|
return Err(Error::new(EINVAL));
|
||||||
|
}
|
||||||
|
let addr = u64::from_ne_bytes([
|
||||||
|
payload[0], payload[1], payload[2], payload[3],
|
||||||
|
payload[4], payload[5], payload[6], payload[7],
|
||||||
|
]);
|
||||||
|
// If the payload is all zeros, use the kernel's
|
||||||
|
// default trampoline address (s3_resume::
|
||||||
|
// s3_trampoline). This is the typical case:
|
||||||
|
// acpid doesn't need to know the address.
|
||||||
|
let trampoline = if addr == 0 {
|
||||||
|
s3_resume::s3_trampoline as *const () as u64
|
||||||
|
} else {
|
||||||
|
addr
|
||||||
|
};
|
||||||
|
if !crate::acpi::facs::set_waking_vector(trampoline) {
|
||||||
|
return Err(Error::new(ENOSYS));
|
||||||
|
// FACS not available: the platform
|
||||||
|
// doesn't expose x_firmware_ctrl in
|
||||||
|
// the FADT (ACPI 1.0 or very old hardware).
|
||||||
|
}
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
AcpiVerb::EnterS3 => {
|
||||||
|
// Phase II.X.W: acpid has done the AML prep
|
||||||
|
// (`_TTS(3)`, `_PTS(3)`, `_SST(3)`) and written
|
||||||
|
// the trampoline address to FACS via
|
||||||
|
// `SetS3WakingVector`. The acpid now requests
|
||||||
|
// the kernel to enter S3. The kernel's
|
||||||
|
// `enter_s3()` reads the SLP_TYP value from
|
||||||
|
// `S3_SLP_TYP` (set by acpid via a previous
|
||||||
|
// kstop write) and does the PM1 register
|
||||||
|
// write.
|
||||||
|
//
|
||||||
|
// We don't pass the SLP_TYP through the verb
|
||||||
|
// payload; the kernel already has it in
|
||||||
|
// `S3_SLP_TYP`. The acpid sets it via a
|
||||||
|
// separate kstop write of the form "s3X" (see
|
||||||
|
// the `s3` branch in `sys/mod.rs::kstop`).
|
||||||
|
//
|
||||||
|
// To trigger the S3 entry, we do an indirect
|
||||||
|
// call: we write "s3" to the sys scheme's
|
||||||
|
// kstop entry, which the kernel routes to
|
||||||
|
// `enter_s3()`. We can do this from the kernel
|
||||||
|
// side via the same atomic-write mechanism.
|
||||||
|
//
|
||||||
|
// For simplicity, we return Ok(0) here and let
|
||||||
|
// the acpid write the S3 entry via the
|
||||||
|
// /scheme/sys/kstop path (which calls the
|
||||||
|
// kernel's stop::enter_s3). The S3 entry will
|
||||||
|
// happen after the kcall returns.
|
||||||
|
//
|
||||||
|
// This verb is essentially a no-op on the
|
||||||
|
// AcpiScheme side; it exists for API symmetry
|
||||||
|
// and future expansion (e.g., the kernel may
|
||||||
|
// eventually trigger S3 directly from the
|
||||||
|
// AcpiScheme in response to a power-button
|
||||||
|
// press).
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user