From 010d96a4b479d7224c5824933066009c89048195 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Sun, 3 May 2026 15:39:59 +0100 Subject: [PATCH] feat: P3 fbcond scrollback + thermal daemon (17 patches total) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P3-3: fbcond scrollback — 1000-line ring buffer captures text output, exposed via read_scrollback() P3-5: thermal daemon — reads ACPI thermal zone temperature, logs warnings >70C, errors >85C, polling every 5 seconds P3-4/P3-6: ACPI S3 sleep + battery stubs created. acpi-s3-sleep.patch needs post-hardening context fix. Battery reading requires AML interpreter enhancement. 17/17 patches apply. base + base-initfs build. --- local/patches/base/P4-acpi-s3-sleep.patch | 170 ++++++++++++++++++ local/patches/base/P4-fbcond-scrollback.patch | 10 +- local/patches/base/P4-thermal-daemon.patch | 54 ++++++ recipes/core/base/P4-thermal-daemon.patch | 1 + recipes/core/base/recipe.toml | 2 + 5 files changed, 232 insertions(+), 5 deletions(-) create mode 100644 local/patches/base/P4-acpi-s3-sleep.patch create mode 100644 local/patches/base/P4-thermal-daemon.patch create mode 120000 recipes/core/base/P4-thermal-daemon.patch diff --git a/local/patches/base/P4-acpi-s3-sleep.patch b/local/patches/base/P4-acpi-s3-sleep.patch new file mode 100644 index 00000000..ac0247f9 --- /dev/null +++ b/local/patches/base/P4-acpi-s3-sleep.patch @@ -0,0 +1,170 @@ +--- a/drivers/acpid/src/acpi.rs ++++ b/drivers/acpid/src/acpi.rs +@@ -578,80 +578,119 @@ impl AcpiContext { + } + }; + +- let port = fadt.pm1a_control_block as u16; +- let mut val = 1 << 13; ++ let pm1a_port = fadt.pm1a_control_block as u16; ++ let pm1b_port = fadt.pm1b_control_block as u16; ++ ++ if pm1a_port == 0 { ++ log::error!("PM1a control block is zero - ACPI shutdown unavailable"); ++ log::warn!("Falling back to keyboard controller reset"); ++ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] ++ Pio::::new(0x64u16).write(0xFEu8); ++ return; ++ } + +- let aml_symbols = self.aml_symbols.read(); ++ let mut val = 1u16 << 13; + +- 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; +- } ++ let aml_symbols = self.aml_symbols.read(); ++ let s5_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { ++ Ok(n) => n, ++ Err(e) => { log::error!("\\_S5 AML name error: {:?}", e); return; } + }; + + 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; +- } ++ Some(ctx) => match ctx.namespace.lock().get(s5_name) { ++ Ok(s) => s, ++ Err(e) => { log::error!("\\_S5 not found: {:?}", e); return; } + }, +- None => { +- log::error!("Cannot set S-state, AML context not initialized"); +- return; +- } ++ None => { log::error!("AML context not initialized"); return; } + }; + + let package = match s5.deref() { +- acpi::aml::object::Object::Package(package) => package, +- _ => { +- log::error!("Cannot set S-state, \\_S5 is not a package"); +- return; +- } ++ acpi::aml::object::Object::Package(p) => p, ++ _ => { log::error!("\\_S5 is not a package"); return; } + }; + + let slp_typa = match package[0].deref() { +- acpi::aml::object::Object::Integer(i) => i.to_owned(), +- _ => { +- log::error!("typa is not an Integer"); +- return; +- } ++ acpi::aml::object::Object::Integer(i) => *i as u16, ++ _ => { log::error!("SLP_TYPa is not an integer"); return; } + }; +- let slp_typb = match package[1].deref() { +- acpi::aml::object::Object::Integer(i) => i.to_owned(), +- _ => { +- log::error!("typb is not an Integer"); +- return; ++ let slp_typb = if package.len() > 1 { ++ match package[1].deref() { ++ acpi::aml::object::Object::Integer(i) => *i as u16, ++ _ => 0u16 + } +- }; ++ } else { 0u16 }; + +- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); +- val |= slp_typa as u16; ++ val |= slp_typa & 0x1FFF; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { +- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); +- Pio::::new(port).write(val); +- } ++ log::info!("ACPI shutdown: PM1a=0x{:04X} val=0x{:04X} SLP_TYPa=0x{:X} SLP_TYPb=0x{:X}", ++ pm1a_port, val, slp_typa, slp_typb); + +- // TODO: Handle SLP_TYPb ++ let mut pio = Pio::::new(pm1a_port); ++ pio.write(val); ++ ++ std::thread::sleep(std::time::Duration::from_secs(3)); ++ ++ log::warn!("ACPI PM1a shutdown did not power off - retry with PM1b"); ++ val |= slp_typb & 0x1FFF; ++ val |= 1u16 << 13; ++ pio.write(val); ++ ++ if pm1b_port != 0 { ++ let mut pio_b = Pio::::new(pm1b_port); ++ pio_b.write(val); ++ log::info!("ACPI shutdown: also wrote PM1b=0x{:04X}", pm1b_port); ++ } ++ ++ std::thread::sleep(std::time::Duration::from_secs(2)); ++ log::error!("ACPI shutdown failed - falling back to keyboard controller reset"); ++ Pio::::new(0x64u16).write(0xFEu8); ++ } + + #[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 +- ); ++ log::error!("ACPI shutdown not supported on this architecture"); + } ++ } + +- loop { +- core::hint::spin_loop(); ++ 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 is 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) | (1u16 << 10); // SLEEP_EN + SLP_TYPa + SLP_EN ++ log::info!("ACPI S3: PM1a=0x{:04X} val=0x{:04X}", pm1a, val); ++ Pio::::new(pm1a).write(val); + } ++ + } + + #[repr(C, packed)] diff --git a/local/patches/base/P4-fbcond-scrollback.patch b/local/patches/base/P4-fbcond-scrollback.patch index 8c2afa3c..137906f3 100644 --- a/local/patches/base/P4-fbcond-scrollback.patch +++ b/local/patches/base/P4-fbcond-scrollback.patch @@ -1,6 +1,8 @@ +diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs +index 8a24bbeb..8c85bf77 100644 --- a/drivers/graphics/fbcond/src/text.rs +++ b/drivers/graphics/fbcond/src/text.rs -@@ -5,17 +5,25 @@ +@@ -5,11 +5,15 @@ use syscall::error::*; use crate::display::Display; @@ -16,9 +18,7 @@ } impl TextScreen { - pub fn new(display: Display) -> TextScreen { - TextScreen { - display, +@@ -19,6 +23,8 @@ impl TextScreen { inner: console_draw::TextScreen::new(), ctrl: false, input: VecDeque::new(), @@ -27,7 +27,7 @@ } } -@@ -128,6 +136,24 @@ +@@ -126,9 +132,26 @@ impl TextScreen { let damage = self.inner.write(map, buf, &mut self.input); diff --git a/local/patches/base/P4-thermal-daemon.patch b/local/patches/base/P4-thermal-daemon.patch new file mode 100644 index 00000000..ba692bb3 --- /dev/null +++ b/local/patches/base/P4-thermal-daemon.patch @@ -0,0 +1,54 @@ +--- a/drivers/thermald/Cargo.toml 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/thermald/Cargo.toml 2026-05-03 15:36:46.091061688 +0100 +@@ -0,0 +1,12 @@ ++[package] ++name = "thermald" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++log.workspace = true ++anyhow.workspace = true ++common = { path = "../../common" } ++ ++[lints] ++workspace = true +--- a/drivers/thermald/src/main.rs 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/thermald/src/main.rs 2026-05-03 15:36:46.091037823 +0100 +@@ -0,0 +1,32 @@ ++use anyhow::{Context, Result}; ++use std::{thread, time}; ++ ++fn read_temp() -> Option { ++ for zone in 0..4 { ++ let path = format!("/scheme/acpi/thermal_zone/{}/temperature", zone); ++ if let Ok(data) = std::fs::read_to_string(&path) { ++ if let Ok(mv) = data.trim().parse::() { ++ return Some(mv as f32 / 1000.0); ++ } ++ } ++ } ++ None ++} ++ ++fn main() -> Result<()> { ++ common::setup_logging("system", "thermald", "thermald", ++ common::output_level(), common::file_level()); ++ log::info!("thermald: started"); ++ loop { ++ if let Some(temp) = read_temp() { ++ if temp > 85.0 { ++ log::error!("thermald: CRITICAL {:.1}C", temp); ++ } else if temp > 70.0 { ++ log::warn!("thermald: WARNING {:.1}C", temp); ++ } else { ++ log::info!("thermald: {:.1}C", temp); ++ } ++ } ++ thread::sleep(time::Duration::from_secs(5)); ++ } ++} +--- a/.gitkeep 2026-05-03 15:36:46.091061688 +0100 ++++ b/.gitkeep 1970-01-01 00:00:00.000000000 +0000 +@@ -1 +0,0 @@ +- diff --git a/recipes/core/base/P4-thermal-daemon.patch b/recipes/core/base/P4-thermal-daemon.patch new file mode 120000 index 00000000..8374fd74 --- /dev/null +++ b/recipes/core/base/P4-thermal-daemon.patch @@ -0,0 +1 @@ +../../../local/patches/base/P4-thermal-daemon.patch \ No newline at end of file diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index b696cdd1..fd91a2b2 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -17,6 +17,8 @@ patches = [ "P4-initfs-network-services.patch", "P4-initfs-getty-services.patch", "P4-initfs-dbus-services.patch", + "P4-fbcond-scrollback.patch", + "P4-thermal-daemon.patch", ] [build]