board_inspector: add interrupt pin routing and usage

This patch adds interrupt pin related information into the board XML,
including:

  * The PCI routing table in ACPI DSDT/SSDT are parsed and generated into
    the board XML as "interrupt_pin_routing" nodes.

  * IRQs encoded in _CRS directly are represented as resources of type
    "irq".

  * Interrupt lines (i.e. INTx#) of PCI devices are represented as
    resources of type "interrupt_pin". When the PCI routing table is
    available, the corresponding interrupt line is identified and
    represented as the "source" attribute of the resource node.

Due to the existence of vIOAPIC in ACRN VMs, the board inspector interprets
the \_PIC method with parameter 1 to inform the ACPI namespace that the
interrupt model should be in APIC mode.

v1 -> v2:
  * Remove the msi_enable variable which is defined but never used.

Tracked-On: #6287
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
This commit is contained in:
Junjie Mao 2021-07-27 10:16:32 +08:00 committed by wenlingz
parent a3aa0797b1
commit 26021bd467
5 changed files with 89 additions and 22 deletions

View File

@ -611,6 +611,9 @@ def rdt_factory(field_list):
for f in self._fields_:
yield getattr(self, f[0])
def __getitem__(self, index):
return getattr(self, self._fields_[index][0])
class ResourceData(cdata.Struct):
_pack_ = 1
_fields_ = [

View File

@ -5,8 +5,10 @@
import logging
import lxml.etree
from collections import defaultdict
from acpiparser import parse_dsdt
from acpiparser import parse_dsdt, parse_resource_data, parse_pci_routing
import acpiparser.aml.context as context
from acpiparser.aml.interpreter import ConcreteInterpreter
from acpiparser.aml.exception import UndefinedSymbol, FutureWork
from acpiparser.rdt import *
@ -68,19 +70,20 @@ def get_device_element(devices_node, namepath, hid):
element.set("id", hid)
return element
def parse_irq(item, elem):
add_child(elem, "resource", type="irq", int=hex(item._INT))
def parse_irq(idx, item, elem):
irqs = ", ".join(map(str, item.irqs))
add_child(elem, "resource", id=f"res{idx}", type="irq", int=irqs)
def parse_io_port(item, elem):
add_child(elem, "resource", type="io_port", min=hex(item._MIN), max=hex(item._MAX), len=hex(item._LEN))
def parse_io_port(idx, item, elem):
add_child(elem, "resource", id=f"res{idx}", type="io_port", min=hex(item._MIN), max=hex(item._MAX), len=hex(item._LEN))
def parse_fixed_io_port(item, elem):
add_child(elem, "resource", type="io_port", min=hex(item._BAS), max=hex(item._BAS + item._LEN - 1), len=hex(item._LEN))
def parse_fixed_io_port(idx, item, elem):
add_child(elem, "resource", id=f"res{idx}", type="io_port", min=hex(item._BAS), max=hex(item._BAS + item._LEN - 1), len=hex(item._LEN))
def parse_fixed_memory_range(item, elem):
add_child(elem, "resource", type="memory", min=hex(item._BAS), max=hex(item._BAS + item._LEN - 1), len=hex(item._LEN))
def parse_fixed_memory_range(idx, item, elem):
add_child(elem, "resource", id=f"res{idx}", type="memory", min=hex(item._BAS), max=hex(item._BAS + item._LEN - 1), len=hex(item._LEN))
def parse_address_space_resource(item, elem):
def parse_address_space_resource(idx, item, elem):
if item._TYP == 0:
typ = "memory"
elif item._TYP == 1:
@ -89,17 +92,17 @@ def parse_address_space_resource(item, elem):
typ = "bus_number"
else:
typ = "custom"
add_child(elem, "resource", type=typ, min=hex(item._MIN), max=hex(item._MAX), len=hex(item._LEN))
add_child(elem, "resource", id=f"res{idx}", type=typ, min=hex(item._MIN), max=hex(item._MAX), len=hex(item._LEN))
def parse_extended_irq(item, elem):
for irq in item._INT:
add_child(elem, "resource", type="irq", int=hex(irq))
def parse_extended_irq(idx, item, elem):
irqs = ", ".join(map(str, item._INT))
add_child(elem, "resource", id=f"res{idx}", type="irq", int=irqs)
resource_parsers = {
(0, SMALL_RESOURCE_ITEM_IRQ_FORMAT): parse_irq,
(0, SMALL_RESOURCE_ITEM_IO_PORT): parse_io_port,
(0, SMALL_RESOURCE_ITEM_FIXED_LOCATION_IO_PORT): parse_fixed_io_port,
(0, SMALL_RESOURCE_ITEM_END_TAG): (lambda x,y: None),
(0, SMALL_RESOURCE_ITEM_END_TAG): (lambda x,y,z: None),
(1, LARGE_RESOURCE_ITEM_32BIT_FIXED_MEMORY_RANGE): parse_fixed_memory_range,
(1, LARGE_RESOURCE_ITEM_ADDRESS_SPACE_RESOURCE): parse_address_space_resource,
(1, LARGE_RESOURCE_ITEM_WORD_ADDRESS_SPACE): parse_address_space_resource,
@ -173,16 +176,41 @@ def fetch_device_info(devices_node, interpreter, namepath):
data = interpreter.interpret_method_call(f"{namepath}._CRS").get()
rdt = parse_resource_data(data)
for item in rdt.items:
for idx, item in enumerate(rdt.items):
p = (item.type, item.name)
if p in resource_parsers.keys():
resource_parsers[p](item, element)
resource_parsers[p](idx, item, element)
else:
add_child(element, "resource", type=item.__class__.__name__)
add_child(element, "resource", type=item.__class__.__name__, id=f"res{idx}")
# PCI interrupt routing
if interpreter.context.has_symbol(f"{namepath}._PRT"):
interpreter.interpret_method_call(f"{namepath}._PRT")
pkg = interpreter.interpret_method_call(f"{namepath}._PRT")
prt = parse_pci_routing(pkg)
prt_info = defaultdict(lambda: {})
for mapping in prt:
if isinstance(mapping.source, int):
assert mapping.source == 0, "A _PRT mapping package should not contain a byte of non-zero as source"
prt_info[mapping.address][mapping.pin] = mapping.source_index
elif isinstance(mapping.source, context.DeviceDecl):
prt_info[mapping.address][mapping.pin] = (mapping.source.name, mapping.source_index)
else:
logging.warning(f"The _PRT of {namepath} has a mapping with invalid source {mapping.source}")
pin_routing_element = add_child(element, "interrupt_pin_routing")
for address, pins in prt_info.items():
mapping_element = add_child(pin_routing_element, "routing", address=hex(address))
pin_names = {
0: "INTA#",
1: "INTB#",
2: "INTC#",
3: "INTD#",
}
for pin, info in pins.items():
if isinstance(info, int):
add_child(mapping_element, "mapping", pin=pin_names[pin], source=str(info))
else:
add_child(mapping_element, "mapping", pin=pin_names[pin], source=info[0], index=str(info[1]))
except FutureWork:
pass
@ -198,6 +226,14 @@ def extract(board_etree):
return
interpreter = ConcreteInterpreter(namespace)
# With IOAPIC, Linux kernel will choose APIC mode as the IRQ model. Evalaute the \_PIC method (if exists) to inform the ACPI
# namespace of this.
try:
interpreter.interpret_method_call("\\_PIC", 1)
except:
logging.info(f"\\_PIC is not evaluated.")
for device in sorted(namespace.devices, key=lambda x:x.name):
try:
fetch_device_info(devices_node, interpreter, device.name)

View File

@ -15,6 +15,13 @@ from extractors.helpers import add_child, get_node
PCI_ROOT_PATH = "/sys/devices/pci0000:00"
bdf_regex = re.compile(r"^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2}).([0-7]{1})$")
interrupt_pin_names = {
1: "INTA#",
2: "INTB#",
3: "INTC#",
4: "INTD#",
}
def collect_hostbridge_resources(bus_node):
with open("/proc/iomem", "r") as f:
for line in f.readlines():
@ -114,6 +121,17 @@ def parse_device(bus_node, device_path):
if cap.name in cap_parsers:
cap_parsers[cap.name](cap_node, cap)
# Interrupt pin
pin = cfg.header.interrupt_pin
if pin > 0 and pin <= 4:
pin_name = interrupt_pin_names[pin]
res_node = add_child(device_node, "resource", type="interrupt_pin", pin=pin_name)
prt_address = hex(int(device_node.get("address"), 16) | 0xffff)
mapping = device_node.xpath(f"../interrupt_pin_routing/routing[@address='{prt_address}']/mapping[@pin='{pin_name}']")
if len(mapping) > 0:
res_node.set("source", mapping[0].get("source"))
# Secondary bus
if cfg.header.header_type == 1:
# According to section 3.2.5.6, PCI to PCI Bridge Architecture Specification, the I/O Limit register contains a
@ -134,6 +152,14 @@ def parse_device(bus_node, device_path):
min=hex(memory_base), max=hex(memory_end), len=hex(memory_end - memory_base + 1))
secondary_bus_node = add_child(device_node, "bus", type="pci", address=hex(cfg.header.secondary_bus_number))
# If a PCI routing table is provided for the root port / switch, move the routing table down to the bus node, in
# order to align the relative position of devices and routing tables.
prt = device_node.find("interrupt_pin_routing")
if prt is not None:
device_node.remove(prt)
secondary_bus_node.append(prt)
return secondary_bus_node
return device_node

View File

@ -11,7 +11,7 @@ def getkey(child):
if typ in ["memory", "io_port"]:
return int(node.get("min"), base=16)
elif typ == "irq":
return int(node.get("int"), base=16)
return int(node.get("int").split(", ")[0])
else:
return 0
@ -22,7 +22,9 @@ def getkey(child):
else:
return 0xFFFFFFFF
tags = ["vendor", "identifier", "subsystem_vendor", "subsystem_identifier", "class", "acpi_object", "status", "resource", "capability", "bus", "device"]
tags = ["vendor", "identifier", "subsystem_vendor", "subsystem_identifier", "class",
"acpi_object", "status",
"resource", "capability", "interrupt_pin_routing", "bus", "device"]
if child.tag == "resource":
return (tags.index(child.tag), child.get("type"), resource_subkey(child))

View File

@ -204,7 +204,7 @@ def get_devs_mem_passthrough(board_etree, scenario_etree):
def get_pci_hole_native(board_etree):
resources = board_etree.xpath(f"//bus[@type = 'pci']/device[@address]/resource[@type = 'memory' and @len != '0x0']")
resources_hostbridge = board_etree.xpath("//bus[@address = '0x0']/resource[@type = 'memory' and @len != '0x0' and not(@id) and not(@width)]")
resources_hostbridge = board_etree.xpath("//bus[@address = '0x0']/resource[@type = 'memory' and @len != '0x0' and not(starts-with(@id, 'bar')) and not(@width)]")
low_mem = set()
high_mem = set()
for resource_hostbridge in resources_hostbridge: