1 Commits

Author SHA1 Message Date
Red Bear OS a0b05b1fc0 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}.<method> 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
2026-07-02 23:58:11 +03:00
2 changed files with 279 additions and 11 deletions
+257
View File
@@ -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}/<method>` 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<Option<Object>, 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<u64> {
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<u64> {
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<u64> = 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<u64> = 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<u64> = 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[{}]: <register>\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[{}]: <register>\n", idx));
found = true;
}
_ => {}
},
_ => {}
}
}
if !found {
out.push_str("# empty\n");
}
out
}
#[repr(C, packed)]
+22 -11
View File
@@ -394,7 +394,10 @@ impl SchemeSync for AcpiScheme<'_, '_> {
["processor", cpu_str, file] => {
// /scheme/acpi/processor/<cpu>/{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.<cpu> 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,
})?;
}