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
This commit is contained in:
@@ -1094,6 +1094,263 @@ impl AcpiContext {
|
|||||||
Err(e) => Err(AmlEvalError::from(e)),
|
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)]
|
#[repr(C, packed)]
|
||||||
|
|||||||
+22
-11
@@ -394,7 +394,10 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
|
|
||||||
["processor", cpu_str, file] => {
|
["processor", cpu_str, file] => {
|
||||||
// /scheme/acpi/processor/<cpu>/{pss,psd,cst,cpc}
|
// /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 {
|
let kind = match *file {
|
||||||
"pss" => ProcFileKind::Pss,
|
"pss" => ProcFileKind::Pss,
|
||||||
"psd" => ProcFileKind::Psd,
|
"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 => {
|
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => {
|
||||||
return Err(Error::new(EISDIR));
|
return Err(Error::new(EISDIR));
|
||||||
}
|
}
|
||||||
HandleKind::ProcFile { .. } => {
|
HandleKind::ProcFile { cpu, kind } => {
|
||||||
// Per-CPU _PSS / _PSD / _CST / _CPC text export. The
|
let method = match kind {
|
||||||
// full AML→text conversion is a Phase G follow-up; for
|
ProcFileKind::Pss => "_PSS",
|
||||||
// now, return a placeholder line so consumers
|
ProcFileKind::Psd => "_PSD",
|
||||||
// (cpufreqd, redbear-power) can detect the path is
|
ProcFileKind::Cst => "_CST",
|
||||||
// present and report "no data" without getting ENOENT.
|
ProcFileKind::Cpc => "_CPC",
|
||||||
proc_buf = b"# ACPI processor data not yet populated\n".to_vec();
|
};
|
||||||
|
let cpu_segment = format!("CPU{}", cpu);
|
||||||
|
proc_buf = self
|
||||||
|
.ctx
|
||||||
|
.processor_method_text(&cpu_segment, method)
|
||||||
|
.into_bytes();
|
||||||
proc_buf.as_slice()
|
proc_buf.as_slice()
|
||||||
}
|
}
|
||||||
HandleKind::Tables => return Err(Error::new(EISDIR)),
|
HandleKind::Tables => return Err(Error::new(EISDIR)),
|
||||||
@@ -624,13 +632,16 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
// Enumerate \_PR.<cpu> entries from the AML namespace.
|
// Enumerate \_PR.<cpu> entries from the AML namespace.
|
||||||
// Returns Ok with no entries on systems with no
|
// Returns Ok with no entries on systems with no
|
||||||
// processors (headless QEMU with no DSDT) so consumers
|
// 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();
|
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 {
|
buf.entry(DirEntry {
|
||||||
inode: 0,
|
inode: 0,
|
||||||
next_opaque_id: idx as u64 + 1,
|
next_opaque_id: idx as u64 + 1,
|
||||||
name: cpu_name.as_str(),
|
name: short,
|
||||||
kind: DirentKind::Directory,
|
kind: DirentKind::Directory,
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user