diff --git a/misc/config_tools/board_inspector/extractors/50-acpi.py b/misc/config_tools/board_inspector/extractors/50-acpi.py new file mode 100644 index 000000000..fea1a183a --- /dev/null +++ b/misc/config_tools/board_inspector/extractors/50-acpi.py @@ -0,0 +1,202 @@ +# Copyright (C) 2021 Intel Corporation. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +import logging +import lxml.etree + +from acpiparser import parse_dsdt +from acpiparser.aml.interpreter import ConcreteInterpreter +from acpiparser.aml.exception import UndefinedSymbol, FutureWork +from acpiparser.rdt import * + +from extractors.helpers import add_child, get_node + +def parse_eisa_id(eisa_id): + chars = [ + (eisa_id & 0x7c) >> 2, # Bit 6:2 of the first byte + ((eisa_id & 0x3) << 3) | ((eisa_id & 0xe000) >> 13), # Bit 1:0 of the first byte and bit 7:5 of the second + (eisa_id & 0x1F00) >> 8, # Bit 4:0 of the second byte + (eisa_id & 0x00F00000) >> 20, # Bit 7:4 of the third byte + (eisa_id & 0x000F0000) >> 16, # Bit 3:0 of the third byte + (eisa_id & 0xF0000000) >> 28, # Bit 7:4 of the fourth byte + (eisa_id & 0x0F000000) >> 24, # Bit 3:0 of the fourth byte + ] + if all(map(lambda x:x <= (ord('Z') - 0x40), chars[:3])): + manufacturer = ''.join(map(lambda x: chr(x + 0x40), chars[:3])) + product = ''.join(map(lambda x: "%X" % x, chars[3:6])) + revision = "%X" % chars[6] + return manufacturer + product + revision + else: + return None + +predefined_nameseg = { + "_SB_": ("bus", "system"), + "_TZ_": ("thermalzone", None), +} + +buses = { + "PNP0A03": "pci", + "PNP0A08": "pci", +} + +def get_device_element(devices_node, namepath, hid): + assert namepath.startswith("\\") + namesegs = namepath[1:].split(".") + + element = devices_node + for i,nameseg in enumerate(namesegs): + buspath = f"\\{'.'.join(namesegs[:(i+1)])}" + tag, typ = "device", None + if nameseg in predefined_nameseg.keys(): + tag, typ = predefined_nameseg[nameseg] + next_element = None + for child in element: + acpi_object = get_node(child, "acpi_object") + if acpi_object is not None and acpi_object.text == buspath: + next_element = child + break + if next_element is None: + next_element = add_child(element, tag, None) + add_child(next_element, "acpi_object", buspath) + if typ: + next_element.set("type", typ) + element = next_element + + if hid: + element.set("id", hid) + return element + +def parse_irq(item, elem): + add_child(elem, "resource", type="irq", int=hex(item._INT)) + +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_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_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_address_space_resource(item, elem): + if item._TYP == 0: + typ = "memory" + elif item._TYP == 1: + typ = "io_port" + elif item._TYP == 2: + typ = "bus_number" + else: + typ = "custom" + add_child(elem, "resource", 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)) + +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), + (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, + (1, LARGE_RESOURCE_ITEM_EXTENDED_INTERRUPT): parse_extended_irq, + (1, LARGE_RESOURCE_ITEM_QWORD_ADDRESS_SPACE): parse_address_space_resource, + (1, LARGE_RESOURCE_ITEM_EXTENDED_ADDRESS_SPACE): parse_address_space_resource, +} + +def fetch_device_info(devices_node, interpreter, namepath): + logging.info(f"Fetch information about device object {namepath}") + + try: + # Check if an _INI method exists + try: + interpreter.interpret_method_call(namepath + "._INI") + except UndefinedSymbol: + pass + + sta = None + if interpreter.context.has_symbol(f"{namepath}._STA"): + sta = interpreter.interpret_method_call(f"{namepath}._STA").get() + if sta & 0x1 == 0: + return + + # Hardware ID + hid = "" + if interpreter.context.has_symbol(f"{namepath}._HID"): + hid = interpreter.interpret_method_call(f"{namepath}._HID").get() + if isinstance(hid, str): + pass + elif isinstance(hid, int): + eisa_id = parse_eisa_id(hid) + if eisa_id: + hid = eisa_id + else: + hid = hex(hid) + else: + hid = "" + + # Create the XML element for the device and create its ancestors if necessary + element = get_device_element(devices_node, namepath, hid) + if hid in buses.keys(): + element.tag = "bus" + element.set("type", buses[hid]) + + # Address + if interpreter.context.has_symbol(f"{namepath}._ADR"): + adr = interpreter.interpret_method_call(f"{namepath}._ADR").get() + if isinstance(adr, int): + adr = hex(adr) + if len(element.xpath(f"../*[@address='{adr}']")) > 0: + logging.warning(f"{namepath} has siblings with duplicated address {adr}.") + else: + element.set("address", hex(adr) if isinstance(adr, int) else adr) + + # Status + if sta is not None: + status = add_child(element, "status") + + add_child(status, "present", "y" if sta & 0x1 != 0 else "n") + add_child(status, "enabled", "y" if sta & 0x2 != 0 else "n") + add_child(status, "functioning", "y" if sta & 0x8 != 0 else "n") + + # Resources + if interpreter.context.has_symbol(f"{namepath}._CRS"): + data = interpreter.interpret_method_call(f"{namepath}._CRS").get() + rdt = parse_resource_data(data) + + for item in rdt.items: + p = (item.type, item.name) + if p in resource_parsers.keys(): + resource_parsers[p](item, element) + else: + add_child(element, "resource", type=item.__class__.__name__) + + # PCI interrupt routing + if interpreter.context.has_symbol(f"{namepath}._PRT"): + interpreter.interpret_method_call(f"{namepath}._PRT") + + except FutureWork: + pass + +def extract(board_etree): + devices_node = get_node(board_etree, "//devices") + + try: + namespace = parse_dsdt() + except Exception as e: + logging.info(f"Parse ACPI DSDT/SSDT failed: {str(e)}") + logging.info(f"Will not extract information from ACPI DSDT/SSDT") + return + + interpreter = ConcreteInterpreter(namespace) + for device in sorted(namespace.devices, key=lambda x:x.name): + try: + fetch_device_info(devices_node, interpreter, device.name) + except Exception as e: + logging.info(f"Fetch information about device object {device.name} failed: {str(e)}") + +advanced = True diff --git a/misc/config_tools/board_inspector/extractors/60-pci.py b/misc/config_tools/board_inspector/extractors/60-pci.py new file mode 100644 index 000000000..0e199e92e --- /dev/null +++ b/misc/config_tools/board_inspector/extractors/60-pci.py @@ -0,0 +1,150 @@ +# Copyright (C) 2021 Intel Corporation. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +import os +import re +import logging +import lxml.etree + +from pcieparser import parse_config_space +from pcieparser.header import IOBar, MemoryBar32, MemoryBar64 +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})$") + +def collect_hostbridge_resources(bus_node): + with open("/proc/iomem", "r") as f: + for line in f.readlines(): + fields = line.strip().split(" : ") + if fields[1] == "PCI Bus 0000:00": + begin, end = tuple(map(lambda x: int(f"0x{x}", base=16), fields[0].split("-"))) + add_child(bus_node, "resource", type="memory", min=hex(begin), max=hex(end), len=hex(end - begin + 1)) + +def parse_msi(cap_node, cap_struct): + if cap_struct.multiple_message_capable > 0: + multiple_message_node = add_child(cap_node, "capability", id="multiple-message") + add_child(multiple_message_node, "count", str(1 << cap_struct.multiple_message_capable)) + + if cap_struct.address_64bit: + add_child(cap_node, "capability", id="64-bit address") + + if cap_struct.per_vector_masking_capable: + add_child(cap_node, "capability", id="per-vector masking") + +cap_parsers = { + "MSI": parse_msi, +} + +def parse_device(bus_node, device_path): + device_name = os.path.basename(device_path) + if device_name == "0000:00:00.0": + device_node = bus_node + else: + m = bdf_regex.match(device_name) + device, function = int(m.group(3), base=16), int(m.group(4), base=16) + adr = hex((device << 16) + function) + device_node = get_node(bus_node, f"./device[@address='{adr}']") + if device_node is None: + device_node = add_child(bus_node, "device", None, address=adr) + + cfg = parse_config_space(device_path) + + # Device identifiers + vendor_id = "0x{:04x}".format(cfg.header.vendor_id) + device_id = "0x{:04x}".format(cfg.header.device_id) + class_code = "0x{:06x}".format(cfg.header.class_code) + if device_node.get("id") is None: + device_node.set("id", device_id) + add_child(device_node, "vendor", vendor_id) + add_child(device_node, "identifier", device_id) + add_child(device_node, "class", class_code) + if cfg.header.header_type == 0: + subvendor_id = "0x{:04x}".format(cfg.header.subsystem_vendor_id) + subdevice_id = "0x{:04x}".format(cfg.header.subsystem_device_id) + add_child(device_node, "subsystem_vendor", subvendor_id) + add_child(device_node, "subsystem_identifier", subdevice_id) + + # BARs + idx = 0 + for bar in cfg.header.bars: + resource_path = os.path.join(device_path, f"resource{idx}") + resource_type = bar.resource_type + base = bar.base + if os.path.exists(resource_path): + resource_node = get_node(device_node, f"./resource[@type = '{resource_type}' and @min = '{hex(base)}']") + if resource_node is None: + size = os.path.getsize(resource_path) + resource_node = add_child(device_node, "resource", None, type=resource_type, min=hex(base), max=hex(base + size - 1), len=hex(size)) + resource_node.set("id", f"bar{idx}") + if isinstance(bar, MemoryBar32): + resource_node.set("width", "32") + resource_node.set("prefetchable", str(bar.prefetchable)) + elif isinstance(bar, MemoryBar64): + resource_node.set("width", "64") + resource_node.set("prefetchable", str(bar.prefetchable)) + elif bar.base != 0: + logging.error(f"Cannot detect the size of BAR {idx}") + if isinstance(bar, MemoryBar64): + idx += 2 + else: + idx += 1 + + # Capabilities + for cap in cfg.caps: + cap_node = add_child(device_node, "capability", id=cap.name) + if cap.name in cap_parsers: + cap_parsers[cap.name](cap_node, cap) + + for cap in cfg.extcaps: + cap_node = add_child(device_node, "capability", id=cap.name) + if cap.name in cap_parsers: + cap_parsers[cap.name](cap_node, cap) + + # 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 + # value smaller than the I/O Base register if there are no I/O addresses on the secondary side. + io_base = (cfg.header.io_base_upper_16_bits << 16) | ((cfg.header.io_base >> 4) << 12) + io_end = (cfg.header.io_limit_upper_16_bits << 16) | ((cfg.header.io_limit >> 4) << 12) | 0xfff + if io_base <= io_end: + add_child(device_node, "resource", type="io_port", + min=hex(io_base), max=hex(io_end), len=hex(io_end - io_base + 1)) + + # According to section 3.2.5.8, PCI to PCI Bridge Architecture Specification, the Memory Limit register contains + # a value smaller than the Memory Base register if there are no memory-mapped I/O addresses on the secondary + # side. + if cfg.header.memory_base <= cfg.header.memory_limit: + memory_base = (cfg.header.memory_base >> 4) << 20 + memory_end = ((cfg.header.memory_limit >> 4) << 20) | 0xfffff + add_child(device_node, "resource", type="memory", + 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)) + return secondary_bus_node + + return device_node + +def enum_devices(bus_node, root_path): + device_names = sorted(filter(lambda x:bdf_regex.match(x) != None, os.listdir(root_path))) + for device_name in device_names: + p = os.path.join(root_path, device_name) + device_node = parse_device(bus_node, p) + enum_devices(device_node, p) + +def extract(board_etree): + bus_node = get_node(board_etree, "//bus[@type='pci']") + if bus_node is None: + devices_node = get_node(board_etree, "//devices") + bus_node = add_child(devices_node, "bus", type="pci", address="0x0") + collect_hostbridge_resources(bus_node) + else: + # Assume there is only one device object in the ACPI DSDT that represents a PCI bridge (which should be the host + # bridge in this case). If the ACPI table does not provide an _ADR object, add the default address of the host + # bridge (i.e. bus 0). + if bus_node.get("address") is None: + bus_node.set("address", "0x0") + + enum_devices(bus_node, PCI_ROOT_PATH) diff --git a/misc/config_tools/board_inspector/extractors/80-lookup.py b/misc/config_tools/board_inspector/extractors/80-lookup.py new file mode 100644 index 000000000..90549255e --- /dev/null +++ b/misc/config_tools/board_inspector/extractors/80-lookup.py @@ -0,0 +1,123 @@ +# Copyright (C) 2021 Intel Corporation. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +import os +import logging + +from extractors.helpers import get_node + +PCI_IDS_PATHS = [ + "/usr/share/misc/pci.ids", + "/usr/share/hwdata/pci.ids", +] + +class PCI_IDs: + def __init__(self, f): + names = {} + vendor_id = None + device_id = None + classes = {} + class_id = None + + for line in f.readlines(): + line = line.strip("\n") + if line == "" or line.startswith("#"): + continue + + if line.startswith("\t\t"): + if device_id is not None: + parts = line.strip().split(" ") + subvendor_id, subdevice_id = tuple(map(lambda x: int(x, base=16), parts[0].split(" "))) + desc = " ".join(parts[1:]) + names[(vendor_id, device_id, subvendor_id, subdevice_id)] = desc + elif line.startswith("\t"): + if vendor_id is not None: + parts = line.strip().split(" ") + device_id = int(parts[0], base=16) + desc = " ".join(parts[1:]) + names[(vendor_id, device_id)] = desc + elif class_id is not None: + parts = line.strip().split(" ") + subclass_id = int(parts[0], base=16) + desc = " ".join(parts[1:]) + classes[(class_id, subclass_id)] = desc + elif line.startswith("C"): + parts = line.strip().split(" ") + class_id = int(parts[0][2:], base=16) + vendor_id = None + device_id = None + desc = " ".join(parts[1:]) + classes[(class_id,)] = desc + else: + parts = line.strip().split(" ") + vendor_id = int(parts[0], base=16) + device_id = None + class_id = None + desc = " ".join(parts[1:]) + names[(vendor_id,)] = desc + + self.__names = names + self.__classes = classes + + def lookup(self, vendor_id, device_id, subsystem_vendor_id, subsystem_device_id, class_code): + acc = [] + + # Class + if class_code: + class_id = (class_code >> 16) + subclass_id = ((class_code >> 8) & 0xFF) + if (class_id, subclass_id) in self.__classes.keys(): + acc.append(self.__classes[(class_id, subclass_id)] + ":") + elif (class_id,) in self.__classes.keys(): + acc.append(self.__classes[(class_id,)] + ":") + + # Vendor + if vendor_id: + if (vendor_id,) in self.__names.keys(): + acc.append(self.__names[(vendor_id,)]) + + # Device + if vendor_id and device_id: + if (vendor_id, device_id) in self.__names.keys(): + acc.append(self.__names[(vendor_id, device_id)]) + + return " ".join(acc) + +def lookup_pci_device(element, ids): + vendor_id = get_node(element, "vendor/text()") + device_id = get_node(element, "identifier/text()") + subsystem_vendor_id = get_node(element, "subsystem_vendor/text()") + subsystem_device_id = get_node(element, "subsystem_identifier/text()") + class_code = get_node(element, "class/text()") + + args = [vendor_id, device_id, subsystem_vendor_id, subsystem_device_id, class_code] + desc = ids.lookup(*list(map(lambda x: int(x, base=16) if x else None, args))) + + if desc: + element.set("description", desc) + +def lookup_pci_devices(board_etree): + # Lookup names of PCI devices from pci.ids if possible + pci_id_path = None + for path in PCI_IDS_PATHS: + if os.path.exists(path): + pci_id_path = path + + if pci_id_path: + with open(pci_id_path, "r") as f: + ids = PCI_IDs(f) + + devices = board_etree.xpath("//device") + for device in devices: + lookup_pci_device(device, ids) + + buses = board_etree.xpath("//bus") + for bus in buses: + lookup_pci_device(bus, ids) + else: + logging.info(f"Cannot find pci.ids under /usr/share. PCI device names will not be available.") + +def extract(board_etree): + lookup_pci_devices(board_etree) diff --git a/misc/config_tools/board_inspector/extractors/90-sorting.py b/misc/config_tools/board_inspector/extractors/90-sorting.py new file mode 100644 index 000000000..29d6539ff --- /dev/null +++ b/misc/config_tools/board_inspector/extractors/90-sorting.py @@ -0,0 +1,41 @@ +# Copyright (C) 2021 Intel Corporation. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +import lxml.etree + +def getkey(child): + def resource_subkey(node): + typ = node.get("type") + if typ in ["memory", "io_port"]: + return int(node.get("min"), base=16) + elif typ == "irq": + return int(node.get("int"), base=16) + else: + return 0 + + def device_subkey(node): + adr = node.get("address") + if adr is not None: + return int(adr, base=16) + else: + return 0xFFFFFFFF + + tags = ["vendor", "identifier", "subsystem_vendor", "subsystem_identifier", "class", "acpi_object", "status", "resource", "capability", "bus", "device"] + + if child.tag == "resource": + return (tags.index(child.tag), child.get("type"), resource_subkey(child)) + elif child.tag == "device": + return (tags.index(child.tag), device_subkey(child)) + else: + return (tags.index(child.tag),) + +def extract(board_etree): + # Sort children of bus and device nodes + bus_nodes = board_etree.xpath("//bus") + for bus_node in bus_nodes: + bus_node[:] = sorted(bus_node, key=getkey) + device_nodes = board_etree.xpath("//device") + for device_node in device_nodes: + device_node[:] = sorted(device_node, key=getkey) diff --git a/misc/config_tools/board_inspector/pcieparser/extcaps.py b/misc/config_tools/board_inspector/pcieparser/extcaps.py new file mode 100644 index 000000000..d8b5eaa7e --- /dev/null +++ b/misc/config_tools/board_inspector/pcieparser/extcaps.py @@ -0,0 +1,90 @@ +# Copyright (C) 2021 Intel Corporation. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +import ctypes +import copy +import lib.cdata as cdata + +class ExtendedCapability: + # Capability names from PCI Express Base Specification, mostly Table 9-23 + _cap_names_ = { + 0x01: "Advanced Error Reporting", + 0x02: "Virtual Channel", + 0x03: "Device Serial Number", + 0x04: "Power Budgeting", + 0x05: "Root Complex Link Declaration", + 0x06: "Root Complex Internal Link Control", + 0x07: "Root Complex Event Collector Endpoint Association", + 0x08: "Multi-Function Virtual Channel", + 0x09: "Virtual Channel", + 0x0a: "RCRB Header", + 0x0b: "Vendor-Specific Extended", + 0x0c: "Configuration Access Correlation", + 0x0d: "ACS", + 0x0e: "ARI", + 0x0f: "ATS", + 0x10: "SR-IOV", + 0x11: "MR-IOV", + 0x12: "Multicast", + 0x13: "PRI", + 0x15: "Resizable BAR", + 0x16: "DPA", + 0x17: "TPH Requester", + 0x18: "LTR", + 0x19: "Secondary PCI Express", + 0x1a: "PMUX", + 0x1b: "PASID", + 0x1c: "LNR", + 0x1d: "DPC", + 0x1e: "L1 PM Substates", + 0x1f: "TPM", + 0x20: "M-PCIe", + 0x21: "FRS Queueing", + 0x22: "Readiness Time Reporting", + 0x23: "Designated Vendor-Specific", + 0x24: "VF Resizable BAR", + 0x25: "Data Link Feature", + 0x26: "Physical Layer 16.0 GT/s", + 0x27: "Lane Margining at the Receiver", + 0x28: "Hierarchy ID", + 0x29: "NPEM", + 0x2a: "Physical Layer 32.0 GT/s", + 0x2b: "Alternate Protocol", + 0x2c: "SFI", + } + + @property + def name(self): + if self.id in self._cap_names_.keys(): + return self._cap_names_[self.id] + else: + return f"Reserved Extended ({hex(self.id)})" + +class ExtendedCapabilityListRegister(cdata.Struct, ExtendedCapability): + _pack_ = 1 + _fields_ = [ + ('id', ctypes.c_uint32, 16), + ('version', ctypes.c_uint32, 4), + ('next_cap_ptr_raw', ctypes.c_uint32, 12), + ] + + @property + def next_cap_ptr(self): + return self.next_cap_ptr_raw & 0xffc + +# Module API + +def extended_capabilities(data): + buf = ctypes.create_string_buffer(data, len(data)) + cap_ptr = 0x100 + + acc = list() + while cap_ptr != 0: + caplist = ExtendedCapabilityListRegister.from_buffer_copy(buf, cap_ptr) + if caplist.id != 0: + acc.append(caplist) + cap_ptr = caplist.next_cap_ptr + + return acc