--- a/drivers/acpid/src/acpi.rs 2026-05-03 15:42:05.770648512 +0100 +++ b/drivers/acpid/src/acpi.rs 2026-05-03 15:42:05.778560344 +0100 @@ -654,6 +654,84 @@ log::error!("ACPI shutdown not supported on this architecture"); } } + + /// Suspend-to-RAM (S3 sleep state) + /// See ACPI 6.1 spec for SLP_TYPa/SLP_TYPb encoding + pub fn suspend_to_ram(&self) { + log::info!("ACPI: attempting suspend-to-RAM (S3)"); + let fadt = match self.fadt() { + Some(f) => f, + None => { log::error!("ACPI S3: missing FADT"); return; } + }; + let pm1a = fadt.pm1a_control_block as u16; + if pm1a == 0 { + log::error!("ACPI S3: PM1a port is zero"); + return; + } + let aml_symbols = self.aml_symbols.read(); + let s3_name = match acpi::aml::namespace::AmlName::from_str("\\_S3") { + Ok(n) => n, + Err(e) => { log::error!("ACPI S3: \\_S3 name error: {:?}", e); return; } + }; + let s3 = match &aml_symbols.aml_context { + Some(ctx) => match ctx.namespace.lock().get(s3_name) { + Ok(s) => s, + Err(e) => { log::error!("ACPI S3: \\_S3 not found: {:?}", e); return; } + }, + None => { log::error!("ACPI S3: AML context missing"); return; } + }; + let pkg = match s3.deref() { + acpi::aml::object::Object::Package(p) => p, + _ => { log::error!("ACPI S3: \\_S3 not a package"); return; } + }; + let slp_typa = match pkg[0].deref() { + acpi::aml::object::Object::Integer(i) => *i as u16, + _ => { log::error!("ACPI S3: SLP_TYPa not integer"); return; } + }; + let mut val = (1u16 << 13) | (slp_typa & 0x1FFF); + log::info!("ACPI S3: writing PM1a=0x{:04X} val=0x{:04X}", pm1a, val); + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { Pio::::new(pm1a).write(val); } + } + + + /// Query ACPI battery via \\_SB_.BAT0._BST + /// Returns (remaining_capacity_mAh, present_voltage_mV) if available + pub fn read_battery_status(&self) -> Option<(u32, u32)> { + let aml_symbols = self.aml_symbols.read(); + let ctx = aml_symbols.aml_context.as_ref()?; + let mut ns = ctx.namespace.lock(); + let bst_name = acpi::aml::namespace::AmlName::from_str("\\_SB_.BAT0._BST").ok()?; + let bst = ns.get(bst_name).ok()?; + match bst.deref() { + acpi::aml::object::Object::Package(p) if p.len() >= 4 => { + let cap = match p[1].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + let volt = match p[2].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + Some((cap, volt)) + } + _ => { log::warn!("ACPI: _BST not a valid battery package"); None } + } + } + + /// Query ACPI battery info via \\_SB_.BAT0._BIF + /// Returns (design_capacity_mAh, last_full_capacity_mAh, design_voltage_mV) if available + pub fn read_battery_info(&self) -> Option<(u32, u32, u32)> { + let aml_symbols = self.aml_symbols.read(); + let ctx = aml_symbols.aml_context.as_ref()?; + let mut ns = ctx.namespace.lock(); + let bif_name = acpi::aml::namespace::AmlName::from_str("\\_SB_.BAT0._BIF").ok()?; + let bif = ns.get(bif_name).ok()?; + match bif.deref() { + acpi::aml::object::Object::Package(p) if p.len() >= 13 => { + let design = match p[1].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + let last = match p[2].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + let volt = match p[4].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + Some((design, last, volt)) + } + _ => { log::warn!("ACPI: _BIF not a valid battery package"); None } + } + } + } #[repr(C, packed)]