Skip to main content

stable_aml/
pci_routing.rs

1use crate::{
2    namespace::AmlName,
3    resource::{self, InterruptPolarity, InterruptTrigger, Resource},
4    value::Args,
5    AmlContext, AmlError, AmlType, AmlValue,
6};
7use alloc::vec::Vec;
8use bit_field::BitField;
9use core::convert::TryInto;
10
11pub use crate::resource::IrqDescriptor;
12
13#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub enum Pin {
15    IntA,
16    IntB,
17    IntC,
18    IntD,
19}
20
21#[derive(Debug)]
22pub enum PciRouteType {
23    /// The interrupt is hard-coded to a specific GSI
24    Gsi(u32),
25
26    /// The interrupt is linked to a link object. This object will have `_PRS`, `_CRS` fields and a `_SRS` method
27    /// that can be used to allocate the interrupt. Note that some platforms (e.g. QEMU's q35 chipset) use link
28    /// objects but do not support changing the interrupt that it's linked to (i.e. `_SRS` doesn't do anything).
29    /*
30     * The actual object itself will just be a `Device`, and we need paths to its children objects to do
31     * anything useful, so we just store the resolved name here.
32     */
33    LinkObject(AmlName),
34}
35
36#[derive(Debug)]
37pub struct PciRoute {
38    device: u16,
39    function: u16,
40    pin: Pin,
41    route_type: PciRouteType,
42}
43
44/// A `PciRoutingTable` is used to interpret the data in a `_PRT` object, which provides a mapping
45/// from PCI interrupt pins to the inputs of the interrupt controller. One of these objects must be
46/// present under each PCI root bridge, and consists of a package of packages, each of which describes the
47/// mapping of a single PCI interrupt pin.
48#[derive(Debug)]
49pub struct PciRoutingTable {
50    entries: Vec<PciRoute>,
51}
52
53impl PciRoutingTable {
54    /// Construct a `PciRoutingTable` from a path to a `_PRT` object. Returns
55    /// `AmlError::IncompatibleValueConversion` if the value passed is not a package, or if any of the values
56    /// within it are not packages. Returns the various `AmlError::Prt*` errors if the internal structure of the
57    /// entries is invalid.
58    pub fn from_prt_path(
59        prt_path: &AmlName,
60        context: &mut AmlContext,
61    ) -> Result<PciRoutingTable, AmlError> {
62        let mut entries = Vec::new();
63
64        let prt = context.invoke_method(&prt_path, Args::default())?;
65        if let AmlValue::Package(ref inner_values) = prt {
66            for value in inner_values {
67                if let AmlValue::Package(pin_package) = value {
68                    /*
69                     * Each inner package has the following structure:
70                     *   | Field      | Type      | Description                                               |
71                     *   | -----------|-----------|-----------------------------------------------------------|
72                     *   | Address    | Dword     | Address of the device. Same format as _ADR objects (high  |
73                     *   |            |           | word = #device, low word = #function)                     |
74                     *   | -----------|-----------|-----------------------------------------------------------|
75                     *   | Pin        | Byte      | The PCI pin (0 = INTA, 1 = INTB, 2 = INTC, 3 = INTD)      |
76                     *   | -----------|-----------|-----------------------------------------------------------|
77                     *   | Source     | Byte or   | Name of the device that allocates the interrupt to which  |
78                     *   |            | NamePath  | the above pin is connected. Can be fully qualified,       |
79                     *   |            |           | relative, or a simple NameSeg that utilizes namespace     |
80                     *   |            |           | search rules. Instead, if this is a byte value of 0, the  |
81                     *   |            |           | interrupt is allocated out of the GSI pool, and Source    |
82                     *   |            |           | Index should be utilised.                                 |
83                     *   | -----------|-----------|-----------------------------------------------------------|
84                     *   | Source     | Dword     | Index that indicates which resource descriptor in the     |
85                     *   | Index      |           | resource template of the device pointed to in the Source  |
86                     *   |            |           | field this interrupt is allocated from. If the Source     |
87                     *   |            |           | is zero, then this field is the GSI number to which the   |
88                     *   |            |           | pin is connected.                                         |
89                     *   | -----------|-----------|-----------------------------------------------------------|
90                     */
91                    let address = pin_package[0].as_integer(context)?;
92                    let device = address
93                        .get_bits(16..32)
94                        .try_into()
95                        .map_err(|_| AmlError::PrtInvalidAddress)?;
96                    let function = address
97                        .get_bits(0..16)
98                        .try_into()
99                        .map_err(|_| AmlError::PrtInvalidAddress)?;
100                    let pin = match pin_package[1].as_integer(context)? {
101                        0 => Pin::IntA,
102                        1 => Pin::IntB,
103                        2 => Pin::IntC,
104                        3 => Pin::IntD,
105                        _ => return Err(AmlError::PrtInvalidPin),
106                    };
107
108                    match pin_package[2] {
109                        AmlValue::Integer(0) => {
110                            /*
111                             * The Source Index field contains the GSI number that this interrupt is attached
112                             * to.
113                             */
114                            entries.push(PciRoute {
115                                device,
116                                function,
117                                pin,
118                                route_type: PciRouteType::Gsi(
119                                    pin_package[3]
120                                        .as_integer(context)?
121                                        .try_into()
122                                        .map_err(|_| AmlError::PrtInvalidGsi)?,
123                                ),
124                            });
125                        }
126                        AmlValue::String(ref name) => {
127                            let link_object_name = context
128                                .namespace
129                                .search_for_level(&AmlName::from_str(name)?, &prt_path)?;
130                            entries.push(PciRoute {
131                                device,
132                                function,
133                                pin,
134                                route_type: PciRouteType::LinkObject(link_object_name),
135                            });
136                        }
137                        _ => return Err(AmlError::PrtInvalidSource),
138                    }
139                } else {
140                    return Err(AmlError::IncompatibleValueConversion {
141                        current: value.type_of(),
142                        target: AmlType::Package,
143                    });
144                }
145            }
146
147            Ok(PciRoutingTable { entries })
148        } else {
149            Err(AmlError::IncompatibleValueConversion {
150                current: prt.type_of(),
151                target: AmlType::Package,
152            })
153        }
154    }
155
156    /// Get the interrupt input that a given PCI interrupt pin is wired to. Returns `AmlError::PrtNoEntry` if the
157    /// PRT doesn't contain an entry for the given address + pin.
158    pub fn route(
159        &self,
160        device: u16,
161        function: u16,
162        pin: Pin,
163        context: &mut AmlContext,
164    ) -> Result<IrqDescriptor, AmlError> {
165        let entry = self
166            .entries
167            .iter()
168            .find(|entry| {
169                entry.device == device
170                    && (entry.function == 0xffff || entry.function == function)
171                    && entry.pin == pin
172            })
173            .ok_or(AmlError::PrtNoEntry)?;
174
175        match entry.route_type {
176            PciRouteType::Gsi(gsi) => Ok(IrqDescriptor {
177                is_consumer: true,
178                trigger: InterruptTrigger::Level,
179                polarity: InterruptPolarity::ActiveLow,
180                is_shared: true,
181                is_wake_capable: false,
182                irq: gsi,
183            }),
184            PciRouteType::LinkObject(ref name) => {
185                let path = AmlName::from_str("_CRS").unwrap().resolve(name)?;
186                let link_crs = context.invoke_method(&path, Args::EMPTY)?;
187
188                let resources = resource::resource_descriptor_list(&link_crs)?;
189                match resources.as_slice() {
190                    [Resource::Irq(descriptor)] => Ok(descriptor.clone()),
191                    _ => Err(AmlError::UnexpectedResourceType),
192                }
193            }
194        }
195    }
196}