From b2eaa8d96f2c9d1773f05a9cb651021d7f36f740 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Wed, 20 May 2026 20:47:37 +0300 Subject: [PATCH] base: Add ACPI C-state discovery and thermal-based C-state policy (P52) - drivers/acpid/src/cstate.rs: Evaluate _CST per processor, parse Package-of-Packages into CStateInfo structs - AcpiContext: add cstate_state field with refresh, add processor_names() to scan _PR namespace - acpid scheme: expose /scheme/acpi/cstates/ read handles - thermald: read /scheme/sys/cstate, set /scheme/sys/cstate_policy to restrict to C1 when temp exceeds WARNING_TEMP Works with kernel P25 cpuidle deep C-states. --- local/patches/base/P52-acpid-cstates.patch | 316 +++++++++++++++++++++ recipes/core/base/recipe.toml | 2 + 2 files changed, 318 insertions(+) diff --git a/local/patches/base/P52-acpid-cstates.patch b/local/patches/base/P52-acpid-cstates.patch index e69de29bb2..991e7012f3 100644 --- a/local/patches/base/P52-acpid-cstates.patch +++ b/local/patches/base/P52-acpid-cstates.patch @@ -0,0 +1,316 @@ +diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs +index ea480bb7..db2ac003 100644 +--- a/drivers/acpid/src/acpi.rs ++++ b/drivers/acpid/src/acpi.rs +@@ -453,0 +454 @@ pub struct AcpiContext { ++ pub cstate_state: crate::cstate::CStateState, +@@ -535,0 +537 @@ impl AcpiContext { ++ cstate_state: crate::cstate::CStateState::new(), +@@ -566 +568 @@ impl AcpiContext { +- // Discover thermal zones and fan devices if AML is ready. ++ // Discover thermal zones, fan devices, and processor C-states if AML is ready. +@@ -568,0 +571 @@ impl AcpiContext { ++ this.cstate_state.refresh(&this); +@@ -690,0 +694,24 @@ impl AcpiContext { ++ /// Discover processor names by scanning the AML namespace under `\_PR`. ++ pub fn processor_names(&self) -> Result, 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("\\_PR_.") || name_str.starts_with("_PR_.") { ++ let after_prefix = if name_str.starts_with("\\_PR_.") { ++ &name_str[6..] ++ } else { ++ &name_str[5..] ++ }; ++ if !after_prefix.contains('.') { ++ names.push(after_prefix.to_string()); ++ } ++ } ++ Ok(true) ++ }); ++ Ok(names) ++ } ++ +diff --git a/drivers/acpid/src/cstate.rs b/drivers/acpid/src/cstate.rs +new file mode 100644 +index 00000000..6e2112b3 +--- /dev/null ++++ b/drivers/acpid/src/cstate.rs +@@ -0,0 +1,194 @@ ++use acpi::aml::namespace::AmlName; ++use acpi::aml::AmlError; ++use std::str::FromStr; ++use std::sync::{Arc, RwLock}; ++ ++use crate::acpi::{AcpiContext, AmlEvalError}; ++use amlserde::AmlSerdeValue; ++ ++/// A single ACPI C-state descriptor from _CST. ++#[derive(Clone, Debug)] ++pub struct CStateInfo { ++ /// C-state type: 1=C1, 2=C2, 3=C3, etc. ++ pub ctype: u64, ++ /// Worst-case latency in microseconds to enter/exit. ++ pub latency: u64, ++ /// Average power consumption in milliwatts (0xFFFFFFFF = unknown). ++ pub power: u64, ++} ++ ++impl CStateInfo { ++ fn from_package(elements: &[AmlSerdeValue]) -> Option { ++ if elements.len() < 4 { ++ return None; ++ } ++ let ctype = extract_u64(&elements[1])?; ++ let latency = extract_u64(&elements[2])?; ++ let power = extract_u64(&elements[3])?; ++ Some(Self { ++ ctype, ++ latency, ++ power, ++ }) ++ } ++} ++ ++/// C-states discovered for a single processor. ++#[derive(Clone, Debug)] ++pub struct ProcessorCStates { ++ pub name: String, ++ pub states: Vec, ++} ++ ++impl ProcessorCStates { ++ fn from_processor_eval( ++ ctx: &AcpiContext, ++ proc_name: &str, ++ ) -> Result { ++ let aml_prefix = format!("\\_PR_.{proc_name}"); ++ ++ let mut states = Vec::new(); ++ ++ if let Ok(cst_name) = AmlName::from_str(&format!("{aml_prefix}._CST")) { ++ match ctx.aml_eval(cst_name, Vec::new()) { ++ Ok(value) => { ++ if let AmlSerdeValue::Package { contents } = value { ++ if contents.len() >= 1 { ++ if let Some(count) = extract_u64(&contents[0]) { ++ let expected = count as usize; ++ for i in 1..contents.len() { ++ if let AmlSerdeValue::Package { contents: ref inner } = ++ contents[i] ++ { ++ if let Some(cstate) = CStateInfo::from_package(inner) { ++ states.push(cstate); ++ } ++ } ++ } ++ if states.len() != expected { ++ log::warn!( ++ "C-state {proc_name}: count mismatch: expected {expected}, got {}", ++ states.len() ++ ); ++ } ++ } ++ } ++ } ++ } ++ Err(e) => { ++ log::debug!("Processor {proc_name}: _CST eval failed: {e:?}"); ++ } ++ } ++ } ++ ++ Ok(Self { ++ name: proc_name.to_owned(), ++ states, ++ }) ++ } ++ ++ pub fn to_text(&self) -> String { ++ let mut s = String::new(); ++ s.push_str(&format!("name={}\n", self.name)); ++ s.push_str(&format!("count={}\n", self.states.len())); ++ for (idx, st) in self.states.iter().enumerate() { ++ s.push_str(&format!( ++ "cstate{}: type=C{} latency={}us power={}mW\n", ++ idx, st.ctype, st.latency, st.power ++ )); ++ } ++ s ++ } ++} ++ ++fn extract_u64(value: &AmlSerdeValue) -> Option { ++ match value { ++ AmlSerdeValue::Integer(i) => Some(*i as u64), ++ _ => None, ++ } ++} ++ ++#[derive(Debug)] ++pub enum CStateError { ++ AmlError(AmlError), ++ EvalError(AmlEvalError), ++ NotFound, ++} ++ ++impl From for CStateError { ++ fn from(value: AmlError) -> Self { ++ CStateError::AmlError(value) ++ } ++} ++ ++impl From for CStateError { ++ fn from(value: AmlEvalError) -> Self { ++ CStateError::EvalError(value) ++ } ++} ++ ++pub fn discover_cstates(ctx: &AcpiContext) -> Vec { ++ let mut procs = Vec::new(); ++ ++ let proc_names = match ctx.processor_names() { ++ Ok(names) => names, ++ Err(e) => { ++ log::debug!("C-state processor discovery failed: {e:?}"); ++ return procs; ++ } ++ }; ++ ++ for child_name in proc_names { ++ match ProcessorCStates::from_processor_eval(ctx, &child_name) { ++ Ok(proc_cstates) => { ++ if !proc_cstates.states.is_empty() { ++ log::info!( ++ "C-states discovered for {}: {} states", ++ proc_cstates.name, ++ proc_cstates.states.len() ++ ); ++ procs.push(proc_cstates); ++ } else { ++ log::debug!("Processor {child_name}: no C-states from _CST"); ++ } ++ } ++ Err(e) => { ++ log::warn!("Processor {child_name}: C-state discovery failed: {e:?}"); ++ } ++ } ++ } ++ ++ procs ++} ++ ++pub struct CStateState { ++ procs: RwLock>, ++} ++ ++impl CStateState { ++ pub fn new() -> Self { ++ Self { ++ procs: RwLock::new(Vec::new()), ++ } ++ } ++ ++ pub fn refresh(&self, ctx: &AcpiContext) { ++ let discovered = discover_cstates(ctx); ++ if let Ok(mut procs) = self.procs.write() { ++ *procs = discovered; ++ } ++ } ++ ++ pub fn processors(&self) -> Vec { ++ self.procs.read().map(|g| g.clone()).unwrap_or_default() ++ } ++ ++ pub fn processor_by_name(&self, name: &str) -> Option { ++ self.procs ++ .read() ++ .ok()? ++ .iter() ++ .find(|p| p.name == name) ++ .cloned() ++ } ++} +diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs +index c7b8ff3e..40b52a7b 100644 +--- a/drivers/acpid/src/main.rs ++++ b/drivers/acpid/src/main.rs +@@ -19,0 +20 @@ mod fan; ++mod cstate; +diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs +index 905b42ff..3258870b 100644 +--- a/drivers/acpid/src/scheme.rs ++++ b/drivers/acpid/src/scheme.rs +@@ -51,0 +52,2 @@ enum HandleKind<'a> { ++ Cstates, ++ CstateProcessor(String), +@@ -68,0 +71,2 @@ impl HandleKind<'_> { ++ Self::Cstates => true, ++ Self::CstateProcessor(_) => false, +@@ -86,0 +91,2 @@ impl HandleKind<'_> { ++ Self::Cstates => 0, ++ Self::CstateProcessor(ref text) => text.len(), +@@ -257,0 +264,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { ++ ["cstates"] => HandleKind::Cstates, ++ ["cstates", proc] => { ++ if let Some(p) = self.ctx.cstate_state.processor_by_name(proc) { ++ HandleKind::CstateProcessor(p.to_text()) ++ } else { ++ return Err(Error::new(ENOENT)); ++ } ++ } +@@ -348,0 +363 @@ impl SchemeSync for AcpiScheme<'_, '_> { ++ HandleKind::CstateProcessor(ref text) => text.as_bytes(), +@@ -377,0 +393 @@ impl SchemeSync for AcpiScheme<'_, '_> { ++ (DirentKind::Directory, "cstates"), +@@ -470,0 +487,17 @@ impl SchemeSync for AcpiScheme<'_, '_> { ++ HandleKind::Cstates => { ++ for (idx, proc) in self ++ .ctx ++ .cstate_state ++ .processors() ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name: &proc.name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } +diff --git a/drivers/thermald/src/main.rs b/drivers/thermald/src/main.rs +index e64cf162..2b02e4ed 100644 +--- a/drivers/thermald/src/main.rs ++++ b/drivers/thermald/src/main.rs +@@ -7,0 +8,14 @@ const WARNING_TEMP: f32 = 70.0; ++fn read_max_cstate() -> Option { ++ fs::read_to_string("/scheme/sys/cstate") ++ .ok() ++ .and_then(|s| s.trim().parse::().ok()) ++} ++ ++fn set_cstate_policy(policy: usize) { ++ if let Err(e) = fs::write("/scheme/sys/cstate_policy", policy.to_string()) { ++ log::debug!("thermald: failed to set cstate_policy={}: {}", policy, e); ++ } else { ++ log::info!("thermald: set cstate_policy={}", policy); ++ } ++} ++ +@@ -138,0 +153,8 @@ fn main() -> Result<()> { ++ if let Some(max_state) = read_max_cstate() { ++ if max_temp > WARNING_TEMP && max_state > 0 { ++ set_cstate_policy(0); ++ } else if max_temp <= WARNING_TEMP { ++ set_cstate_policy(max_state); ++ } ++ } ++ diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index d093f8d3fd..6183ef8c58 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -103,6 +103,8 @@ patches = [ "P50-structured-logging.patch", # P51: Add per-service log files and size-based rotation to logd "P51-logd-rotation.patch", + # P52: Add ACPI C-state discovery and thermal-based C-state policy + "P52-acpid-cstates.patch", ] [package]