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}