acpid: Add ACPI thermal zone discovery and evaluation (P44)

Implement full thermal zone backend in acpid:
- thermal.rs: Discover \_TZ_.TZ* zones, evaluate \_TMP, \_CRT, \_PSV,
  \_AC0, \_TC1, \_TC2, \_TSP, \_TZP methods
- scheme.rs: Expose /scheme/acpi/thermal/ with per-zone temperature files
- acpi.rs: Add thermal_state and thermal_zone_names() to AcpiContext

Wired as P44 patch in base recipe.toml.
This commit is contained in:
2026-05-20 13:47:04 +03:00
parent 6ca3e47383
commit b1af8a356f
2 changed files with 793 additions and 0 deletions
@@ -0,0 +1,783 @@
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
index 94a1eb17..5c881334 100644
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -55,3 +55,2 @@ impl SdtHeader {
- self.length
- .try_into()
- .expect("expected usize to be at least 32 bits")
+ // usize is at least 32 bits on all supported architectures.
+ self.length as usize
@@ -95,0 +95,3 @@ pub enum InvalidSdtError {
+
+ #[error("bad alignment")]
+ BadAlignment,
@@ -98 +100 @@ pub enum InvalidSdtError {
-struct PhysmapGuard {
+pub struct PhysmapGuard {
@@ -103 +105 @@ impl PhysmapGuard {
- fn map(page: usize, page_count: usize) -> std::io::Result<Self> {
+ pub fn map(page: usize, page_count: usize) -> std::io::Result<Self> {
@@ -139,3 +141,4 @@ impl Sdt {
- Err(plain::Error::BadAlignment) => panic!(
- "plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!"
- ),
+ Err(plain::Error::BadAlignment) => {
+ log::error!("plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]");
+ return Err(InvalidSdtError::BadAlignment);
+ }
@@ -171 +174,3 @@ impl Sdt {
- assert!(pages.len() >= mem::size_of::<SdtHeader>());
+ if pages.len() < mem::size_of::<SdtHeader>() {
+ return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize));
+ }
@@ -174,2 +179,5 @@ impl Sdt {
- let sdt = plain::from_bytes::<SdtHeader>(&sdt_mem[..mem::size_of::<SdtHeader>()])
- .expect("either alignment is wrong, or the length is too short, both of which are already checked for");
+ let sdt = match plain::from_bytes::<SdtHeader>(&sdt_mem[..mem::size_of::<SdtHeader>()]) {
+ Ok(header) => header,
+ Err(plain::Error::TooShort) => return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize)),
+ Err(plain::Error::BadAlignment) => return Err(TablePhysLoadError::Validity(InvalidSdtError::BadAlignment)),
+ };
@@ -200 +208,4 @@ impl Sdt {
- assert_eq!(left, 0);
+ if left != 0 {
+ log::error!("SDT physical load left {} bytes remaining after loop", left);
+ return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize));
+ }
@@ -213,2 +224,2 @@ impl Deref for Sdt {
- plain::from_bytes::<SdtHeader>(&self.0)
- .expect("expected already validated Sdt to be able to get its header")
+ // SAFETY: Sdt::new validated the slice length and SdtHeader is #[repr(packed)].
+ unsafe { &*(self.0.as_ptr() as *const SdtHeader) }
@@ -247,0 +259 @@ pub struct AmlSymbols {
+ pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
@@ -251 +263 @@ impl AmlSymbols {
- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> Self {
+ pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>, pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>) -> Self {
@@ -256,0 +269 @@ impl AmlSymbols {
+ pci_fd,
@@ -260 +273 @@ impl AmlSymbols {
- pub fn init(&mut self, pci_fd: Option<&libredox::Fd>) -> Result<(), Box<dyn Error>> {
+ pub fn init(&mut self) -> Result<(), Box<dyn Error>> {
@@ -265 +278 @@ impl AmlSymbols {
- let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
+ let handler = AmlPhysMemHandler::new(Arc::clone(&self.pci_fd), Arc::clone(&self.page_cache));
@@ -281 +293,0 @@ impl AmlSymbols {
- pci_fd: Option<&libredox::Fd>,
@@ -284 +296 @@ impl AmlSymbols {
- match self.init(pci_fd) {
+ match self.init() {
@@ -308,2 +320,2 @@ impl AmlSymbols {
- pub fn build_cache(&mut self, pci_fd: Option<&libredox::Fd>) {
- let Ok(aml_context) = self.aml_context_mut(pci_fd) else {
+ pub fn build_cache(&mut self) {
+ let Ok(aml_context) = self.aml_context_mut() else {
@@ -377,0 +390,44 @@ impl From<AmlError> for AmlEvalError {
+/// Cached S5 (soft-off) state derived from FADT and AML \_S5 package.
+///
+/// Derived once at startup (or on first shutdown if AML was not ready at init)
+/// and reused for all subsequent shutdown attempts, eliminating redundant AML
+/// namespace lookups on the critical shutdown path.
+#[derive(Clone, Debug)]
+pub struct S5State {
+ pub slp_typa: u16,
+ pub slp_typb: u16,
+ pub pm1a_port: u16,
+ pub pm1b_port: u16,
+ pub derived_at: &'static str,
+}
+
+/// Errors that can occur when deriving or executing the S5 shutdown sequence.
+#[derive(Debug, Error)]
+pub enum ShutdownError {
+ #[error("FADT not available — cannot determine shutdown parameters")]
+ MissingFadt,
+ #[error("PM1a control block address is zero — ACPI shutdown unavailable")]
+ Pm1aZero,
+ #[error("AML interpreter not initialized — cannot look up \\_S5")]
+ AmlNotReady,
+ #[error("\\_S5 not found in AML namespace")]
+ S5NotFound,
+ #[error("\\_S5 is not a Package object")]
+ S5NotPackage,
+ #[error("SLP_TYP value in \\_S5 is not an Integer")]
+ SlpTypNotInteger,
+ #[error("PM1a control write failed")]
+ S5WriteFailed,
+}
+
+/// Result of a shutdown attempt.
+#[derive(Debug)]
+pub enum ShutdownResult {
+ /// Shutdown sequence completed (machine should power off).
+ Ok,
+ /// ACPI shutdown failed; fell back to keyboard controller reset.
+ FallbackReset,
+ /// Shutdown could not proceed due to a deterministic error.
+ Err(ShutdownError),
+}
+
@@ -384,0 +441,4 @@ pub struct AcpiContext {
+ s5_state: RwLock<Option<S5State>>,
+
+ pub pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
+
@@ -390,0 +451,2 @@ pub struct AcpiContext {
+
+ pub thermal_state: crate::thermal::ThermalState,
@@ -400 +462 @@ impl AcpiContext {
- let interpreter = symbols.aml_context_mut(None)?;
+ let interpreter = symbols.aml_context_mut()?;
@@ -414,3 +476,3 @@ impl AcpiContext {
- interpreter
- .release_global_lock()
- .expect("Failed to release GIL!"); //TODO: check if this should panic
+ if let Err(e) = interpreter.release_global_lock() {
+ log::error!("Failed to release AML global lock: {:?}", e);
+ }
@@ -432,4 +494,8 @@ impl AcpiContext {
- .map(|physaddr| {
- let physaddr: usize = physaddr
- .try_into()
- .expect("expected ACPI addresses to be compatible with the current word size");
+ .filter_map(|physaddr| {
+ let physaddr: usize = match physaddr.try_into() {
+ Ok(addr) => addr,
+ Err(e) => {
+ log::error!("expected ACPI addresses to be compatible with the current word size: {}", e);
+ return None;
+ }
+ };
@@ -439 +505,7 @@ impl AcpiContext {
- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT")
+ match Sdt::load_from_physical(physaddr) {
+ Ok(sdt) => Some(sdt),
+ Err(error) => {
+ log::error!("failed to load physical SDT at {:#x}: {}", physaddr, error);
+ None
+ }
+ }
@@ -442,0 +515,2 @@ impl AcpiContext {
+ let pci_fd = Arc::new(parking_lot::RwLock::new(None));
+
@@ -449 +523,5 @@ impl AcpiContext {
- aml_symbols: RwLock::new(AmlSymbols::new(ec)),
+ aml_symbols: RwLock::new(AmlSymbols::new(ec, Arc::clone(&pci_fd))),
+
+ s5_state: RwLock::new(None),
+
+ pci_fd,
@@ -453,0 +532,2 @@ impl AcpiContext {
+
+ thermal_state: crate::thermal::ThermalState::new(),
@@ -462,0 +543,24 @@ impl AcpiContext {
+ // Trigger AML interpreter initialization so we can derive S5 state early.
+ // If AML init fails, S5 derivation will fall back to "shutdown_fallback" at
+ // shutdown time.
+ {
+ let mut symbols = this.aml_symbols.write();
+ if symbols.aml_context.is_none() {
+ if let Err(e) = symbols.init() {
+ log::warn!("ACPI S5: AML init at startup failed: {} — will derive at shutdown", e);
+ }
+ }
+ }
+ match this.derive_s5_state("register_pci") {
+ Ok(_) => {}
+ Err(ShutdownError::AmlNotReady) => {
+ log::info!("ACPI S5: AML not ready at init — will derive at shutdown");
+ }
+ Err(e) => {
+ log::warn!("ACPI S5: early derivation failed: {} — will derive at shutdown", e);
+ }
+ }
+
+ // Discover thermal zones if AML is ready.
+ this.thermal_state.refresh(&this);
+
@@ -529 +633 @@ impl AcpiContext {
- if let Ok(aml_symbols) = self.aml_symbols(None) {
+ if let Ok(aml_symbols) = self.aml_symbols() {
@@ -535,0 +640,24 @@ impl AcpiContext {
+ /// Discover thermal zone names by scanning the AML namespace under `\_TZ`.
+ pub fn thermal_zone_names(&self) -> Result<Vec<String>, AmlEvalError> {
+ let mut symbols = self.aml_symbols.write();
+ let interpreter = symbols.aml_context_mut()?;
+ let mut ns = interpreter.namespace.lock();
+
+ let mut names = Vec::new();
+ let _ = ns.traverse(|level_aml_name, _level| {
+ let name_str = aml_to_symbol(level_aml_name);
+ if name_str.starts_with("\\_TZ_.TZ") || name_str.starts_with("_TZ_.TZ") {
+ let after_prefix = if name_str.starts_with("\\_TZ_.") {
+ &name_str[7..]
+ } else {
+ &name_str[6..]
+ };
+ if !after_prefix.contains('.') {
+ names.push(after_prefix.to_string());
+ }
+ }
+ Ok(true)
+ });
+ Ok(names)
+ }
+
@@ -538 +665,0 @@ impl AcpiContext {
- pci_fd: Option<&libredox::Fd>,
@@ -553 +680 @@ impl AcpiContext {
- aml_symbols.build_cache(pci_fd);
+ aml_symbols.build_cache();
@@ -564,0 +692,69 @@ impl AcpiContext {
+ /// Derive the S5 (soft-off) state from FADT and AML \_S5 package.
+///
+/// Reads PM1a/PM1b control block addresses from the FADT and the SLP_TYP
+/// values from the AML `\_S5` package, then caches the result. Subsequent
+/// calls return the cached value without re-parsing AML.
+///
+/// `derived_at` is a log marker indicating when this derivation occurred
+/// (e.g. "register_pci", "shutdown_fallback").
+ pub fn derive_s5_state(&self, derived_at: &'static str) -> Result<S5State, ShutdownError> {
+ let fadt = self.fadt().ok_or(ShutdownError::MissingFadt)?;
+ let pm1a_port = fadt.pm1a_control_block as u16;
+ let pm1b_port = fadt.pm1b_control_block as u16;
+
+ if pm1a_port == 0 {
+ return Err(ShutdownError::Pm1aZero);
+ }
+
+ let aml_symbols = self.aml_symbols.read();
+ let aml_context = aml_symbols
+ .aml_context
+ .as_ref()
+ .ok_or(ShutdownError::AmlNotReady)?;
+
+ let s5_name = acpi::aml::namespace::AmlName::from_str("\\_S5")
+ .map_err(|_| ShutdownError::S5NotFound)?;
+
+ let s5 = aml_context
+ .namespace
+ .lock()
+ .get(s5_name)
+ .map_err(|_| ShutdownError::S5NotFound)?;
+
+ let package = match s5.deref() {
+ acpi::aml::object::Object::Package(p) => p,
+ _ => return Err(ShutdownError::S5NotPackage),
+ };
+
+ let slp_typa = match package[0].deref() {
+ acpi::aml::object::Object::Integer(i) => *i as u16,
+ _ => return Err(ShutdownError::SlpTypNotInteger),
+ };
+ let slp_typb = if package.len() > 1 {
+ match package[1].deref() {
+ acpi::aml::object::Object::Integer(i) => *i as u16,
+ _ => 0u16,
+ }
+ } else {
+ 0u16
+ };
+
+ let state = S5State {
+ slp_typa,
+ slp_typb,
+ pm1a_port,
+ pm1b_port,
+ derived_at,
+ };
+
+ log::info!(
+ "ACPI S5: derived at={}, SLP_TYPa=0x{:X}, SLP_TYPb=0x{:X}, PM1a=0x{:04X}, PM1b=0x{:04X}",
+ derived_at, slp_typa, slp_typb, pm1a_port, pm1b_port
+ );
+
+ drop(aml_symbols);
+ *self.s5_state.write() = Some(state.clone());
+
+ Ok(state)
+ }
+
@@ -569 +765 @@ impl AcpiContext {
- pub fn set_global_s_state(&self, state: u8) {
+ pub fn set_global_s_state(&self, state: u8) -> ShutdownResult {
@@ -571 +767 @@ impl AcpiContext {
- return;
+ return ShutdownResult::Ok;
@@ -573,5 +769,12 @@ impl AcpiContext {
- let fadt = match self.fadt() {
- Some(fadt) => fadt,
- None => {
- log::error!("Cannot set global S-state due to missing FADT.");
- return;
+
+ let s5 = match self.s5_state.read().as_ref() {
+ Some(cached) => cached.clone(),
+ None => match self.derive_s5_state("shutdown_fallback") {
+ Ok(s5) => s5,
+ Err(e) => {
+ log::error!("ACPI S5 derivation failed: {}", e);
+ if matches!(e, ShutdownError::Pm1aZero | ShutdownError::MissingFadt) {
+ log::warn!("Falling back to keyboard controller reset");
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ Pio::<u8>::new(0x64u16).write(0xFEu8);
+ return ShutdownResult::FallbackReset;
@@ -578,0 +782,3 @@ impl AcpiContext {
+ return ShutdownResult::Err(e);
+ }
+ },
@@ -581,2 +787,7 @@ impl AcpiContext {
- let port = fadt.pm1a_control_block as u16;
- let mut val = 1 << 13;
+ if s5.pm1a_port == 0 {
+ log::error!("ACPI S5: cached PM1a port is zero — shutdown unavailable");
+ log::warn!("Falling back to keyboard controller reset");
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ Pio::<u8>::new(0x64u16).write(0xFEu8);
+ return ShutdownResult::FallbackReset;
+ }
@@ -584 +795 @@ impl AcpiContext {
- let aml_symbols = self.aml_symbols.read();
+ let mut val = (1u16 << 13) | (s5.slp_typa & 0x1FFF);
@@ -586,7 +797,6 @@ impl AcpiContext {
- let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") {
- Ok(aml_name) => aml_name,
- Err(error) => {
- log::error!("Could not build AmlName for \\_S5, {:?}", error);
- return;
- }
- };
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ log::info!(
+ "ACPI shutdown: writing PM1a=0x{:04X} val=0x{:04X} (SLP_TYPa=0x{:X}, SLP_TYPb=0x{:X})",
+ s5.pm1a_port, val, s5.slp_typa, s5.slp_typb
+ );
@@ -594,11 +804,13 @@ impl AcpiContext {
- let s5 = match &aml_symbols.aml_context {
- Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) {
- Ok(s5) => s5,
- Err(error) => {
- log::error!("Cannot set S-state, missing \\_S5, {:?}", error);
- return;
- }
- },
- None => {
- log::error!("Cannot set S-state, AML context not initialized");
- return;
+ let mut pio = Pio::<u16>::new(s5.pm1a_port);
+ pio.write(val);
+
+ std::thread::sleep(std::time::Duration::from_secs(3));
+
+ log::warn!("ACPI PM1a shutdown did not power off — retrying with PM1b");
+ val = (1u16 << 13) | (s5.slp_typb & 0x1FFF);
+ pio.write(val);
+
+ if s5.pm1b_port != 0 {
+ let mut pio_b = Pio::<u16>::new(s5.pm1b_port);
+ pio_b.write(val);
+ log::info!("ACPI shutdown: also wrote PM1b=0x{:04X}", s5.pm1b_port);
@@ -606 +817,0 @@ impl AcpiContext {
- };
@@ -608,5 +819,4 @@ impl AcpiContext {
- let package = match s5.deref() {
- acpi::aml::object::Object::Package(package) => package,
- _ => {
- log::error!("Cannot set S-state, \\_S5 is not a package");
- return;
+ std::thread::sleep(std::time::Duration::from_secs(2));
+ log::error!("ACPI shutdown failed — falling back to keyboard controller reset");
+ Pio::<u8>::new(0x64u16).write(0xFEu8);
+ return ShutdownResult::FallbackReset;
@@ -614 +823,0 @@ impl AcpiContext {
- };
@@ -616,5 +825,5 @@ impl AcpiContext {
- let slp_typa = match package[0].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typa is not an Integer");
- return;
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ {
+ log::error!("ACPI shutdown not supported on this architecture");
+ ShutdownResult::Err(ShutdownError::S5WriteFailed)
+ }
@@ -621,0 +831,8 @@ impl AcpiContext {
+
+ /// 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; }
@@ -623,4 +840,3 @@ impl AcpiContext {
- let slp_typb = match package[1].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typb is not an Integer");
+ let pm1a = fadt.pm1a_control_block as u16;
+ if pm1a == 0 {
+ log::error!("ACPI S3: PM1a port is zero");
@@ -628,0 +845,4 @@ impl AcpiContext {
+ 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; }
@@ -630,4 +850,17 @@ impl AcpiContext {
-
- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
- val |= slp_typa as u16;
-
+ 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);
@@ -635,3 +868 @@ impl AcpiContext {
- {
- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val);
- Pio::<u16>::new(port).write(val);
+ { Pio::<u16>::new(pm1a).write(val); }
@@ -640 +870,0 @@ impl AcpiContext {
- // TODO: Handle SLP_TYPb
@@ -642,7 +872,16 @@ impl AcpiContext {
- #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
- {
- log::error!(
- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture",
- port,
- 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 }
+ }
@@ -651,2 +890,14 @@ impl AcpiContext {
- loop {
- core::hint::spin_loop();
+ /// 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))
@@ -653,0 +905 @@ impl AcpiContext {
+ _ => { log::warn!("ACPI: _BIF not a valid battery package"); None }
@@ -656,0 +909,2 @@ impl AcpiContext {
+}
+
@@ -752,3 +1006,4 @@ impl Fadt {
- Err(plain::Error::BadAlignment) => unreachable!(
- "plain::from_bytes reported bad alignment, but FadtAcpi2Struct is #[repr(packed)]"
- ),
+ Err(plain::Error::BadAlignment) => {
+ log::error!("plain::from_bytes reported bad alignment for FadtAcpi2Struct, but it is #[repr(packed)]");
+ None
+ }
@@ -763,2 +1018,2 @@ impl Deref for Fadt {
- plain::from_bytes::<FadtStruct>(&self.0 .0)
- .expect("expected FADT struct to already be validated in Deref impl")
+ // SAFETY: Fadt::new validated the slice length and FadtStruct is #[repr(packed)].
+ unsafe { &*(self.0 .0.as_ptr() as *const FadtStruct) }
@@ -777,3 +1032,7 @@ impl Fadt {
- let fadt_sdt = context
- .take_single_sdt(*b"FACP")
- .expect("expected ACPI to always have a FADT");
+ let fadt_sdt = match context.take_single_sdt(*b"FACP") {
+ Some(sdt) => sdt,
+ None => {
+ log::error!("expected ACPI to always have a FADT");
+ return;
+ }
+ };
@@ -790,4 +1049,2 @@ impl Fadt {
- Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| {
- usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize")
- }),
- None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"),
+ Some(fadt2) => fadt2.x_dsdt as usize,
+ None => fadt.dsdt as usize,
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
index 059254b3..91336ba7 100644
--- a/drivers/acpid/src/main.rs
+++ b/drivers/acpid/src/main.rs
@@ -16,0 +17,2 @@ mod ec;
+mod dmi;
+mod thermal;
@@ -31,3 +33,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`")
- .into();
+ let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") {
+ Ok(data) => data.into(),
+ Err(e) => {
+ log::warn!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {} — no ACPI", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
@@ -41 +48,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
+ let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) {
+ Ok(sdt) => sdt,
+ Err(e) => {
+ log::warn!("acpid: failed to parse [RX]SDT: {} — booting without ACPI", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
@@ -67 +81,5 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
+ _ => {
+ log::warn!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT, got {:?} — booting without ACPI", String::from_utf8_lossy(&sdt.signature));
+ daemon.ready();
+ std::process::exit(0);
+ }
@@ -75,0 +94,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
+ let dmi_strings = match self::dmi::read_smbios_dmi() {
+ Ok(strings) => Some(strings),
+ Err(e) => {
+ log::warn!("Failed to read SMBIOS DMI: {}", e);
+ None
+ }
+ };
+
@@ -78 +104,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
+ if let Err(e) = common::acquire_port_io_rights() {
+ log::warn!("acpid: failed to set I/O privilege level to Ring 3: {} — continuing without port I/O", e);
+ }
@@ -80,2 +108,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
+ let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") {
+ Ok(f) => f,
+ Err(e) => {
+ log::warn!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {} — booting without ACPI shutdown", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
@@ -83,2 +117,17 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
+ let mut event_queue = match RawEventQueue::new() {
+ Ok(q) => q,
+ Err(e) => {
+ log::warn!("acpid: failed to create event queue: {} — booting without ACPI", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
+
+ let socket = match Socket::nonblock() {
+ Ok(s) => s,
+ Err(e) => {
+ log::warn!("acpid: failed to create scheme socket: {} — booting without ACPI", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
@@ -86 +135 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
+ let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket, dmi_strings);
@@ -89,6 +138,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- event_queue
- .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
- .expect("acpid: failed to register shutdown pipe for event queue");
- event_queue
- .subscribe(socket.inner().raw(), 1, EventFlags::READ)
- .expect("acpid: failed to register scheme socket for event queue");
+ if let Err(e) = event_queue
+ .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) {
+ log::warn!("acpid: failed to register shutdown pipe for event queue: {} — booting without ACPI", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ if let Err(e) = event_queue
+ .subscribe(socket.inner().raw(), 1, EventFlags::READ) {
+ log::warn!("acpid: failed to register scheme socket for event queue: {} — booting without ACPI", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
@@ -96,2 +151,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- register_sync_scheme(&socket, "acpi", &mut scheme)
- .expect("acpid: failed to register acpi scheme to namespace");
+ if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) {
+ log::warn!(
+ "acpid: failed to register acpi scheme (error: {}). Another acpid instance may already own it.",
+ err
+ );
+ daemon.ready();
+ std::process::exit(0);
+ }
@@ -101 +162,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
+ if let Err(e) = libredox::call::setrens(0, 0) {
+ log::warn!("acpid: failed to enter null namespace: {} — continuing", e);
+ }
@@ -105,6 +168,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let Some(event) = event_queue
- .next()
- .transpose()
- .expect("acpid: failed to read event file")
- else {
- break;
+ let event = match event_queue.next().transpose() {
+ Ok(Some(e)) => e,
+ Ok(None) => break,
+ Err(e) => {
+ log::error!("acpid: failed to read event file: {} — continuing", e);
+ continue;
+ }
@@ -115,6 +179,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- match handler
- .process_requests_nonblocking(&mut scheme)
- .expect("acpid: failed to process requests")
- {
- ControlFlow::Continue(()) => {}
- ControlFlow::Break(()) => break,
+ match handler.process_requests_nonblocking(&mut scheme) {
+ Ok(ControlFlow::Continue(())) => {}
+ Ok(ControlFlow::Break(())) => break,
+ Err(e) => {
+ log::error!("acpid: failed to process requests: {} — continuing", e);
+ continue;
+ }
@@ -135 +200,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- acpi_context.set_global_s_state(5);
+ let result = acpi_context.set_global_s_state(5);
+ log::info!("ACPI shutdown result: {:?}", result);
@@ -137 +203,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- unreachable!("System should have shut down before this is entered");
+ log::error!("System should have shut down before this was reached");
+ std::process::exit(1);
@@ -141 +208 @@ fn main() {
- common::init();
+ if let Err(err) = common::init() { eprintln!("acpid: failed to initialize common: {err}"); std::process::exit(1); }
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
index 5a5040c3..b92327be 100644
--- a/drivers/acpid/src/scheme.rs
+++ b/drivers/acpid/src/scheme.rs
@@ -24,0 +25 @@ use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
+use crate::dmi::DmiStrings;
@@ -29 +29,0 @@ pub struct AcpiScheme<'acpi, 'sock> {
- pci_fd: Option<Fd>,
@@ -30,0 +31 @@ pub struct AcpiScheme<'acpi, 'sock> {
+ dmi_text: Option<String>,
@@ -45,0 +47,3 @@ enum HandleKind<'a> {
+ Dmi(String),
+ Thermal,
+ ThermalZone(String),
@@ -57,0 +62,3 @@ impl HandleKind<'_> {
+ Self::Dmi(_) => false,
+ Self::Thermal => true,
+ Self::ThermalZone(_) => false,
@@ -67,0 +75 @@ impl HandleKind<'_> {
+ Self::Dmi(text) => text.len(),
@@ -70,0 +79,2 @@ impl HandleKind<'_> {
+ Self::Thermal => 0,
+ Self::ThermalZone(ref text) => text.len(),
@@ -76 +86 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
- pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket) -> Self {
+ pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket, dmi: Option<DmiStrings>) -> Self {
@@ -80 +89,0 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
- pci_fd: None,
@@ -81,0 +91 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
+ dmi_text: dmi.map(|d| d.to_text()),
@@ -198,0 +209 @@ impl SchemeSync for AcpiScheme<'_, '_> {
+ ["dmi"] => HandleKind::Dmi(self.dmi_text.clone().unwrap_or_default()),
@@ -207 +218 @@ impl SchemeSync for AcpiScheme<'_, '_> {
- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
+ if let Ok(aml_symbols) = self.ctx.aml_symbols() {
@@ -224,0 +236,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
+ ["thermal"] => HandleKind::Thermal,
+ ["thermal", zone] => {
+ if let Some(tz) = self.ctx.thermal_state.zone_by_name(zone) {
+ HandleKind::ThermalZone(tz.to_text())
+ } else {
+ return Err(Error::new(ENOENT));
+ }
+ }
+
@@ -311,0 +332,2 @@ impl SchemeSync for AcpiScheme<'_, '_> {
+ HandleKind::Dmi(ref text) => text.as_bytes(),
+ HandleKind::ThermalZone(ref text) => text.as_bytes(),
@@ -335,3 +357,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
-
- for (idx, name) in TOPLEVEL_ENTRIES
+ const TOPLEVEL_ENTRIES: &[(DirentKind, &str)] = &[
+ (DirentKind::Regular, "dmi"),
+ (DirentKind::Directory, "tables"),
+ (DirentKind::Directory, "symbols"),
+ (DirentKind::Directory, "thermal"),
+ ];
+
+ for (idx, (kind, name)) in TOPLEVEL_ENTRIES
@@ -346 +373 @@ impl SchemeSync for AcpiScheme<'_, '_> {
- kind: DirentKind::Directory,
+ kind: *kind,
@@ -393,0 +421,17 @@ impl SchemeSync for AcpiScheme<'_, '_> {
+ HandleKind::Thermal => {
+ for (idx, zone) in self
+ .ctx
+ .thermal_state
+ .zones()
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name: &zone.name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
@@ -473 +517,3 @@ impl SchemeSync for AcpiScheme<'_, '_> {
- if self.pci_fd.is_some() {
+ {
+ let mut pci_fd = self.ctx.pci_fd.write();
+ if pci_fd.is_some() {
@@ -475,2 +521,2 @@ impl SchemeSync for AcpiScheme<'_, '_> {
- } else {
- self.pci_fd = Some(new_fd);
+ }
+ *pci_fd = Some(new_fd);
+10
View File
@@ -79,6 +79,16 @@ patches = [
"P32-acpid-graceful-boot.patch",
"P33-vesad-graceful-boot.patch",
"P34-fbcond-fbbootlogd-env.patch",
"P35-fbcond-fbbootlogd-init.patch",
"P36-graphics-scheme-graceful-init.patch",
"P37-smolnetd-ready-after-init.patch",
"P38-vesad-eventqueue-deadlock.patch",
"P39-pci-allocate-interrupt-vector-graceful.patch",
"P40-bar-rs-graceful.patch",
"P41-common-init-graceful.patch",
"P42-inputd-graceful-fallback.patch",
"P43-dhcpd-requires-hard-dep.patch",
"P44-acpid-thermal-zones.patch",
]
[package]