Compare commits
1 Commits
25a988a15d
...
a0b05b1fc0
| Author | SHA1 | Date | |
|---|---|---|---|
| a0b05b1fc0 |
@@ -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
@@ -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,
|
||||
})?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user