From 814ab9e867628530b736eff0020b4ba6921ebcd1 Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Thu, 9 Jul 2026 14:08:22 +0200 Subject: [PATCH 1/4] aml: return errors for relative namespace paths Namespace entry points require absolute AML names, but some callers can pass relative names from firmware input. Return an error instead of asserting so bad paths propagate as normal interpreter errors. Add `AmlName::require_absolute` and `AmlName::normalize_absolute`, so the check cannot be forgotten by a new entry point that normalizes a path, and a dedicated `AmlError::NameNotAbsolute` variant: `InvalidNormalizedName` means normalization itself failed, which is a different condition. Note that this changes the contract of several public functions from panicking to returning an error. Cover namespace insertion, removal, lookup, alias creation, both scope searches and `AmlName::resolve` with regression tests for relative inputs. --- src/aml/mod.rs | 2 ++ src/aml/namespace.rs | 41 +++++++++++++++++---------- tests/namespace_paths.rs | 61 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 tests/namespace_paths.rs diff --git a/src/aml/mod.rs b/src/aml/mod.rs index dc47889d..bf3af70d 100644 --- a/src/aml/mod.rs +++ b/src/aml/mod.rs @@ -3410,6 +3410,8 @@ pub enum AmlError { InvalidNameSeg([u8; 4]), InvalidNormalizedName(AmlName), + /// An absolute name was required, but a relative one was supplied. + NameNotAbsolute(AmlName), RootHasNoParent, EmptyNamesAreInvalid, LevelDoesNotExist(AmlName), diff --git a/src/aml/namespace.rs b/src/aml/namespace.rs index c887bcac..faeb1023 100644 --- a/src/aml/namespace.rs +++ b/src/aml/namespace.rs @@ -137,8 +137,7 @@ impl Namespace { } pub fn add_level(&mut self, path: AmlName, kind: NamespaceLevelKind) -> Result<(), AmlError> { - assert!(path.is_absolute()); - let path = path.normalize()?; + let path = path.normalize_absolute()?; // Don't try to recreate the root scope if path != AmlName::root() { @@ -155,8 +154,7 @@ impl Namespace { } pub fn remove_level(&mut self, path: AmlName) -> Result<(), AmlError> { - assert!(path.is_absolute()); - let path = path.normalize()?; + let path = path.normalize_absolute()?; // Don't try to remove the root scope // TODO: we probably shouldn't be able to remove the pre-defined scopes either? @@ -169,8 +167,7 @@ impl Namespace { } pub fn insert(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> { - assert!(path.is_absolute()); - let path = path.normalize()?; + let path = path.normalize_absolute()?; let (level, last_seg) = self.get_level_for_path_mut(&path)?; match level.values.insert(last_seg, (ObjectFlags::new(false), object)) { @@ -187,8 +184,7 @@ impl Namespace { } pub fn create_alias(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> { - assert!(path.is_absolute()); - let path = path.normalize()?; + let path = path.normalize_absolute()?; let (level, last_seg) = self.get_level_for_path_mut(&path)?; match level.values.insert(last_seg, (ObjectFlags::new(true), object)) { @@ -198,8 +194,7 @@ impl Namespace { } pub fn get(&mut self, path: AmlName) -> Result { - assert!(path.is_absolute()); - let path = path.normalize()?; + let path = path.normalize_absolute()?; let (level, last_seg) = self.get_level_for_path_mut(&path)?; match level.values.get(&last_seg) { @@ -210,8 +205,10 @@ impl Namespace { /// Search for an object at the given path of the namespace, applying the search rules described in §5.3 of the /// ACPI specification, if they are applicable. Returns the resolved name, and the handle of the first valid - /// object, if found. + /// object, if found. Errors if `starting_scope` is not absolute. pub fn search(&self, path: &AmlName, starting_scope: &AmlName) -> Result<(AmlName, WrappedObject), AmlError> { + starting_scope.require_absolute()?; + if path.search_rules_apply() { /* * If search rules apply, we need to recursively look through the namespace. If the @@ -219,7 +216,6 @@ impl Namespace { * we either find the name, or reach the root of the namespace. */ let mut scope = starting_scope.clone(); - assert!(scope.is_absolute()); loop { // Search for the name at this namespace level. If we find it, we're done. let name = path.resolve(&scope)?; @@ -254,9 +250,10 @@ impl Namespace { } pub fn search_for_level(&self, level_name: &AmlName, starting_scope: &AmlName) -> Result { + starting_scope.require_absolute()?; + if level_name.search_rules_apply() { let mut scope = starting_scope.clone().normalize()?; - assert!(scope.is_absolute()); loop { let name = level_name.resolve(&scope)?; @@ -554,10 +551,24 @@ impl AmlName { } } + /// Returns `AmlError::NameNotAbsolute` if this path is not absolute. Relative paths can reach + /// entry points that require absolute ones from firmware input, so this is a normal error + /// rather than a panic. + pub fn require_absolute(&self) -> Result<(), AmlError> { + if self.is_absolute() { Ok(()) } else { Err(AmlError::NameNotAbsolute(self.clone())) } + } + + /// Normalize an AML path that is required to be absolute. Prefer this over `normalize` where + /// an absolute path is required. + pub fn normalize_absolute(self) -> Result { + self.require_absolute()?; + self.normalize() + } + /// Resolve this path against a given scope, making it absolute. If the path is absolute, it is - /// returned directly. The path is also normalized. + /// returned directly. The path is also normalized. Errors if `scope` is not absolute. pub fn resolve(&self, scope: &AmlName) -> Result { - assert!(scope.is_absolute()); + scope.require_absolute()?; if self.is_absolute() { return Ok(self.clone()); diff --git a/tests/namespace_paths.rs b/tests/namespace_paths.rs new file mode 100644 index 00000000..d27d1f64 --- /dev/null +++ b/tests/namespace_paths.rs @@ -0,0 +1,61 @@ +use std::str::FromStr; + +use acpi::{ + Handle, + aml::{ + AmlError, + namespace::{AmlName, Namespace, NamespaceLevelKind}, + object::Object, + }, +}; + +#[test] +fn namespace_rejects_relative_paths_without_panicking() { + let mut namespace = Namespace::new(Handle(0)); + let relative = AmlName::from_str("_STA").unwrap(); + let expected = Err(AmlError::NameNotAbsolute(relative.clone())); + + assert_eq!(namespace.add_level(relative.clone(), NamespaceLevelKind::Scope), expected); + assert_eq!(namespace.remove_level(relative.clone()), expected); + assert_eq!(namespace.insert(relative.clone(), Object::Integer(0).wrap()), expected); + assert_eq!(namespace.create_alias(relative.clone(), Object::Integer(0).wrap()), expected); + assert_eq!(namespace.get(relative.clone()).err(), Some(AmlError::NameNotAbsolute(relative))); +} + +#[test] +fn namespace_searches_reject_relative_starting_scopes_without_panicking() { + let namespace = Namespace::new(Handle(0)); + let name = AmlName::from_str("_STA").unwrap(); + let relative_scope = AmlName::from_str("DEV0").unwrap(); + let expected = AmlError::NameNotAbsolute(relative_scope.clone()); + + assert_eq!(namespace.search(&name, &relative_scope).map(|(name, _)| name), Err(expected.clone())); + assert_eq!(namespace.search_for_level(&name, &relative_scope), Err(expected)); +} + +#[test] +fn relative_name_resolution_rejects_relative_scope_without_panicking() { + let relative_name = AmlName::from_str("_STA").unwrap(); + let relative_scope = AmlName::from_str("DEV0").unwrap(); + + assert_eq!(relative_name.resolve(&relative_scope), Err(AmlError::NameNotAbsolute(relative_scope.clone()))); + + // An absolute name still requires an absolute scope, as the scope is validated first. + let absolute_name = AmlName::from_str("\\_SB.DEV0._STA").unwrap(); + assert_eq!(absolute_name.resolve(&relative_scope), Err(AmlError::NameNotAbsolute(relative_scope))); +} + +#[test] +fn absolute_paths_are_still_accepted() { + let mut namespace = Namespace::new(Handle(0)); + let absolute = AmlName::from_str("\\DEV0").unwrap(); + + namespace.insert(absolute.clone(), Object::Integer(7).wrap()).unwrap(); + assert!(matches!(*namespace.get(absolute).unwrap(), Object::Integer(7))); + + let name = AmlName::from_str("_STA").unwrap(); + assert_eq!( + name.resolve(&AmlName::from_str("\\_SB.DEV0").unwrap()), + Ok(AmlName::from_str("\\_SB.DEV0._STA").unwrap()) + ); +} From 1610a0d7c6bbe8e990cb1707a986153a009a24e9 Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Mon, 27 Jul 2026 11:18:55 +0200 Subject: [PATCH 2/4] aml: extract `ObjectType` and `DerefOf` handling into methods Pure refactor with no functional change: move the bodies of the `ObjectType` and `DerefOf` opcode arms out of the main interpreter loop, so the following commit can extend them without further growing the loop. --- src/aml/mod.rs | 92 ++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/src/aml/mod.rs b/src/aml/mod.rs index bf3af70d..fb79df67 100644 --- a/src/aml/mod.rs +++ b/src/aml/mod.rs @@ -839,18 +839,7 @@ where } Opcode::DerefOf => { extract_args!(op => [Argument::Object(object)]); - let object = object.clone().unwrap_reference(); - let result = match &*object { - Object::BufferField { .. } => object.read_buffer_field(self.integer_size)?.wrap(), - Object::FieldUnit(field) => self.do_field_read(field)?, - Object::String(path) => { - let path = AmlName::from_str(path)?; - let (_, object) = self.namespace.lock().search(&path, &context.current_scope)?; - object.clone() - } - Object::Reference { .. } => unreachable!(), - _ => object, - }; + let result = self.do_deref_of(object.clone(), &context.current_scope)?; context.contribute_arg(Argument::Object(result)); context.retire_op(op); } @@ -967,38 +956,8 @@ where } Opcode::ObjectType => { extract_args!(op => [Argument::Object(object)]); - - // TODO: this should technically support scopes as well - this is less easy - // (they should return `0`) - fn object_type(object: &Object) -> u64 { - if let Object::Reference { kind: _, inner } = object { - object_type(&inner) - } else { - match object.typ() { - ObjectType::Uninitialized => 0, - ObjectType::Integer => 1, - ObjectType::String => 2, - ObjectType::Buffer => 3, - ObjectType::Package => 4, - ObjectType::FieldUnit => 5, - ObjectType::Device => 6, - ObjectType::Event => 7, - ObjectType::Method => 8, - ObjectType::Mutex => 9, - ObjectType::OpRegion => 10, - ObjectType::PowerResource => 11, - ObjectType::Processor => 12, - ObjectType::ThermalZone => 13, - ObjectType::BufferField => 14, - // XXX: 15 is reserved - ObjectType::Debug => 16, - ObjectType::RawDataBuffer => 17, - ObjectType::Reference => unreachable!(), - } - } - } - - context.contribute_arg(Argument::Object(Object::Integer(object_type(&object)).wrap())); + let object_type = self.object_type(object); + context.contribute_arg(Argument::Object(Object::Integer(object_type).wrap())); context.retire_op(op); } Opcode::SizeOf => self.do_size_of(&mut context, op)?, @@ -2300,6 +2259,51 @@ where Ok(()) } + fn object_type(&self, object: &Object) -> u64 { + if let Object::Reference { kind: _, inner } = object { + return self.object_type(inner); + } + + // TODO: this should technically support scopes as well - this is less easy + // (they should return `0`) + match object.typ() { + ObjectType::Uninitialized => 0, + ObjectType::Integer => 1, + ObjectType::String => 2, + ObjectType::Buffer => 3, + ObjectType::Package => 4, + ObjectType::FieldUnit => 5, + ObjectType::Device => 6, + ObjectType::Event => 7, + ObjectType::Method => 8, + ObjectType::Mutex => 9, + ObjectType::OpRegion => 10, + ObjectType::PowerResource => 11, + ObjectType::Processor => 12, + ObjectType::ThermalZone => 13, + ObjectType::BufferField => 14, + // XXX: 15 is reserved + ObjectType::Debug => 16, + ObjectType::RawDataBuffer => 17, + ObjectType::Reference => unreachable!(), + } + } + + fn do_deref_of(&self, object: WrappedObject, current_scope: &AmlName) -> Result { + let object = object.unwrap_reference(); + match &*object { + Object::BufferField { .. } => Ok(object.read_buffer_field(self.integer_size)?.wrap()), + Object::FieldUnit(field) => self.do_field_read(field), + Object::String(path) => { + let path = AmlName::from_str(path)?; + let (_, object) = self.namespace.lock().search(&path, current_scope)?; + Ok(object.clone()) + } + Object::Reference { .. } => unreachable!(), + _ => Ok(object), + } + } + /// Perform a store of `object` into `target`, matching the expected behaviour of `DefStore`, /// which depends on the target: /// - Locals are overwritten, unless they contain a reference, in which case a store is From d2d3b68fc6faa3a749de4ef656f371bf80497440 Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Mon, 27 Jul 2026 11:22:51 +0200 Subject: [PATCH 3/4] aml: resolve package name references lazily Package NameStrings can refer to objects that are defined later, or in another table. Represent such an element as a reference to a new `Object::NamePath`, which retains the name and the scope it was declared in, and resolve it when the reference is used. Resolution is then independent of the order tables are loaded in, and of the scope the package is used from. Resolution goes through a single `resolve_name_path` helper, used by `DerefOf`, `ObjectType` and `SizeOf`, which follows a bounded number of indirections so a malformed table cannot make us loop. The `DerefOf` of a string stays a single namespace lookup for the same reason. Package elements were previously `String` objects, so also decode the `Source` field of a `_PRT` entry from a name reference. Strings are still accepted there, as some tables store the path as one. --- src/aml/mod.rs | 65 ++++++++++++++++++++++++++++++++++-------- src/aml/object.rs | 10 ++++--- src/aml/pci_routing.rs | 49 +++++++++++++++++++++---------- 3 files changed, 93 insertions(+), 31 deletions(-) diff --git a/src/aml/mod.rs b/src/aml/mod.rs index fb79df67..882823d0 100644 --- a/src/aml/mod.rs +++ b/src/aml/mod.rs @@ -119,6 +119,11 @@ unsafe impl Sync for Interpreter where H: Handler + Send {} /// The value returned by the `Revision` opcode. const INTERPRETER_REVISION: u64 = 1; +/// The maximum number of times a name used as a package element may resolve to another such name +/// before we give up. Real firmware doesn't chain these at all, but broken tables must not be able +/// to make us loop forever. +const MAX_NAME_PATH_INDIRECTIONS: usize = 8; + impl Interpreter where H: Handler, @@ -956,7 +961,7 @@ where } Opcode::ObjectType => { extract_args!(op => [Argument::Object(object)]); - let object_type = self.object_type(object); + let object_type = self.object_type(object.clone())?; context.contribute_arg(Argument::Object(Object::Integer(object_type).wrap())); context.retire_op(op); } @@ -1589,7 +1594,16 @@ where } } ResolveBehaviour::AsPackageElements => { - context.contribute_arg(Argument::Object(Object::String(name.to_string()).wrap())); + /* + * NameStrings in packages can refer to objects that are defined later, + * including ones in another table. Keep the name and the scope it + * appeared in, so resolution doesn't depend on table load order, or on + * the scope the package is eventually used from. + */ + let name_path = Object::NamePath { name, scope: context.current_scope.clone() }.wrap(); + context.contribute_arg(Argument::Object( + Object::Reference { kind: ReferenceKind::RefOf, inner: name_path }.wrap(), + )); } ResolveBehaviour::Placeholder => { panic!("Invalid resolve behaviour for name to be resolved!") @@ -2116,6 +2130,8 @@ where Object::Method { .. } | Object::NativeMethod { .. } => "[Control Method]".to_string(), Object::Mutex { .. } => "[Mutex]".to_string(), Object::Reference { inner, .. } => resolve_as_string(&(inner.clone().unwrap_reference())), + // We can't resolve the name here, as we don't have access to the namespace + Object::NamePath { name, .. } => name.to_string(), Object::OpRegion(_) => "[Operation Region]".to_string(), Object::Package(_) => "[Package]".to_string(), Object::PowerResource { .. } => "[Power Resource]".to_string(), @@ -2195,7 +2211,7 @@ where fn do_size_of(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { extract_args!(op => [Argument::Object(object)]); - let object = object.clone().unwrap_reference(); + let object = self.resolve_name_path(object.clone())?; let result = match *object { Object::Buffer(ref buffer) => buffer.len(), @@ -2259,14 +2275,34 @@ where Ok(()) } - fn object_type(&self, object: &Object) -> u64 { - if let Object::Reference { kind: _, inner } = object { - return self.object_type(inner); + /// Resolve an object to the value it refers to, looking up `NamePath`s (i.e. names that were + /// used as package elements) in the namespace. Objects that aren't references are returned + /// unchanged. + fn resolve_name_path(&self, object: WrappedObject) -> Result { + let mut object = object.unwrap_reference(); + + /* + * A name can resolve to another unresolved name, and firmware is free to make that + * circular. Bound the number of indirections we follow rather than recursing. + */ + for _ in 0..MAX_NAME_PATH_INDIRECTIONS { + let (name, scope) = match *object { + Object::NamePath { ref name, ref scope } => (name.clone(), scope.clone()), + _ => return Ok(object), + }; + let (_, resolved) = self.namespace.lock().search(&name, &scope)?; + object = resolved.unwrap_reference(); } + Err(AmlError::NameResolutionLoop) + } + + fn object_type(&self, object: WrappedObject) -> Result { + let object = self.resolve_name_path(object)?; + // TODO: this should technically support scopes as well - this is less easy // (they should return `0`) - match object.typ() { + Ok(match object.typ() { ObjectType::Uninitialized => 0, ObjectType::Integer => 1, ObjectType::String => 2, @@ -2286,11 +2322,11 @@ where ObjectType::Debug => 16, ObjectType::RawDataBuffer => 17, ObjectType::Reference => unreachable!(), - } + }) } fn do_deref_of(&self, object: WrappedObject, current_scope: &AmlName) -> Result { - let object = object.unwrap_reference(); + let object = self.resolve_name_path(object)?; match &*object { Object::BufferField { .. } => Ok(object.read_buffer_field(self.integer_size)?.wrap()), Object::FieldUnit(field) => self.do_field_read(field), @@ -2840,9 +2876,11 @@ enum ResolveBehaviour { /// `SuperName`, but can also be `NullName` Target, /// Surrogate argument, used by `DefPackage` and `DefVarPackage`. Only one of these is emitted, - /// but represents parsing of potentially many package elements. Names in packages should be - /// resolved into `String` objects - this is not well defined by the specification, but matches - /// expected behaviour of other interpreters. + /// but represents parsing of potentially many package elements. Names in packages refer to the + /// named object (see `Package` in the ASL reference, §19.6.101 of ACPI 6.5). We keep the name + /// and the scope it appeared in, and only resolve it when the reference is used, as the object + /// may not exist yet. ACPICA resolves these at package creation, but that makes resolution + /// depend on the order tables are loaded in. AsPackageElements, /// Used with [`OpInFlight::new_with`] to represent arguments that have already been resolved /// when an operation enters flight. @@ -3416,6 +3454,9 @@ pub enum AmlError { InvalidNormalizedName(AmlName), /// An absolute name was required, but a relative one was supplied. NameNotAbsolute(AmlName), + /// A name in a package resolved to another unresolved name too many times, probably because + /// the names form a cycle. + NameResolutionLoop, RootHasNoParent, EmptyNamesAreInvalid, LevelDoesNotExist(AmlName), diff --git a/src/aml/object.rs b/src/aml/object.rs index 780d8cda..bfc03b41 100644 --- a/src/aml/object.rs +++ b/src/aml/object.rs @@ -1,4 +1,4 @@ -use crate::aml::{AmlError, Handle, IntegerSize, Operation, op_region::OpRegion}; +use crate::aml::{AmlError, Handle, IntegerSize, Operation, namespace::AmlName, op_region::OpRegion}; use alloc::{ borrow::Cow, string::{String, ToString}, @@ -23,6 +23,7 @@ pub enum Object { NativeMethod { f: Arc, flags: MethodFlags }, Mutex { mutex: Handle, sync_level: u8 }, Reference { kind: ReferenceKind, inner: WrappedObject }, + NamePath { name: AmlName, scope: AmlName }, OpRegion(OpRegion), Package(Vec), PowerResource { system_level: u8, resource_order: u16 }, @@ -62,6 +63,7 @@ impl fmt::Display for Object { Object::NativeMethod { .. } => write!(f, "NativeMethod"), Object::Mutex { .. } => write!(f, "Mutex"), Object::Reference { kind, inner } => write!(f, "Reference({:?} -> {})", kind, **inner), + Object::NamePath { name, scope } => write!(f, "NamePath({name} from {scope})"), Object::OpRegion(region) => write!(f, "{region:?}"), Object::Package(elements) => { write!(f, "Package {{ ")?; @@ -354,9 +356,9 @@ impl Object { Object::NativeMethod { .. } => ObjectType::Method, Object::Mutex { .. } => ObjectType::Mutex, // Object::Reference { inner, .. } => inner.typ(), - Object::Reference { .. } => ObjectType::Reference, // TODO: maybe this should - // differentiate internal/real - // references? + // TODO: maybe this should differentiate internal/real references? + // An unresolved package name also behaves as a reference until dereferenced. + Object::Reference { .. } | Object::NamePath { .. } => ObjectType::Reference, Object::OpRegion(_) => ObjectType::OpRegion, Object::Package(_) => ObjectType::Package, Object::PowerResource { .. } => ObjectType::PowerResource, diff --git a/src/aml/pci_routing.rs b/src/aml/pci_routing.rs index 475ee9d6..5b3d037e 100644 --- a/src/aml/pci_routing.rs +++ b/src/aml/pci_routing.rs @@ -105,8 +105,40 @@ impl PciRoutingTable { _ => return Err(AmlError::PrtInvalidPin), }; - match *pin_package[2] { - Object::Integer(0) => { + /* + * A `NamePath` source is a reference to a name that we haven't resolved yet. We + * also accept a string, as that is how names in packages used to be + * represented, and some tables store the path as a string themselves. + */ + let source = pin_package[2].clone().unwrap_reference(); + let link_object_name = match *source { + /* + * A `Source` of a single NameSeg is subject to the namespace search rules, + * so search from the scope the name appeared in, rather than resolving it. + */ + Object::NamePath { ref name, ref scope } => { + Some(interpreter.namespace.lock().search_for_level(name, scope)?) + } + Object::String(ref name) => Some( + interpreter.namespace.lock().search_for_level(&AmlName::from_str(name)?, &prt_path)?, + ), + _ => None, + }; + + match link_object_name { + Some(link_object_name) => { + entries.push(PciRoute { + device, + function, + pin, + route_type: PciRouteType::LinkObject(link_object_name), + }); + } + None => { + if !matches!(*source, Object::Integer(0)) { + return Err(AmlError::PrtInvalidSource); + } + /* * The Source Index field contains the GSI number that this interrupt is attached * to. @@ -121,19 +153,6 @@ impl PciRoutingTable { route_type: PciRouteType::Gsi(gsi as u32), }); } - Object::String(ref name) => { - let link_object_name = interpreter - .namespace - .lock() - .search_for_level(&AmlName::from_str(name)?, &prt_path)?; - entries.push(PciRoute { - device, - function, - pin, - route_type: PciRouteType::LinkObject(link_object_name), - }); - } - _ => return Err(AmlError::PrtInvalidSource), } } else { return Err(AmlError::InvalidOperationOnObject { op: Operation::DecodePrt, typ: value.typ() }); From 1ea5a0604e393c2d4e785b6bd210a58c039a269b Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Mon, 27 Jul 2026 11:22:51 +0200 Subject: [PATCH 4/4] aml: test package name references Cover the AMD Rhino gpio-leds _DSD that motivated the previous commit, a forward reference consumed from another scope, _PRT sources naming a link device both absolutely and by search rules, and that dereferencing a self-referential string terminates. --- tests/package_name_references.rs | 197 +++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 tests/package_name_references.rs diff --git a/tests/package_name_references.rs b/tests/package_name_references.rs new file mode 100644 index 00000000..98619133 --- /dev/null +++ b/tests/package_name_references.rs @@ -0,0 +1,197 @@ +//! Tests for `NameString`s used as package elements, which are references to the named object and +//! are resolved lazily, as they may refer to objects that don't exist yet. + +use acpi::{ + Handler, + aml::{ + Interpreter, + namespace::AmlName, + object::{Object, ReferenceKind}, + pci_routing::{PciRoutingTable, Pin}, + }, +}; +use aml_test_tools::{RunTestResult, handlers::null_handler::NullHandler, new_interpreter, run_test_for_string}; +use std::str::FromStr; + +fn interpret(asl: &'static str) -> Interpreter { + let result = run_test_for_string(asl, new_interpreter(NullHandler {}), &None); + let RunTestResult::Pass(interpreter) = result else { + panic!("AML test failed"); + }; + interpreter +} + +fn evaluate(interpreter: &Interpreter, path: &str) -> Object { + (*interpreter.evaluate(AmlName::from_str(path).unwrap(), vec![]).unwrap()).clone() +} + +/// A cut-down version of the `gpio-leds` `_DSD` from the AMD Rhino, which refers to the device the +/// package is declared in with a relative name. +#[test] +fn relative_name_in_package_refers_to_declaration_scope() { + const AML: &str = r#" +DefinitionBlock("", "DSDT", 2, "COREBT", "RHINO", 1) { + Scope (\_SB) { + Device (LEDS) { + Name (_HID, "PRP0001") + Name (_UID, 0x05) + Name (_DDN, "gpio-leds APU HeartBeat") + Name (_DSD, Package () { + ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), + Package () { + Package () {"compatible", Package () {"gpio-leds"}}, + }, + ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), + Package () { + Package () {"led-0", "LED0"}, + } + }) + Name (LED0, Package () { + ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), + Package () { + Package () {"label", "heartbeat"}, + Package () {"default-state", "on"}, + Package () {"linux,default-trigger", "heartbeat"}, + Package () {"gpios", Package () {^LEDS, 0, 0, 1}}, + Package () {"retain-state-suspended", 1}, + } + }) + } + } +} +"#; + + let interpreter = interpret(AML); + let led = evaluate(&interpreter, "\\_SB.LEDS.LED0"); + + let Object::Package(top_level) = led else { panic!("LED0 is not a package") }; + let Object::Package(ref properties) = *top_level[1] else { panic!("LED0 properties are not a package") }; + let Object::Package(ref gpio_property) = *properties[3] else { panic!("GPIO property is not a package") }; + let Object::Package(ref gpio_ref) = *gpio_property[1] else { panic!("GPIO reference is not a package") }; + + let Object::Reference { kind: ReferenceKind::RefOf, ref inner } = *gpio_ref[0] else { + panic!("GPIO reference is not a reference: {}", *gpio_ref[0]); + }; + let Object::NamePath { ref name, ref scope } = **inner else { + panic!("GPIO reference does not refer to a name: {}", **inner); + }; + + // `^LEDS`, declared in `\_SB.LEDS`, refers to the device itself. + assert_eq!(name.resolve(scope), Ok(AmlName::from_str("\\_SB.LEDS").unwrap())); +} + +/// Names in packages may refer to objects that don't exist when the package is created, and must be +/// resolved against the scope the package was declared in, not the one it is used from. +#[test] +fn forward_reference_in_package_uses_declaration_scope() { + const AML: &str = r#" +DefinitionBlock("", "DSDT", 2, "RSACPI", "PKGREF", 1) { + Scope (\_SB) { + Device (OWNR) { + Name (_HID, "TEST0001") + Name (PKG0, Package () { ^LATE }) + } + + Device (LATE) { + Name (_HID, "TEST0002") + } + + Device (USER) { + Name (_HID, "TEST0003") + + Device (SUB0) { + Name (_HID, "TEST0004") + + Method (GET0, 0, NotSerialized) { + Return (DerefOf (\_SB.OWNR.PKG0[0])) + } + + Method (TYP0, 0, NotSerialized) { + Return (ObjectType (\_SB.OWNR.PKG0[0])) + } + + Method (SIZ0, 0, NotSerialized) { + Return (SizeOf (\_SB.OWNR.PKG0[0])) + } + } + } + } +} +"#; + + let interpreter = interpret(AML); + + let Object::Package(elements) = evaluate(&interpreter, "\\_SB.OWNR.PKG0") else { + panic!("PKG0 is not a package") + }; + assert!(matches!(*elements[0], Object::Reference { kind: ReferenceKind::RefOf, .. })); + + // `^LATE` is declared after the package that refers to it, and is used from another scope. + assert!(matches!(evaluate(&interpreter, "\\_SB.USER.SUB0.GET0"), Object::Device)); + // `ObjectType` dereferences its operand, so this is a device (`6`), not a reference. + assert!(matches!(evaluate(&interpreter, "\\_SB.USER.SUB0.TYP0"), Object::Integer(6))); + // `SizeOf` also dereferences, and a device has no size. + assert!(interpreter.evaluate(AmlName::from_str("\\_SB.USER.SUB0.SIZ0").unwrap(), vec![]).is_err()); +} + +/// Names in `_PRT` `Source` fields are package elements, and so are references to the link device. +#[test] +fn prt_source_name_is_resolved_to_a_link_object() { + const AML: &str = r#" +DefinitionBlock("", "DSDT", 2, "RSACPI", "PRTTST", 1) { + Scope (\_SB) { + Device (LNKA) { + Name (_HID, EisaId ("PNP0C0F")) + Name (_UID, 1) + Name (_CRS, ResourceTemplate () { IRQ (Level, ActiveLow, Shared) {11} }) + } + + Device (LNKB) { + Name (_HID, EisaId ("PNP0C0F")) + Name (_UID, 2) + Name (_CRS, ResourceTemplate () { IRQ (Level, ActiveLow, Shared) {10} }) + } + + Device (PCI0) { + Name (_HID, EisaId ("PNP0A03")) + Name (_PRT, Package () { + Package () { 0x0001FFFF, 0, \_SB.LNKA, 0 }, + Package () { 0x0002FFFF, 1, LNKB, 0 }, + Package () { 0x0003FFFF, 2, 0, 19 }, + }) + } + } +} +"#; + + let interpreter = interpret(AML); + let table = + PciRoutingTable::from_prt_path(AmlName::from_str("\\_SB.PCI0._PRT").unwrap(), &interpreter).unwrap(); + + // An absolute name, a relative name found by the namespace search rules, and a GSI. + // The IRQ of a link object is decoded from its `_CRS` as a mask. + assert_eq!(table.route(1, 0, Pin::IntA, &interpreter).unwrap().irq, 1 << 11); + assert_eq!(table.route(2, 0, Pin::IntB, &interpreter).unwrap().irq, 1 << 10); + assert_eq!(table.route(3, 0, Pin::IntC, &interpreter).unwrap().irq, 19); +} + +/// `DerefOf` of a string is a namespace lookup, and must not recurse into the object it finds - +/// otherwise firmware can make us loop forever. +#[test] +fn deref_of_self_referential_string_terminates() { + const AML: &str = r#" +DefinitionBlock("", "DSDT", 2, "RSACPI", "DRFTST", 1) { + Scope (\_SB) { + Name (FOO, "\\_SB.FOO") + + Method (GET0, 0, NotSerialized) { + Return (DerefOf (FOO)) + } + } +} +"#; + + let interpreter = interpret(AML); + let Object::String(value) = evaluate(&interpreter, "\\_SB.GET0") else { panic!("FOO is not a string") }; + assert_eq!(value, "\\_SB.FOO"); +}