diff --git a/src/aml/mod.rs b/src/aml/mod.rs index dc47889d..37ec1e60 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, @@ -839,18 +844,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 +961,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.clone())?; + context.contribute_arg(Argument::Object(Object::Integer(object_type).wrap())); context.retire_op(op); } Opcode::SizeOf => self.do_size_of(&mut context, op)?, @@ -1630,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. Store it directly; + * indexing the package creates a reference to the element when needed. + */ + context.contribute_arg(Argument::Object( + Object::NamePath { name, scope: context.current_scope.clone() }.wrap(), + )); } ResolveBehaviour::Placeholder => { panic!("Invalid resolve behaviour for name to be resolved!") @@ -2157,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(), @@ -2236,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(), @@ -2300,6 +2275,71 @@ where Ok(()) } + /// 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`) + Ok(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 = 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), + 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 @@ -2836,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. @@ -3410,6 +3452,11 @@ pub enum AmlError { InvalidNameSeg([u8; 4]), 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/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/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() }); 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()) + ); +} diff --git a/tests/package_name_references.rs b/tests/package_name_references.rs new file mode 100644 index 00000000..ee90e85e --- /dev/null +++ b/tests/package_name_references.rs @@ -0,0 +1,181 @@ +//! 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. + +mod test_infra; + +use crate::test_infra::{evaluate, run_aml_test}; +use acpi::aml::{ + namespace::AmlName, + object::Object, + pci_routing::{PciRoutingTable, Pin}, +}; +use aml_test_tools::handlers::null_handler::NullHandler; +use std::str::FromStr; + +/// 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 = run_aml_test(AML, NullHandler {}); + 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::NamePath { ref name, ref scope } = *gpio_ref[0] else { + panic!("GPIO reference is not a name: {}", *gpio_ref[0]); + }; + + // `^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 = run_aml_test(AML, NullHandler {}); + + let Object::Package(elements) = evaluate(&interpreter, "\\_SB.OWNR.PKG0") else { + panic!("PKG0 is not a package") + }; + assert!(matches!(*elements[0], Object::NamePath { .. })); + + // `^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 = run_aml_test(AML, NullHandler {}); + 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 = run_aml_test(AML, NullHandler {}); + let Object::String(value) = evaluate(&interpreter, "\\_SB.GET0") else { panic!("FOO is not a string") }; + assert_eq!(value, "\\_SB.FOO"); +} diff --git a/tests/test_infra/mod.rs b/tests/test_infra/mod.rs index 36ea6093..eda991bd 100644 --- a/tests/test_infra/mod.rs +++ b/tests/test_infra/mod.rs @@ -1,4 +1,7 @@ -use acpi::Handler; +use acpi::{ + Handler, + aml::{Interpreter, namespace::AmlName, object::Object}, +}; use aml_test_tools::{ RunTestResult, TestResult, @@ -7,6 +10,7 @@ use aml_test_tools::{ run_test_for_opcodes, run_test_for_string, }; +use std::str::FromStr; // The following two functions are very similar in structure, but whilst there are only two of them // it's not worth adding complexity to make them DRY. @@ -16,7 +20,7 @@ use aml_test_tools::{ /// The string `asl` represents a compile-able ASL string, so needs to include the `DefinitionBlock` /// statement. #[allow(dead_code)] -pub fn run_aml_test(asl: &'static str, handler: impl Handler) { +pub fn run_aml_test(asl: &'static str, handler: H) -> Interpreter> { // Tests calling `run_aml_test` don't do much else, and we usually want logging, so initialize it here. let _ = pretty_env_logger::try_init(); @@ -24,7 +28,16 @@ pub fn run_aml_test(asl: &'static str, handler: impl Handler) { let interpreter = new_interpreter(logged_handler); let result = run_test_for_string(asl, interpreter, &None); - assert!(matches!(result, RunTestResult::Pass(_)), "Test failed with: {:?}", TestResult::from(&result)); + match result { + RunTestResult::Pass(interpreter) => interpreter, + result => panic!("Test failed with: {:?}", TestResult::from(&result)), + } +} + +/// Evaluate an object without arguments and return its unwrapped value. +#[allow(dead_code)] +pub fn evaluate(interpreter: &Interpreter, path: &str) -> Object { + (*interpreter.evaluate(AmlName::from_str(path).unwrap(), vec![]).unwrap()).clone() } /// Run a test against a sequence of AML opcodes.