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/<proc> 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.
This commit is contained in:
@@ -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<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("\\_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<Self> {
|
||||
+ 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<CStateInfo>,
|
||||
+}
|
||||
+
|
||||
+impl ProcessorCStates {
|
||||
+ fn from_processor_eval(
|
||||
+ ctx: &AcpiContext,
|
||||
+ proc_name: &str,
|
||||
+ ) -> Result<Self, CStateError> {
|
||||
+ 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<u64> {
|
||||
+ match value {
|
||||
+ AmlSerdeValue::Integer(i) => Some(*i as u64),
|
||||
+ _ => None,
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+#[derive(Debug)]
|
||||
+pub enum CStateError {
|
||||
+ AmlError(AmlError),
|
||||
+ EvalError(AmlEvalError),
|
||||
+ NotFound,
|
||||
+}
|
||||
+
|
||||
+impl From<AmlError> for CStateError {
|
||||
+ fn from(value: AmlError) -> Self {
|
||||
+ CStateError::AmlError(value)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl From<AmlEvalError> for CStateError {
|
||||
+ fn from(value: AmlEvalError) -> Self {
|
||||
+ CStateError::EvalError(value)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+pub fn discover_cstates(ctx: &AcpiContext) -> Vec<ProcessorCStates> {
|
||||
+ 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<Vec<ProcessorCStates>>,
|
||||
+}
|
||||
+
|
||||
+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<ProcessorCStates> {
|
||||
+ self.procs.read().map(|g| g.clone()).unwrap_or_default()
|
||||
+ }
|
||||
+
|
||||
+ pub fn processor_by_name(&self, name: &str) -> Option<ProcessorCStates> {
|
||||
+ 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<usize> {
|
||||
+ fs::read_to_string("/scheme/sys/cstate")
|
||||
+ .ok()
|
||||
+ .and_then(|s| s.trim().parse::<usize>().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);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user