Browse Source

Implement search rules

In certain cases, special search rules apply to looking up AML names in the
namespace. I think they're used simply to reduce the size of the encoded
AML by leaving off prefix chars etc., at our expense.
Isaac Woods 5 years ago
parent
commit
d9b30d2d93
2 changed files with 118 additions and 8 deletions
  1. 35 7
      aml_parser/src/lib.rs
  2. 83 1
      aml_parser/src/name_object.rs

+ 35 - 7
aml_parser/src/lib.rs

@@ -155,7 +155,8 @@ impl AmlContext {
     }
 
     /// Resolves a given path relative to the current scope (if the given path is not absolute).
-    /// The returned path can be used to index the namespace.
+    /// If you want to use this to index the namespace (and expect things like search rules to
+    /// work), use `lookup`.
     pub fn resolve_path(&self, path: &AmlName) -> AmlName {
         // TODO: we should normalize the path by resolving prefix chars etc.
 
@@ -165,16 +166,43 @@ impl AmlContext {
         }
 
         // Otherwise, it's relative to the current scope so append it onto that.
-        let mut new_path = self.current_scope.clone();
-        new_path.0.extend_from_slice(&(path.0));
-        new_path
+        self.current_scope.clone() + path.clone()
     }
 
     /// Lookup the object at the given path of the namespace. If the given path is not absolute, it
-    /// is resolved against the current scope. Returns `None` if no object exists at that path.
+    /// is resolved against the current scope. Returns `None` if no object exists at that path. If
+    /// `path` is a single name segment and does not appear in the current scope, the search rules
+    /// describes in §5.3 of the ACPI specification.
     pub fn lookup(&self, path: &AmlName) -> Option<&AmlValue> {
-        let resolved_path = self.resolve_path(&path);
-        self.namespace.get(&resolved_path)
+        if path.search_rules_apply() {
+            /*
+             * If search rules apply, we need to recursively look through the namespace. If the
+             * given name does not occur in the current scope, we look at the parent scope, until
+             * we either find the name, or reach the root of the namespace.
+             */
+            let mut scope = self.current_scope.clone();
+            assert!(scope.is_absolute());
+            loop {
+                // Search for the name at this namespace level. If we find it, we're done.
+                let resolved_path = scope.clone() + path.clone();
+                if let Some(value) = self.namespace.get(&resolved_path) {
+                    return Some(value);
+                }
+
+                // If we don't find it, go up a level in the namespace and search for it there,
+                // recursively.
+                match scope.parent() {
+                    Some(parent) => scope = parent,
+                    // If we still haven't found the value and have run out of parents, return `None`.
+                    None => return None,
+                }
+            }
+            None
+        } else {
+            // If special search rules don't apply, simply resolve it against the current scope
+            let resolved_path = self.resolve_path(&path);
+            self.namespace.get(&resolved_path)
+        }
     }
 
     /// Add an `AmlValue` to the namespace. `path` can either be absolute, or relative (in which

+ 83 - 1
aml_parser/src/name_object.rs

@@ -8,7 +8,7 @@ use alloc::{
     string::{String, ToString},
     vec::Vec,
 };
-use core::{fmt, str};
+use core::{fmt, ops::Add, str};
 
 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
 pub struct AmlName(pub(crate) Vec<NameComponent>);
@@ -68,6 +68,57 @@ impl AmlName {
     pub fn is_absolute(&self) -> bool {
         self.0.first() == Some(&NameComponent::Root)
     }
+
+    /// Returns `true` if this path is at the root of the namespace (`\`).
+    pub fn is_at_root(&self) -> bool {
+        self.0 == &[NameComponent::Root]
+    }
+
+    /// Special rules apply when searching for certain paths (specifically, those that are made up
+    /// of a single name segment). Returns `true` if those rules apply.
+    pub fn search_rules_apply(&self) -> bool {
+        if self.0.len() != 1 {
+            return false;
+        }
+
+        match self.0[0] {
+            NameComponent::Segment(_) => true,
+            _ => false,
+        }
+    }
+
+    /// Normalize an AML path, resolving prefix chars. Returns `None` if the path normalizes to an
+    /// invalid path (e.g. `\^_FOO`)
+    pub fn normalize(self) -> Option<AmlName> {
+        // TODO: currently, this doesn't do anything. Work out a nice way of handling prefix chars.
+        Some(self)
+    }
+
+    /// Get the parent of this `AmlName`. For example, the parent of `\_SB.PCI0._PRT` is `\_SB.PCI0`. The root
+    /// path has no parent, and so returns `None`.
+    pub fn parent(&self) -> Option<AmlName> {
+        // Firstly, normalize the path so we don't have to deal with prefix chars
+        let mut normalized_self = self.clone().normalize()?;
+
+        match normalized_self.0.last() {
+            None => None,
+            Some(NameComponent::Root) => None,
+            Some(NameComponent::Segment(_)) => {
+                normalized_self.0.pop();
+                Some(normalized_self)
+            }
+            Some(NameComponent::Prefix) => unreachable!(), // Prefix chars are removed by normalization
+        }
+    }
+}
+
+impl Add for AmlName {
+    type Output = AmlName;
+
+    fn add(mut self, other: Self) -> Self {
+        self.0.extend_from_slice(&(other.0));
+        self
+    }
 }
 
 impl fmt::Display for AmlName {
@@ -323,4 +374,35 @@ mod tests {
             ]))
         );
     }
+
+    #[test]
+    fn test_is_at_root() {
+        assert_eq!(AmlName::root().is_at_root(), true);
+        assert_eq!(AmlName::from_str("\\_SB").unwrap().is_at_root(), false);
+        assert_eq!(AmlName::from_str("\\_SB.PCI0.^VGA").unwrap().is_at_root(), false);
+    }
+
+    #[test]
+    fn test_search_rules_apply() {
+        assert_eq!(AmlName::root().search_rules_apply(), false);
+        assert_eq!(AmlName::from_str("\\_SB").unwrap().search_rules_apply(), false);
+        assert_eq!(AmlName::from_str("^VGA").unwrap().search_rules_apply(), false);
+        assert_eq!(AmlName::from_str("_SB.PCI0.VGA").unwrap().search_rules_apply(), false);
+        assert_eq!(AmlName::from_str("VGA").unwrap().search_rules_apply(), true);
+        assert_eq!(AmlName::from_str("_SB").unwrap().search_rules_apply(), true);
+    }
+
+    #[test]
+    fn test_aml_name_parent() {
+        assert_eq!(AmlName::from_str("\\").unwrap().parent(), None);
+        assert_eq!(AmlName::from_str("\\_SB").unwrap().parent(), Some(AmlName::root()));
+        assert_eq!(
+            AmlName::from_str("\\_SB.PCI0").unwrap().parent(),
+            Some(AmlName::from_str("\\_SB").unwrap())
+        );
+        assert_eq!(
+            AmlName::from_str("\\_SB.PCI0").unwrap().parent().unwrap().parent(),
+            Some(AmlName::root())
+        );
+    }
 }