From a0b05b1fc03ca4ca4533a50c5e3cf248a6ad62c0 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 2 Jul 2026 23:58:11 +0300 Subject: [PATCH] acpid: implement real _CST/_PSS/_PSD/_CPC processor data readers Replace placeholder ProcFile reader with actual AML evaluation: - processor_method_text(): evaluates \_PR.CPU{n}. via AML interpreter, returns formatted text for each ACPI method type - format_pss_text(): P-state table (freq/power/latency/control/status) - format_cst_text(): C-state table (type/latency/power) - format_psd_text(): P-state dependency domains - format_cpc_text(): Continuous Performance Control descriptor dump scheme.rs changes: - open(): parse CPU{n} path format (processor/CPU0/pss) - read(): call processor_method_text() instead of placeholder string - readdir(): return short CPU segment names (CPU0) not full AML paths --- drivers/acpid/src/acpi.rs | 257 ++++++++++++++++++++++++++++++++++++ drivers/acpid/src/scheme.rs | 33 +++-- 2 files changed, 279 insertions(+), 11 deletions(-) diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index d0048009f5..b731179dda 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -1094,6 +1094,263 @@ impl AcpiContext { Err(e) => Err(AmlEvalError::from(e)), } } + + /// Evaluate a no-argument AML processor method (`_PSS`, `_CST`, `_PSD`, + /// `_CPC`) and return the result formatted as text for the + /// `/scheme/acpi/processor/CPU{n}/` interface. + /// + /// `cpu_segment` is the processor name segment (e.g. `"CPU0"`). The + /// AML path `\_PR.CPU0._PSS` is constructed internally. + /// `method` is the ACPI method name (`"_PSS"`, `"_CST"`, `"_PSD"`, + /// `"_CPC"`). + /// + /// Returns the text content. If the method is absent the result is + /// `"# not available\n"` for all methods, or `"# not supported\n"` + /// for `_CPC` (signalling that the firmware does not advertise + /// Collaborative Processor Performance Control). + pub fn processor_method_text(&self, cpu_segment: &str, method: &str) -> String { + let path = format!("\\_PR.{}.{}", cpu_segment, method); + match self.eval_processor_method(&path) { + Ok(Some(obj)) => format_processor_result(method, &obj), + Ok(None) => { + if method == "_CPC" { + "# not supported\n".to_string() + } else { + "# not available\n".to_string() + } + } + Err(e) => { + log::debug!("acpid: {} evaluation failed: {:?}", path, e); + "# not available\n".to_string() + } + } + } + + /// Internal helper: evaluate a no-argument AML method/object and return + /// the resulting [`Object`]. Uses [`Interpreter::evaluate_if_present`] so + /// that missing objects return `Ok(None)` cleanly rather than an error. + fn eval_processor_method(&self, path: &str) -> Result, AmlEvalError> { + let mut symbols = self.aml_symbols.write(); + let aml_name = AmlName::from_str(path) + .map_err(|_| AmlEvalError::DeserializationError)?; + let interpreter = symbols.aml_context_mut(None)?; + interpreter.acquire_global_lock(16)?; + + let result = interpreter.evaluate_if_present(aml_name, Vec::new()); + interpreter + .release_global_lock() + .expect("acpid: failed to release AML global lock"); + + match result { + Ok(Some(obj)) => { + let unwrapped = obj.unwrap_reference(); + Ok(Some(unwrapped.deref().clone())) + } + Ok(None) => Ok(None), + Err(e) => Err(AmlEvalError::from(e)), + } + } +} + +// --------------------------------------------------------------------------- +// Processor method text formatting +// --------------------------------------------------------------------------- + +/// Extract a `u64` integer from an AML [`Object`], following references. +fn obj_as_integer(obj: &Object) -> Option { + match obj { + Object::Integer(n) => Some(*n), + Object::Reference { inner, .. } => obj_as_integer(inner.deref()), + _ => None, + } +} + +/// Extract the inner elements of an AML `Package` [`Object`], following +/// references. Returns `None` for non-package objects. +fn obj_as_package(obj: &Object) -> Option<&[WrappedObject]> { + match obj { + Object::Package(elements) => Some(elements.as_slice()), + Object::Reference { inner, .. } => obj_as_package(inner.deref()), + _ => None, + } +} + +/// Convenience: extract an integer from a [`WrappedObject`] package element. +fn elem_as_integer(wo: &WrappedObject) -> Option { + obj_as_integer(wo.deref()) +} + +/// Convenience: extract a sub-package slice from a [`WrappedObject`] element. +fn elem_as_package(wo: &WrappedObject) -> Option<&[WrappedObject]> { + obj_as_package(wo.deref()) +} + +/// Dispatch formatting based on the ACPI method name. +fn format_processor_result(method: &str, obj: &Object) -> String { + let elements = match obj_as_package(obj) { + Some(e) => e, + None => { + log::debug!("acpid: {} returned non-package object: {}", method, obj); + return "# not available\n".to_string(); + } + }; + match method { + "_PSS" => format_pss_text(elements), + "_CST" => format_cst_text(elements), + "_PSD" => format_psd_text(elements), + "_CPC" => format_cpc_text(elements), + _ => "# not available\n".to_string(), + } +} + +/// Format `_PSS` (Performance Supported States) as text. +/// +/// `_PSS` returns a Package of sub-packages. Each sub-package contains six +/// integers: `[CoreFrequency(MHz), Power(mW), TransitionLatency(µs), +/// BusMasterLatency(µs), Control, Status]`. +/// +/// Output — one header line, then one data line per P-state: +/// ```text +/// # _PSS: core_freq_mhz power_mw trans_latency_us bus_latency_us control status +/// 3400 35 10 50 0x00001A00 0x00001A00 +/// ``` +fn format_pss_text(elements: &[WrappedObject]) -> String { + let mut out = String::from( + "# _PSS: core_freq_mhz power_mw trans_latency_us bus_latency_us control status\n", + ); + let mut count = 0; + for element in elements { + if let Some(sub) = elem_as_package(element) { + let vals: Vec = sub.iter().filter_map(elem_as_integer).collect(); + if vals.len() >= 6 { + out.push_str(&format!( + "{} {} {} {} 0x{:08X} 0x{:08X}\n", + vals[0], vals[1], vals[2], vals[3], vals[4], vals[5] + )); + count += 1; + } else { + log::debug!("_PSS sub-package has {} integers, expected >= 6", vals.len()); + } + } + } + if count == 0 { + out.push_str("# no P-states\n"); + } + out +} + +/// Format `_CST` (C-State Table) as text. +/// +/// `_CST` returns a Package whose first element is a count integer, +/// followed by that many C-state descriptor sub-packages. Each +/// sub-package contains `[CStateType, Latency(µs), Power(mW), +/// Register(Buffer), Register(Buffer)]`. +/// +/// Output — one header line, then one data line per C-state: +/// ```text +/// # _CST: type latency_us power_mw +/// 1 1 1000 +/// 2 80 500 +/// 3 1000 10 +/// ``` +fn format_cst_text(elements: &[WrappedObject]) -> String { + let mut out = String::from("# _CST: type latency_us power_mw\n"); + // Element[0] is the C-state count (an Integer). If the first element + // is an integer we skip it; otherwise we start from index 0 to + // tolerate non-standard firmware. + let start = if elements.first().map(elem_as_integer).is_some() { + 1 + } else { + 0 + }; + let mut count = 0; + for element in elements.iter().skip(start) { + if let Some(sub) = elem_as_package(element) { + let vals: Vec = sub.iter().filter_map(elem_as_integer).collect(); + if vals.len() >= 3 { + out.push_str(&format!("{} {} {}\n", vals[0], vals[1], vals[2])); + count += 1; + } + } + } + if count == 0 { + out.push_str("# no C-states\n"); + } + out +} + +/// Format `_PSD` (P-State Dependency) as text. +/// +/// `_PSD` returns a Package of dependency sub-packages. Each sub-package +/// contains five integers: `[NumEntries, Revision, Domain, CoordType, +/// NumProcessors]`. +/// +/// Output — one header line, then one data line per domain: +/// ```text +/// # _PSD: num_entries revision domain coord_type num_processors +/// 5 0 0 0xFC 8 +/// ``` +fn format_psd_text(elements: &[WrappedObject]) -> String { + let mut out = String::from( + "# _PSD: num_entries revision domain coord_type num_processors\n", + ); + let mut count = 0; + for element in elements { + if let Some(sub) = elem_as_package(element) { + let vals: Vec = sub.iter().filter_map(elem_as_integer).collect(); + if vals.len() >= 5 { + out.push_str(&format!( + "{} {} {} 0x{:02X} {}\n", + vals[0], vals[1], vals[2], vals[3], vals[4] + )); + count += 1; + } + } + } + if count == 0 { + out.push_str("# no P-state dependencies\n"); + } + out +} + +/// Format `_CPC` (Continuous Performance Control) as text. +/// +/// `_CPC` returns a Package of mixed Integer and Buffer (Generic Address +/// Structure) descriptors defined in ACPI 6.5 §8.4.7.1. The layout is +/// vendor-specific in practice, so this is a best-effort index dump: +/// integers are printed by index, buffers are noted as register +/// descriptors. +fn format_cpc_text(elements: &[WrappedObject]) -> String { + let mut out = String::from("# _CPC: continuous performance control\n"); + let mut found = false; + for (idx, element) in elements.iter().enumerate() { + match element.deref() { + Object::Integer(n) => { + out.push_str(&format!("cpc[{}]: {}\n", idx, n)); + found = true; + } + Object::Buffer(_) => { + out.push_str(&format!("cpc[{}]: \n", idx)); + found = true; + } + Object::Reference { inner, .. } => match inner.deref() { + Object::Integer(n) => { + out.push_str(&format!("cpc[{}]: {}\n", idx, n)); + found = true; + } + Object::Buffer(_) => { + out.push_str(&format!("cpc[{}]: \n", idx)); + found = true; + } + _ => {} + }, + _ => {} + } + } + if !found { + out.push_str("# empty\n"); + } + out } #[repr(C, packed)] diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs index 0222d61fcb..085bf1e92c 100644 --- a/drivers/acpid/src/scheme.rs +++ b/drivers/acpid/src/scheme.rs @@ -394,7 +394,10 @@ impl SchemeSync for AcpiScheme<'_, '_> { ["processor", cpu_str, file] => { // /scheme/acpi/processor//{pss,psd,cst,cpc} - let cpu: u32 = cpu_str.parse().map_err(|_| Error::new(EINVAL))?; + let cpu: u32 = cpu_str + .strip_prefix("CPU") + .and_then(|rest| rest.parse().ok()) + .ok_or(Error::new(EINVAL))?; let kind = match *file { "pss" => ProcFileKind::Pss, "psd" => ProcFileKind::Psd, @@ -512,13 +515,18 @@ impl SchemeSync for AcpiScheme<'_, '_> { HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => { return Err(Error::new(EISDIR)); } - HandleKind::ProcFile { .. } => { - // Per-CPU _PSS / _PSD / _CST / _CPC text export. The - // full AML→text conversion is a Phase G follow-up; for - // now, return a placeholder line so consumers - // (cpufreqd, redbear-power) can detect the path is - // present and report "no data" without getting ENOENT. - proc_buf = b"# ACPI processor data not yet populated\n".to_vec(); + HandleKind::ProcFile { cpu, kind } => { + let method = match kind { + ProcFileKind::Pss => "_PSS", + ProcFileKind::Psd => "_PSD", + ProcFileKind::Cst => "_CST", + ProcFileKind::Cpc => "_CPC", + }; + let cpu_segment = format!("CPU{}", cpu); + proc_buf = self + .ctx + .processor_method_text(&cpu_segment, method) + .into_bytes(); proc_buf.as_slice() } HandleKind::Tables => return Err(Error::new(EISDIR)), @@ -624,13 +632,16 @@ impl SchemeSync for AcpiScheme<'_, '_> { // Enumerate \_PR. entries from the AML namespace. // Returns Ok with no entries on systems with no // processors (headless QEMU with no DSDT) so consumers - // see an empty-but-existing directory. + // see an empty-but-existing directory. The directory + // entry names use the short CPU segment (e.g. "CPU0") + // so that `processor/CPU0/pss` is a valid sub-path. let cpus = self.ctx.cpu_names(); - for (idx, cpu_name) in cpus.iter().enumerate().skip(opaque_offset as usize) { + for (idx, cpu_path) in cpus.iter().enumerate().skip(opaque_offset as usize) { + let short = cpu_path.strip_prefix("\\_PR.").unwrap_or(cpu_path); buf.entry(DirEntry { inode: 0, next_opaque_id: idx as u64 + 1, - name: cpu_name.as_str(), + name: short, kind: DirentKind::Directory, })?; }