2
0
Эх сурвалжийг харах

Merge #64

64: Various updates and PCI routing r=IsaacWoods a=IsaacWoods

Closes #50 

Todo:
- [x] Update readme
- [x] Parse `_PRT` objects
- [x] Parse interrupt formats in `_CRS` and `_PRS` objects
- [x] Provide useful interface to PCI routing information
- [x] Implement path normalization, and write tests for it (fixes #63)

Co-authored-by: Isaac Woods <isaacwoods.home@gmail.com>
bors[bot] 5 жил өмнө
parent
commit
99098b792c

+ 6 - 2
README.md

@@ -9,11 +9,15 @@ A library to parse ACPI tables and AML, written in pure Rust. Designed to be eas
 - `acpi` parses the static tables (useful but not feature-complete)
 - `aml` parses the AML tables (can be useful, far from feature-complete)
 
+There is also the `acpi-dumper` utility to easily dump a platform's ACPI tables (this currently only works on
+Linux).
+
 ## Contributing
 Contributions are more than welcome! You can:
 - Write code - the ACPI spec is huge and there are bound to be things we don't support yet!
-- Documentation
-- Using the crates within your kernel and file bug reports and feature requests!
+- Improve our documentation!
+- Use the crates within your kernel and file bug reports and feature requests!
+- Dump the ACPI tables of hardware you have access to and add them to our test suite!
 
 Useful resources for contributing are:
 - [The ACPI specification](https://uefi.org/specifications)

+ 2 - 2
acpi/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "acpi"
-version = "0.4.0"
+version = "0.5.0"
 authors = ["Isaac Woods"]
 repository = "https://github.com/rust-osdev/acpi"
 description = "Library for parsing ACPI tables"
@@ -11,5 +11,5 @@ edition = "2018"
 
 [dependencies]
 log = "0.4"
-bit_field = "0.9"
+bit_field = "0.10"
 typenum = "1"

+ 2 - 4
acpi/src/lib.rs

@@ -219,8 +219,7 @@ where
         (*mapping).validate(b"RSDT")?;
 
         let num_tables = ((*mapping).length() as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u32>();
-        let tables_base =
-            ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u32;
+        let tables_base = ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u32;
 
         for i in 0..num_tables {
             sdt::dispatch_sdt(&mut acpi, handler, unsafe { *tables_base.offset(i as isize) } as usize)?;
@@ -232,8 +231,7 @@ where
         (*mapping).validate(b"XSDT")?;
 
         let num_tables = ((*mapping).length() as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u64>();
-        let tables_base =
-            ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u64;
+        let tables_base = ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u64;
 
         for i in 0..num_tables {
             sdt::dispatch_sdt(&mut acpi, handler, unsafe { *tables_base.offset(i as isize) } as usize)?;

+ 2 - 6
acpi/src/madt.rs

@@ -432,12 +432,8 @@ fn parse_apic_model(acpi: &mut Acpi, mapping: &PhysicalMapping<Madt>) -> Result<
                     (false, false) => ProcessorState::Running,
                 };
 
-                let processor = Processor {
-                    processor_uid: entry.processor_id,
-                    local_apic_id: entry.apic_id,
-                    state,
-                    is_ap,
-                };
+                let processor =
+                    Processor { processor_uid: entry.processor_id, local_apic_id: entry.apic_id, state, is_ap };
 
                 if is_ap {
                     acpi.application_processors.push(processor);

+ 2 - 2
acpi/src/mcfg.rs

@@ -48,8 +48,8 @@ impl Mcfg {
         let num_entries = length / mem::size_of::<McfgEntry>();
 
         unsafe {
-            let pointer = (self as *const Mcfg as *const u8).offset(mem::size_of::<Mcfg>() as isize)
-                as *const McfgEntry;
+            let pointer =
+                (self as *const Mcfg as *const u8).offset(mem::size_of::<Mcfg>() as isize) as *const McfgEntry;
             slice::from_raw_parts(pointer, num_entries)
         }
     }

+ 1 - 2
acpi/src/rsdp_search.rs

@@ -25,8 +25,7 @@ where
 {
     // Read base segment from BIOS area. This is not always given by the bios, so it needs to be
     // checked. We left shift 4 because it is a segment ptr.
-    let ebda_start_mapping =
-        handler.map_physical_region::<u16>(EBDA_START_SEGMENT_PTR, mem::size_of::<u16>());
+    let ebda_start_mapping = handler.map_physical_region::<u16>(EBDA_START_SEGMENT_PTR, mem::size_of::<u16>());
     let ebda_start = (*ebda_start_mapping as usize) << 4;
     handler.unmap_physical_region(ebda_start_mapping);
 

+ 3 - 9
acpi/src/sdt.rs

@@ -156,11 +156,7 @@ where
 
 /// This takes the physical address of an SDT, maps it correctly and dispatches it to whatever
 /// function parses that table.
-pub(crate) fn dispatch_sdt<H>(
-    acpi: &mut Acpi,
-    handler: &mut H,
-    physical_address: usize,
-) -> Result<(), AcpiError>
+pub(crate) fn dispatch_sdt<H>(acpi: &mut Acpi, handler: &mut H, physical_address: usize) -> Result<(), AcpiError>
 where
     H: AcpiHandler,
 {
@@ -186,15 +182,13 @@ where
         }
 
         "APIC" => {
-            let madt_mapping =
-                handler.map_physical_region::<Madt>(physical_address, header.length() as usize);
+            let madt_mapping = handler.map_physical_region::<Madt>(physical_address, header.length() as usize);
             crate::madt::parse_madt(acpi, handler, &madt_mapping)?;
             handler.unmap_physical_region(madt_mapping);
         }
 
         "MCFG" => {
-            let mcfg_mapping =
-                handler.map_physical_region::<Mcfg>(physical_address, header.length() as usize);
+            let mcfg_mapping = handler.map_physical_region::<Mcfg>(physical_address, header.length() as usize);
             crate::mcfg::parse_mcfg(acpi, &mcfg_mapping)?;
             handler.unmap_physical_region(mcfg_mapping);
         }

+ 3 - 2
aml/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "aml"
-version = "0.4.0"
+version = "0.5.0"
 authors = ["Isaac Woods"]
 repository = "https://github.com/rust-osdev/acpi"
 description = "Library for parsing AML"
@@ -11,7 +11,8 @@ edition = "2018"
 
 [dependencies]
 log = "0.4"
-bit_field = "0.9"
+bit_field = "0.10"
+byteorder = { version = "1", default-features = false }
 
 [features]
 debug_parser = []

+ 24 - 3
aml/src/lib.rs

@@ -47,7 +47,9 @@ pub(crate) mod name_object;
 pub(crate) mod namespace;
 pub(crate) mod opcode;
 pub(crate) mod parser;
+pub mod pci_routing;
 pub(crate) mod pkg_length;
+pub mod resource;
 pub(crate) mod term_object;
 pub(crate) mod type1;
 pub(crate) mod type2;
@@ -143,6 +145,7 @@ impl AmlContext {
             self.local_6 = None;
             self.local_7 = None;
 
+            log::trace!("Invoking method with {} arguments, code: {:x?}", flags.arg_count(), code);
             let return_value =
                 match term_list(PkgLength::from_raw_length(&code, code.len() as u32)).parse(&code, self) {
                     // If the method doesn't return a value, we implicitly return `0`
@@ -203,7 +206,7 @@ pub enum AmlError {
      */
     UnexpectedEndOfStream,
     UnexpectedByte(u8),
-    InvalidNameSeg([u8; 4]),
+    InvalidNameSeg,
     InvalidFieldFlags,
     IncompatibleValueConversion,
     UnterminatedStringConstant,
@@ -217,9 +220,11 @@ pub enum AmlError {
     /*
      * Errors produced manipulating AML names.
      */
+    EmptyNamesAreInvalid,
     /// Produced when trying to normalize a path that does not point to a valid level of the
-    /// namespace. E.g. `\_SB.^^PCI0` goes above the root of the namespace.
-    InvalidNormalizedName(String),
+    /// namespace. E.g. `\_SB.^^PCI0` goes above the root of the namespace. The contained value is the name that
+    /// normalization was attempted upon.
+    InvalidNormalizedName(AmlName),
     RootHasNoParent,
 
     /*
@@ -245,4 +250,20 @@ pub enum AmlError {
     /// error system here because the way errors are propagated matches how we want to handle
     /// return values.
     Return(AmlValue),
+
+    /*
+     * Errors produced parsing the PCI routing tables (_PRT objects).
+     */
+    PrtInvalidAddress,
+    PrtInvalidPin,
+    PrtInvalidSource,
+    PrtInvalidGsi,
+    /// Produced when the PRT doesn't contain an entry for the requested address + pin
+    PrtNoEntry,
+
+    /*
+     * Errors produced parsing Resource Descriptors.
+     */
+    ReservedResourceType,
+    ResourceDescriptorTooShort,
 }

+ 2 - 3
aml/src/misc.rs

@@ -64,9 +64,8 @@ where
      * Arg5Op = 0x6d
      * Arg6Op = 0x6e
      */
-    let arg_parser = |i, arg_opcode| {
-        opcode(arg_opcode).then(comment_scope_verbose("ArgObj", id())).map(move |((), _)| Ok(i))
-    };
+    let arg_parser =
+        |i, arg_opcode| opcode(arg_opcode).then(comment_scope_verbose("ArgObj", id())).map(move |((), _)| Ok(i));
 
     choice!(
         arg_parser(0, opcode::ARG0_OP),

+ 6 - 10
aml/src/name_object.rs

@@ -139,10 +139,10 @@ where
 pub struct NameSeg(pub(crate) [u8; 4]);
 
 impl NameSeg {
-    pub(crate) fn from_str(string: &str) -> Option<NameSeg> {
+    pub(crate) fn from_str(string: &str) -> Result<NameSeg, AmlError> {
         // Each NameSeg can only have four chars, and must have at least one
         if string.len() < 1 || string.len() > 4 {
-            return None;
+            return Err(AmlError::InvalidNameSeg);
         }
 
         // We pre-fill the array with '_', so it will already be correct if the length is < 4
@@ -151,19 +151,19 @@ impl NameSeg {
 
         // Manually do the first one, because we have to check it's a LeadNameChar
         if !is_lead_name_char(bytes[0]) {
-            return None;
+            return Err(AmlError::InvalidNameSeg);
         }
         seg[0] = bytes[0];
 
         // Copy the rest of the chars, checking that they're NameChars
         for i in 1..bytes.len() {
             if !is_name_char(bytes[i]) {
-                return None;
+                return Err(AmlError::InvalidNameSeg);
             }
             seg[i] = bytes[i];
         }
 
-        Some(NameSeg(seg))
+        Ok(NameSeg(seg))
     }
 
     /// Turn a `NameSeg` into a `&str`. Returns it in a `ParseResult` so it's easy to use from
@@ -247,11 +247,7 @@ mod tests {
         check_err!(name_path().parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]);
         check_ok!(name_path().parse(&[0x00], &mut context), alloc::vec![], &[]);
         check_ok!(name_path().parse(&[0x00, 0x00], &mut context), alloc::vec![], &[0x00]);
-        check_err!(
-            name_path().parse(&[0x2e, b'A'], &mut context),
-            AmlError::UnexpectedEndOfStream,
-            &[0x2e, b'A']
-        );
+        check_err!(name_path().parse(&[0x2e, b'A'], &mut context), AmlError::UnexpectedEndOfStream, &[0x2e, b'A']);
         check_ok!(
             name_path().parse(&[0x2e, b'A', b'B', b'C', b'D', b'E', b'_', b'F', b'G'], &mut context),
             alloc::vec![

+ 75 - 31
aml/src/namespace.rs

@@ -97,9 +97,9 @@ 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 handle of
-    /// the first valid object, if found.
-    pub fn search(&self, path: &AmlName, starting_scope: &AmlName) -> Result<AmlHandle, AmlError> {
+    /// 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.
+    pub fn search(&self, path: &AmlName, starting_scope: &AmlName) -> Result<(AmlName, AmlHandle), AmlError> {
         if path.search_rules_apply() {
             /*
              * If search rules apply, we need to recursively look through the namespace. If the
@@ -110,8 +110,9 @@ impl Namespace {
             assert!(scope.is_absolute());
             loop {
                 // Search for the name at this namespace level. If we find it, we're done.
-                if let Some(handle) = self.name_map.get(&path.resolve(&scope)?) {
-                    return Ok(*handle);
+                let name = path.resolve(&scope)?;
+                if let Some(handle) = self.name_map.get(&name) {
+                    return Ok((name, *handle));
                 }
 
                 // If we don't find it, go up a level in the namespace and search for it there,
@@ -119,18 +120,20 @@ impl Namespace {
                 match scope.parent() {
                     Ok(parent) => scope = parent,
                     // If we still haven't found the value and have run out of parents, return `None`.
-                    Err(AmlError::RootHasNoParent) => {
-                        return Err(AmlError::ObjectDoesNotExist(path.as_string()))
-                    }
+                    Err(AmlError::RootHasNoParent) => return Err(AmlError::ObjectDoesNotExist(path.as_string())),
                     Err(err) => return Err(err),
                 }
             }
         } else {
             // If search rules don't apply, simply resolve it against the starting scope
-            self.name_map
-                .get(&path.resolve(starting_scope)?)
-                .map(|&handle| handle)
-                .ok_or(AmlError::ObjectDoesNotExist(path.as_string()))
+            let name = path.resolve(starting_scope)?;
+            Ok((
+                name,
+                self.name_map
+                    .get(&path.resolve(starting_scope)?)
+                    .map(|&handle| handle)
+                    .ok_or(AmlError::ObjectDoesNotExist(path.as_string()))?,
+            ))
         }
     }
 }
@@ -157,11 +160,10 @@ impl AmlName {
         AmlName(alloc::vec![NameComponent::Segment(seg)])
     }
 
-    /// Convert a string representation of an AML name into an `AmlName`. Returns `None` if the
-    /// passed string is not a valid AML path.
-    pub fn from_str(mut string: &str) -> Option<AmlName> {
+    /// Convert a string representation of an AML name into an `AmlName`.
+    pub fn from_str(mut string: &str) -> Result<AmlName, AmlError> {
         if string.len() == 0 {
-            return None;
+            return Err(AmlError::EmptyNamesAreInvalid);
         }
 
         let mut components = Vec::new();
@@ -185,7 +187,7 @@ impl AmlName {
             }
         }
 
-        Some(AmlName(components))
+        Ok(AmlName(components))
     }
 
     pub fn as_string(&self) -> String {
@@ -223,12 +225,29 @@ impl AmlName {
         }
     }
 
-    /// Normalize an AML path, resolving prefix chars. Returns `None` if the path normalizes to an
-    /// invalid path (e.g. `\^_FOO`)
+    /// Normalize an AML path, resolving prefix chars. Returns `AmlError::InvalidNormalizedName` if the path
+    /// normalizes to an invalid path (e.g. `\^_FOO`)
     pub fn normalize(self) -> Result<AmlName, AmlError> {
-        // TODO: currently, this doesn't do anything. Work out a nice way of handling prefix chars.
-        // If the name can't be normalized, emit AmlError::InvalidNormalizedName
-        Ok(self)
+        Ok(AmlName(self.0.iter().try_fold(alloc::vec![], |mut name, &component| match component {
+            seg @ NameComponent::Segment(_) => {
+                name.push(seg);
+                Ok(name)
+            }
+
+            NameComponent::Root => {
+                name.push(NameComponent::Root);
+                Ok(name)
+            }
+
+            NameComponent::Prefix => {
+                if let Some(NameComponent::Segment(_)) = name.iter().last() {
+                    name.pop().unwrap();
+                    Ok(name)
+                } else {
+                    Err(AmlError::InvalidNormalizedName(self.clone()))
+                }
+            }
+        })?))
     }
 
     /// Get the parent of this `AmlName`. For example, the parent of `\_SB.PCI0._PRT` is `\_SB.PCI0`. The root
@@ -268,7 +287,7 @@ impl fmt::Display for AmlName {
     }
 }
 
-#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
 pub enum NameComponent {
     Root,
     Prefix,
@@ -281,11 +300,11 @@ mod tests {
 
     #[test]
     fn test_aml_name_from_str() {
-        assert_eq!(AmlName::from_str(""), None);
-        assert_eq!(AmlName::from_str("\\"), Some(AmlName::root()));
+        assert_eq!(AmlName::from_str(""), Err(AmlError::EmptyNamesAreInvalid));
+        assert_eq!(AmlName::from_str("\\"), Ok(AmlName::root()));
         assert_eq!(
             AmlName::from_str("\\_SB.PCI0"),
-            Some(AmlName(alloc::vec![
+            Ok(AmlName(alloc::vec![
                 NameComponent::Root,
                 NameComponent::Segment(NameSeg([b'_', b'S', b'B', b'_'])),
                 NameComponent::Segment(NameSeg([b'P', b'C', b'I', b'0']))
@@ -293,7 +312,7 @@ mod tests {
         );
         assert_eq!(
             AmlName::from_str("\\_SB.^^^PCI0"),
-            Some(AmlName(alloc::vec![
+            Ok(AmlName(alloc::vec![
                 NameComponent::Root,
                 NameComponent::Segment(NameSeg([b'_', b'S', b'B', b'_'])),
                 NameComponent::Prefix,
@@ -314,6 +333,34 @@ mod tests {
         assert_eq!(AmlName::from_str("_SB.PCI0.VGA").unwrap().is_normal(), true);
     }
 
+    #[test]
+    fn test_normalization() {
+        assert_eq!(
+            AmlName::from_str("\\_SB.PCI0").unwrap().normalize(),
+            Ok(AmlName::from_str("\\_SB.PCI0").unwrap())
+        );
+        assert_eq!(
+            AmlName::from_str("\\_SB.^PCI0").unwrap().normalize(),
+            Ok(AmlName::from_str("\\PCI0").unwrap())
+        );
+        assert_eq!(
+            AmlName::from_str("\\_SB.PCI0.^^FOO").unwrap().normalize(),
+            Ok(AmlName::from_str("\\FOO").unwrap())
+        );
+        assert_eq!(
+            AmlName::from_str("_SB.PCI0.^FOO.BAR").unwrap().normalize(),
+            Ok(AmlName::from_str("_SB.FOO.BAR").unwrap())
+        );
+        assert_eq!(
+            AmlName::from_str("\\^_SB").unwrap().normalize(),
+            Err(AmlError::InvalidNormalizedName(AmlName::from_str("\\^_SB").unwrap()))
+        );
+        assert_eq!(
+            AmlName::from_str("\\_SB.PCI0.FOO.^^^^BAR").unwrap().normalize(),
+            Err(AmlError::InvalidNormalizedName(AmlName::from_str("\\_SB.PCI0.FOO.^^^^BAR").unwrap()))
+        );
+    }
+
     #[test]
     fn test_is_absolute() {
         assert_eq!(AmlName::root().is_absolute(), true);
@@ -338,10 +385,7 @@ mod tests {
     fn test_aml_name_parent() {
         assert_eq!(AmlName::from_str("\\").unwrap().parent(), Err(AmlError::RootHasNoParent));
         assert_eq!(AmlName::from_str("\\_SB").unwrap().parent(), Ok(AmlName::root()));
-        assert_eq!(
-            AmlName::from_str("\\_SB.PCI0").unwrap().parent(),
-            Ok(AmlName::from_str("\\_SB").unwrap())
-        );
+        assert_eq!(AmlName::from_str("\\_SB.PCI0").unwrap().parent(), Ok(AmlName::from_str("\\_SB").unwrap()));
         assert_eq!(AmlName::from_str("\\_SB.PCI0").unwrap().parent().unwrap().parent(), Ok(AmlName::root()));
     }
 }

+ 1 - 5
aml/src/opcode.rs

@@ -94,11 +94,7 @@ mod tests {
     fn empty() {
         let mut context = AmlContext::new();
         check_err!(opcode(NULL_NAME).parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]);
-        check_err!(
-            ext_opcode(EXT_DEF_FIELD_OP).parse(&[], &mut context),
-            AmlError::UnexpectedEndOfStream,
-            &[]
-        );
+        check_err!(ext_opcode(EXT_DEF_FIELD_OP).parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]);
     }
 
     #[test]

+ 2 - 9
aml/src/parser.rs

@@ -115,10 +115,7 @@ where
         Ok((
             &input[4..],
             context,
-            input[0] as u32
-                + ((input[1] as u32) << 8)
-                + ((input[2] as u32) << 16)
-                + ((input[3] as u32) << 24),
+            input[0] as u32 + ((input[1] as u32) << 8) + ((input[2] as u32) << 16) + ((input[3] as u32) << 24),
         ))
     }
 }
@@ -471,11 +468,7 @@ mod tests {
         check_err!(take_u16().parse(&[0x34], &mut context), AmlError::UnexpectedEndOfStream, &[0x34]);
         check_ok!(take_u16().parse(&[0x34, 0x12], &mut context), 0x1234, &[]);
 
-        check_err!(
-            take_u32().parse(&[0x34, 0x12], &mut context),
-            AmlError::UnexpectedEndOfStream,
-            &[0x34, 0x12]
-        );
+        check_err!(take_u32().parse(&[0x34, 0x12], &mut context), AmlError::UnexpectedEndOfStream, &[0x34, 0x12]);
         check_ok!(take_u32().parse(&[0x34, 0x12, 0xf4, 0xc3, 0x3e], &mut context), 0xc3f41234, &[0x3e]);
 
         check_err!(take_u64().parse(&[0x34], &mut context), AmlError::UnexpectedEndOfStream, &[0x34]);

+ 184 - 0
aml/src/pci_routing.rs

@@ -0,0 +1,184 @@
+use crate::{
+    namespace::AmlName,
+    resource::{self, InterruptPolarity, InterruptTrigger, Resource},
+    value::Args,
+    AmlContext,
+    AmlError,
+    AmlValue,
+};
+use alloc::vec::Vec;
+use bit_field::BitField;
+use core::convert::TryInto;
+
+pub use crate::resource::IrqDescriptor;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum Pin {
+    IntA,
+    IntB,
+    IntC,
+    IntD,
+}
+
+#[derive(Debug)]
+pub enum PciRouteType {
+    /// The interrupt is hard-coded to a specific GSI
+    Gsi(u32),
+
+    /// The interrupt is linked to a link object. This object will have `_PRS`, `_CRS` fields and a `_SRS` method
+    /// that can be used to allocate the interrupt. Note that some platforms (e.g. QEMU's q35 chipset) use link
+    /// objects but do not support changing the interrupt that it's linked to (i.e. `_SRS` doesn't do anything).
+    /*
+     * The actual object itself will just be a `Device`, and we need paths to its children objects to do
+     * anything useful, so we just store the resolved name here.
+     */
+    LinkObject(AmlName),
+}
+
+#[derive(Debug)]
+pub struct PciRoute {
+    device: u16,
+    function: u16,
+    pin: Pin,
+    route_type: PciRouteType,
+}
+
+/// A `PciRoutingTable` is used to interpret the data in a `_PRT` object, which provides a mapping
+/// from PCI interrupt pins to the inputs of the interrupt controller. One of these objects must be
+/// present under each PCI root bridge, and consists of a package of packages, each of which describes the
+/// mapping of a single PCI interrupt pin.
+#[derive(Debug)]
+pub struct PciRoutingTable {
+    entries: Vec<PciRoute>,
+}
+
+impl PciRoutingTable {
+    /// Construct a `PciRoutingTable` from a path to a `_PRT` object. Returns
+    /// `AmlError::IncompatibleValueConversion` if the value passed is not a package, or if any of the values
+    /// within it are not packages. Returns the various `AmlError::Prt*` errors if the internal structure of the
+    /// entries is invalid.
+    pub fn from_prt_path(prt_path: &AmlName, context: &mut AmlContext) -> Result<PciRoutingTable, AmlError> {
+        let mut entries = Vec::new();
+
+        let prt = context.invoke_method(&prt_path, Args::default())?;
+        if let AmlValue::Package(ref inner_values) = prt {
+            for value in inner_values {
+                if let AmlValue::Package(ref pin_package) = value {
+                    /*
+                     * Each inner package has the following structure:
+                     *   | Field      | Type      | Description                                               |
+                     *   | -----------|-----------|-----------------------------------------------------------|
+                     *   | Address    | Dword     | Address of the device. Same format as _ADR objects (high  |
+                     *   |            |           | word = #device, low word = #function)                     |
+                     *   | -----------|-----------|-----------------------------------------------------------|
+                     *   | Pin        | Byte      | The PCI pin (0 = INTA, 1 = INTB, 2 = INTC, 3 = INTD)      |
+                     *   | -----------|-----------|-----------------------------------------------------------|
+                     *   | Source     | Byte or   | Name of the device that allocates the interrupt to which  |
+                     *   |            | NamePath  | the above pin is connected. Can be fully qualified,       |
+                     *   |            |           | relative, or a simple NameSeg that utilizes namespace     |
+                     *   |            |           | search rules. Instead, if this is a byte value of 0, the  |
+                     *   |            |           | interrupt is allocated out of the GSI pool, and Source    |
+                     *   |            |           | Index should be utilised.                                 |
+                     *   | -----------|-----------|-----------------------------------------------------------|
+                     *   | Source     | Dword     | Index that indicates which resource descriptor in the     |
+                     *   | Index      |           | resource template of the device pointed to in the Source  |
+                     *   |            |           | field this interrupt is allocated from. If the Source     |
+                     *   |            |           | is zero, then this field is the GSI number to which the   |
+                     *   |            |           | pin is connected.                                         |
+                     *   | -----------|-----------|-----------------------------------------------------------|
+                     */
+                    let address = pin_package[0].as_integer()?;
+                    let device = address.get_bits(16..32).try_into().map_err(|_| AmlError::PrtInvalidAddress)?;
+                    let function = address.get_bits(0..16).try_into().map_err(|_| AmlError::PrtInvalidAddress)?;
+                    let pin = match pin_package[1].as_integer()? {
+                        0 => Pin::IntA,
+                        1 => Pin::IntB,
+                        2 => Pin::IntC,
+                        3 => Pin::IntD,
+                        _ => return Err(AmlError::PrtInvalidPin),
+                    };
+
+                    match pin_package[2] {
+                        AmlValue::Integer(0) => {
+                            /*
+                             * The Source Index field contains the GSI number that this interrupt is attached
+                             * to.
+                             */
+                            entries.push(PciRoute {
+                                device,
+                                function,
+                                pin,
+                                route_type: PciRouteType::Gsi(
+                                    pin_package[3]
+                                        .as_integer()?
+                                        .try_into()
+                                        .map_err(|_| AmlError::PrtInvalidGsi)?,
+                                ),
+                            });
+                        }
+                        AmlValue::String(ref name) => {
+                            let (link_object_name, _) =
+                                context.namespace.search(&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::IncompatibleValueConversion);
+                }
+            }
+
+            Ok(PciRoutingTable { entries })
+        } else {
+            Err(AmlError::IncompatibleValueConversion)
+        }
+    }
+
+    /// Get the interrupt input that a given PCI interrupt pin is wired to. Returns `AmlError::PrtNoEntry` if the
+    /// PRT doesn't contain an entry for the given address + pin.
+    pub fn route(
+        &self,
+        device: u16,
+        function: u16,
+        pin: Pin,
+        context: &mut AmlContext,
+    ) -> Result<IrqDescriptor, AmlError> {
+        let entry = self
+            .entries
+            .iter()
+            .find(|entry| {
+                entry.device == device
+                    && (entry.function == 0xffff || entry.function == function)
+                    && entry.pin == pin
+            })
+            .ok_or(AmlError::PrtNoEntry)?;
+
+        match entry.route_type {
+            PciRouteType::Gsi(gsi) => Ok(IrqDescriptor {
+                is_consumer: true,
+                trigger: InterruptTrigger::Level,
+                polarity: InterruptPolarity::ActiveLow,
+                is_shared: true,
+                is_wake_capable: false,
+                irq: gsi,
+            }),
+            PciRouteType::LinkObject(ref name) => {
+                let link_crs =
+                    context.namespace.get_by_path(&AmlName::from_str("_CRS").unwrap().resolve(name)?)?;
+
+                match link_crs {
+                    AmlValue::Name(ref boxed_object) => match resource::resource_descriptor(boxed_object)? {
+                        Resource::Irq(descriptor) => Ok(descriptor),
+                        _ => Err(AmlError::IncompatibleValueConversion),
+                    },
+                    _ => return Err(AmlError::IncompatibleValueConversion),
+                }
+            }
+        }
+    }
+}

+ 4 - 3
aml/src/pkg_length.rs

@@ -70,9 +70,10 @@ where
                     (
                         new_input,
                         context,
-                        bytes.iter().enumerate().fold(initial_length, |length, (i, &byte)| {
-                            length + (u32::from(byte) << (4 + i * 8))
-                        }),
+                        bytes
+                            .iter()
+                            .enumerate()
+                            .fold(initial_length, |length, (i, &byte)| length + (u32::from(byte) << (4 + i * 8))),
                     )
                 }
 

+ 156 - 0
aml/src/resource.rs

@@ -0,0 +1,156 @@
+use crate::{value::AmlValue, AmlError};
+use bit_field::BitField;
+use byteorder::{ByteOrder, LittleEndian};
+
+#[derive(Debug)]
+pub enum Resource {
+    Irq(IrqDescriptor),
+}
+
+/// Parse a `ResourceDescriptor`. Returns `AmlError::IncompatibleValueConversion` if the passed value is not a
+/// `Buffer`.
+pub(crate) fn resource_descriptor(descriptor: &AmlValue) -> Result<Resource, AmlError> {
+    if let AmlValue::Buffer { bytes, size: _ } = descriptor {
+        /*
+         * If bit 7 of Byte 0 is set, it's a large descriptor. If not, it's a small descriptor.
+         */
+        if bytes[0].get_bit(7) {
+            /*
+             * We're parsing a large item. The descriptor type is encoded in Bits 0-6 of Byte 0. Valid types:
+             *      0x00: Reserved
+             *      0x01: 24-bit Memory Range Descriptor
+             *      0x02: Generic Register Descriptor
+             *      0x03: Reserved
+             *      0x04: Vendor-defined Descriptor
+             *      0x05: 32-bit Memory Range Descriptor
+             *      0x06: 32-bit Fixed Memory Range Descriptor
+             *      0x07: Address Space Resource Descriptor
+             *      0x08: Word Address Space Descriptor
+             *      0x09: Extended Interrupt Descriptor
+             *      0x0a: QWord Address Space Descriptor
+             *      0x0b: Extended Address Space Descriptor
+             *      0x0c: GPIO Connection Descriptor
+             *      0x0d: Pin Function Descriptor
+             *      0x0e: GenericSerialBus Connection Descriptor
+             *      0x0f: Pin Configuration Descriptor
+             *      0x10: Pin Group Descriptor
+             *      0x11: Pin Group Function Descriptor
+             *      0x12: Pin Group Configuration Descriptor
+             *      0x13-0x7f: Reserved
+             *
+             * Byte 1 contains bits 0-7 of the length, and Byte 2 contains bits 8-15 of the length. Subsequent
+             * bytes contain the actual data items.
+             */
+            let descriptor_type = bytes[0].get_bits(0..7);
+            match descriptor_type {
+                0x01 => unimplemented!(),
+                0x02 => unimplemented!(),
+                0x03 => unimplemented!(),
+                0x04 => unimplemented!(),
+                0x05 => unimplemented!(),
+                0x06 => unimplemented!(),
+                0x07 => unimplemented!(),
+                0x08 => unimplemented!(),
+                0x09 => extended_interrupt_descriptor(bytes),
+                0x0a => unimplemented!(),
+                0x0b => unimplemented!(),
+                0x0c => unimplemented!(),
+                0x0d => unimplemented!(),
+                0x0e => unimplemented!(),
+                0x0f => unimplemented!(),
+                0x10 => unimplemented!(),
+                0x11 => unimplemented!(),
+                0x12 => unimplemented!(),
+
+                0x00 | 0x13..=0x7f => Err(AmlError::ReservedResourceType),
+                0x80..=0xff => unreachable!(),
+            }
+        } else {
+            /*
+             * We're parsing a small descriptor. Byte 0 has the format:
+             *    | Bits        | Field             |
+             *    |-------------|-------------------|
+             *    | 0-2         | Length - n bytes  |
+             *    | 3-6         | Small item type   |
+             *    | 7           | 0 = small item    |
+             *
+             * The valid types are:
+             *      0x00-0x03: Reserved
+             *      0x04: IRQ Format Descriptor
+             *      0x05: DMA Format Descriptor
+             *      0x06: Start Dependent Functions Descriptor
+             *      0x07: End Dependent Functions Descriptor
+             *      0x08: IO Port Descriptor
+             *      0x09: Fixed Location IO Port Descriptor
+             *      0x0A: Fixed DMA Descriptor
+             *      0x0B-0x0D: Reserved
+             *      0x0E: Vendor Defined Descriptor
+             *      0x0F: End Tag Descriptor
+             */
+            unimplemented!()
+        }
+    } else {
+        Err(AmlError::IncompatibleValueConversion)
+    }
+}
+
+#[derive(Debug)]
+pub enum InterruptTrigger {
+    Edge,
+    Level,
+}
+
+#[derive(Debug)]
+pub enum InterruptPolarity {
+    ActiveHigh,
+    ActiveLow,
+}
+
+#[derive(Debug)]
+pub struct IrqDescriptor {
+    pub is_consumer: bool,
+    pub trigger: InterruptTrigger,
+    pub polarity: InterruptPolarity,
+    pub is_shared: bool,
+    pub is_wake_capable: bool,
+    /*
+     * NOTE: We currently only support the cases where a descriptor only contains a single interrupt
+     * number.
+     */
+    pub irq: u32,
+}
+
+fn extended_interrupt_descriptor(bytes: &[u8]) -> Result<Resource, AmlError> {
+    /*
+     * --- Extended Interrupt Descriptor ---
+     * Byte 3 contains the Interrupt Vector Flags:
+     *      Bit 0: 1 if device consumes the resource, 0 if it produces it
+     *      Bit 1: 1 if edge-triggered, 0 if level-triggered
+     *      Bit 2: 1 = active-high, 0 = active-low
+     *      Bit 3: 1 if interrupt is shared with other devices
+     *      Bit 4: 1 if this interrupt is capable of waking the system, 0 if it is not
+     * Byte 4 contains the number of interrupt numbers that follow. When this descriptor is
+     * returned from `_CRS` or send to `_SRS`, this field must be 1.
+     *
+     * From Byte 5 onwards, there are `n` interrupt numbers, each of which is encoded as a
+     * 4-byte little-endian number.
+     *
+     * NOTE: We only support the case where there is a single interrupt number.
+     */
+    if bytes.len() < 9 {
+        return Err(AmlError::ResourceDescriptorTooShort);
+    }
+
+    let number_of_interrupts = bytes[4] as usize;
+    assert_eq!(number_of_interrupts, 1);
+    let irq = LittleEndian::read_u32(&[bytes[5], bytes[6], bytes[7], bytes[8]]);
+
+    Ok(Resource::Irq(IrqDescriptor {
+        is_consumer: bytes[3].get_bit(0),
+        trigger: if bytes[3].get_bit(1) { InterruptTrigger::Edge } else { InterruptTrigger::Level },
+        polarity: if bytes[3].get_bit(2) { InterruptPolarity::ActiveLow } else { InterruptPolarity::ActiveHigh },
+        is_shared: bytes[3].get_bit(3),
+        is_wake_capable: bytes[3].get_bit(4),
+        irq,
+    }))
+}

+ 4 - 15
aml/src/term_object.rs

@@ -603,9 +603,7 @@ where
             opcode::DWORD_CONST => {
                 take_u32().map(|value| Ok(AmlValue::Integer(value as u64))).parse(new_input, context)
             }
-            opcode::QWORD_CONST => {
-                take_u64().map(|value| Ok(AmlValue::Integer(value))).parse(new_input, context)
-            }
+            opcode::QWORD_CONST => take_u64().map(|value| Ok(AmlValue::Integer(value))).parse(new_input, context),
             opcode::STRING_PREFIX => string_parser.parse(new_input, context),
             opcode::ZERO_OP => Ok((new_input, context, AmlValue::Integer(0))),
             opcode::ONE_OP => Ok((new_input, context, AmlValue::Integer(1))),
@@ -618,8 +616,7 @@ where
     comment_scope_verbose(
         "ComputationalData",
         choice!(
-            ext_opcode(opcode::EXT_REVISION_OP)
-                .map(|_| Ok(AmlValue::Integer(crate::AML_INTERPRETER_REVISION))),
+            ext_opcode(opcode::EXT_REVISION_OP).map(|_| Ok(AmlValue::Integer(crate::AML_INTERPRETER_REVISION))),
             const_parser,
             make_parser_concrete!(def_buffer())
         ),
@@ -654,16 +651,8 @@ mod test {
             AmlValue::Integer(crate::AML_INTERPRETER_REVISION),
             &[]
         );
-        check_ok!(
-            computational_data().parse(&[0x0a, 0xf3, 0x35], &mut context),
-            AmlValue::Integer(0xf3),
-            &[0x35]
-        );
-        check_ok!(
-            computational_data().parse(&[0x0b, 0xf3, 0x35], &mut context),
-            AmlValue::Integer(0x35f3),
-            &[]
-        );
+        check_ok!(computational_data().parse(&[0x0a, 0xf3, 0x35], &mut context), AmlValue::Integer(0xf3), &[0x35]);
+        check_ok!(computational_data().parse(&[0x0b, 0xf3, 0x35], &mut context), AmlValue::Integer(0x35f3), &[]);
         check_ok!(
             computational_data().parse(&[0x0c, 0xf3, 0x35, 0x12, 0x65, 0xff, 0x00], &mut context),
             AmlValue::Integer(0x651235f3),

+ 1 - 2
aml/src/type1.rs

@@ -56,8 +56,7 @@ where
                 .map_with_context(|((predicate, then_branch), else_branch), context| {
                     let branch = if predicate { then_branch } else { else_branch };
 
-                    match term_list(PkgLength::from_raw_length(branch, branch.len() as u32))
-                        .parse(branch, context)
+                    match term_list(PkgLength::from_raw_length(branch, branch.len() as u32)).parse(branch, context)
                     {
                         Ok((_, context, result)) => (Ok(result), context),
                         Err((_, context, err)) => (Err(err), context),

+ 7 - 8
aml/src/type2.rs

@@ -58,12 +58,11 @@ where
      * after the store (as opposed to the data we think we put into it), because some stores can
      * alter the data during the store.
      */
-    opcode(opcode::DEF_STORE_OP)
-        .then(comment_scope("DefStore", term_arg().then(super_name())))
-        .map_with_context(|((), (value, target)), context| {
+    opcode(opcode::DEF_STORE_OP).then(comment_scope("DefStore", term_arg().then(super_name()))).map_with_context(
+        |((), (value, target)), context| {
             match target {
                 Target::Name(ref path) => {
-                    let handle =
+                    let (_, handle) =
                         try_with_context!(context, context.namespace.search(path, &context.current_scope));
                     let desired_type = context.namespace.get(handle).unwrap().type_of();
                     let converted_object = try_with_context!(context, value.as_type(desired_type));
@@ -88,7 +87,8 @@ where
                     unimplemented!()
                 }
             }
-        })
+        },
+    )
 }
 
 fn method_invocation<'a, 'c>() -> impl Parser<'a, 'c, AmlValue>
@@ -112,9 +112,8 @@ where
         "MethodInvocation",
         name_string()
             .map_with_context(move |name, context| {
-                let handle =
-                    try_with_context!(context, context.namespace.search(&name, &context.current_scope))
-                        .clone();
+                let (_, handle) =
+                    try_with_context!(context, context.namespace.search(&name, &context.current_scope)).clone();
                 (Ok(handle), context)
             })
             .feed(|handle| {

+ 7 - 16
aml_tester/src/main.rs

@@ -2,10 +2,10 @@
  * This is a small program that is meant for testing the AML parser on artificial
  * AML. We want to:
  *      - scan a directory for ASL files
- *      - compile them using `iasl` into AML files (these should be gitignored), but only if the ASL file
- *        has a newer timestamp than the AML file (or just compile if there isn't a corresponding AML file)
- *      - Run the AML parser on each AML file, printing test output like `cargo test` does in a nice table
- *        for each AML file
+ *      - compile them using `iasl` into AML files (these should be gitignored), but only if the ASL file has a
+ *        newer timestamp than the AML file (or just compile if there isn't a corresponding AML file)
+ *      - Run the AML parser on each AML file, printing test output like `cargo test` does in a nice table for
+ *        each AML file
  *      - For failing tests, print out a nice summary of the errors for each file
  */
 
@@ -41,9 +41,7 @@ fn main() -> std::io::Result<()> {
      * parser.
      */
     let aml_files = fs::read_dir(dir_path)?
-        .filter(|entry| {
-            entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("aml"))
-        })
+        .filter(|entry| entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("aml")))
         .map(|entry| entry.unwrap());
 
     let (passed, failed) = aml_files.fold((0, 0), |(passed, failed), file_entry| {
@@ -63,12 +61,7 @@ fn main() -> std::io::Result<()> {
             }
 
             Err(err) => {
-                println!(
-                    "{}Failed ({:?}){}",
-                    termion::color::Fg(termion::color::Red),
-                    err,
-                    termion::style::Reset
-                );
+                println!("{}Failed ({:?}){}", termion::color::Fg(termion::color::Red), err, termion::style::Reset);
                 (passed, failed + 1)
             }
         }
@@ -80,9 +73,7 @@ fn main() -> std::io::Result<()> {
 
 fn compile_asl_files(dir_path: &Path) -> std::io::Result<()> {
     let mut asl_files = fs::read_dir(dir_path)?
-        .filter(|entry| {
-            entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("asl"))
-        })
+        .filter(|entry| entry.is_ok() && entry.as_ref().unwrap().path().extension() == Some(OsStr::new("asl")))
         .map(|file| file.unwrap())
         .peekable();
 

+ 2 - 2
rustfmt.toml

@@ -7,6 +7,6 @@ use_field_init_shorthand = true
 use_try_shorthand = true
 format_code_in_doc_comments = true
 wrap_comments = true
-max_width = 110
-comment_width = 110
+max_width = 115
+comment_width = 115
 use_small_heuristics = "max"