diff --git a/misc/config_tools/board_inspector/extractors/60-pci.py b/misc/config_tools/board_inspector/extractors/60-pci.py index 97094d7f4..0481f0efc 100644 --- a/misc/config_tools/board_inspector/extractors/60-pci.py +++ b/misc/config_tools/board_inspector/extractors/60-pci.py @@ -31,7 +31,7 @@ def collect_hostbridge_resources(bus_node, bus_number): 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): +def parse_msi(cap_node, cap_struct, **kwargs): add_child(cap_node, "count", str(1 << cap_struct.multiple_message_capable)) if cap_struct.multiple_message_capable > 0: add_child(cap_node, "capability", id="multiple-message") @@ -42,21 +42,61 @@ def parse_msi(cap_node, cap_struct): if cap_struct.per_vector_masking_capable: add_child(cap_node, "capability", id="per-vector masking") -def parse_msix(cap_node, cap_struct): +def parse_msix(cap_node, cap_struct, **kwargs): add_child(cap_node, "table_size", str(cap_struct.table_size)) add_child(cap_node, "table_bir", str(cap_struct.table_bir)) add_child(cap_node, "table_offset", hex(cap_struct.table_offset_z)) add_child(cap_node, "pba_bir", str(cap_struct.pba_bir)) add_child(cap_node, "pba_offset", hex(cap_struct.pba_offset_z)) +def parse_sriov(cap_node, cap_struct, **kwargs): + device_node = cap_node.getparent() + bus_node = device_node.getparent() + assert bus_node.tag == "bus" and device_node.tag == "device" + + pf_bus_id = int(bus_node.get("address"), 16) + pf_dev_id = int(device_node.get("address"), 16) >> 16 + pf_func_id = int(device_node.get("address"), 16) & 0xffff + pf_bdf = (pf_bus_id << 8) | (pf_dev_id << 3) | pf_func_id + + if cap_struct.vf_enable == 0 or cap_struct.num_vfs == 0: + logging.warning(f"The PCI device at {pf_bus_id:02x}:{pf_dev_id:02x}.{pf_func_id} has SR-IOV capability which is currently disabled.") + logging.warning(f"If that device is intended to provide virtual functions in your scenario, write (as root) the number of VFs to {kwargs['device_path']}/sriov_numvfs and rerun the board inspector.") + + if cap_struct.vf_migration_capable: + vf_migration_cap = add_child(cap_node, "capability", id="vf_migration") + add_child(vf_migration_cap, "vf_migration_interrupt_message_number", str(cap_struct.vf_migration_interrupt_message_number)) + + if cap_struct.ari_capable_hierarchy_preserved: + add_child(cap_node, "capability", id="ari_capable_hierarchy_preserved") + + if cap_struct.vf_10_bit_tag_requester_supported: + add_child(cap_node, "capability", id="vf_10_bit_tag_requester_supported") + + add_child(cap_node, "initial_vfs", str(cap_struct.initial_vfs)) + add_child(cap_node, "total_vfs", str(cap_struct.total_vfs)) + add_child(cap_node, "function_dependency_link", str(cap_struct.function_dependency_link)) + + first_vf_bdf_node = add_child(cap_node, "first_vf_bdf") + first_vf_bdf = pf_bdf + cap_struct.first_vf_offset + add_child(first_vf_bdf_node, "bus", hex(first_vf_bdf >> 8)) + add_child(first_vf_bdf_node, "device", hex((first_vf_bdf >> 3) & 0x1f)) + add_child(first_vf_bdf_node, "function", hex(first_vf_bdf & 0x7)) + + add_child(cap_node, "vf_stride", str(cap_struct.vf_stride)) + add_child(cap_node, "vf_device_id", hex(cap_struct.vf_device_id)) + add_child(cap_node, "supported_page_sizes", hex(cap_struct.supported_page_sizes)) + cap_parsers = { "MSI": parse_msi, "MSI-X": parse_msix, + "SR-IOV": parse_sriov, } def parse_device(bus_node, device_path): device_name = os.path.basename(device_path) cfg = parse_config_space(device_path) + physfn_cfg = parse_config_space(os.path.join(device_path, "physfn")) # There are cases where Linux creates device-like nodes without a file named "config", e.g. when there is a PCIe # non-transparent bridge (NTB) on the physical platform. @@ -85,8 +125,8 @@ def parse_device(bus_node, device_path): logging.info(f"Resuming {device_path} failed: {str(e)}") # Device identifiers - vendor_id = "0x{:04x}".format(cfg.header.vendor_id) - device_id = "0x{:04x}".format(cfg.header.device_id) + vendor_id = "0x{:04x}".format(cfg.header.vendor_id if physfn_cfg is None else physfn_cfg.header.vendor_id) + device_id = "0x{:04x}".format(cfg.header.device_id if physfn_cfg is None else physfn_cfg.get_cap("SR-IOV").vf_device_id) class_code = "0x{:06x}".format(cfg.header.class_code) if device_node.get("id") is None: device_node.set("id", device_id) @@ -100,43 +140,85 @@ def parse_device(bus_node, device_path): 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): - if bar.base == 0: - logging.debug(f"PCI {device_name}: BAR {idx} exists but is programmed with all 0. This device cannot be passed through to any VM.") + if physfn_cfg is None: + 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): + if bar.base == 0: + logging.debug(f"PCI {device_name}: BAR {idx} exists but is programmed with all 0. This device cannot be passed through to any VM.") + else: + 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.debug(f"PCI {device_name}: Cannot detect the size of BAR {idx}") + if isinstance(bar, MemoryBar64): + idx += 2 else: - resource_node = get_node(device_node, f"./resource[@type = '{resource_type}' and @min = '{hex(base)}']") - if resource_node is None: + idx += 1 + else: + pf_path = os.path.realpath(os.path.join(device_path, "physfn")) + pf_name = os.path.basename(pf_path) + m = bdf_regex.match(pf_name) + pf_bus, pf_device, pf_function = int(m.group(2), base=16), int(m.group(3), base=16), int(m.group(4), base=16) + pf_bdf = (pf_bus << 8) | (pf_device << 3) | pf_function + + m = bdf_regex.match(device_name) + vf_bus, vf_device, vf_function = int(m.group(2), base=16), int(m.group(3), base=16), int(m.group(4), base=16) + vf_bdf = (vf_bus << 8) | (vf_device << 3) | vf_function + first_vf_offset = physfn_cfg.get_cap("SR-IOV").first_vf_offset + vf_stride = physfn_cfg.get_cap("SR-IOV").vf_stride + + vf_id = (vf_bdf - (pf_bdf + first_vf_offset)) // vf_stride + + idx = 0 + for bar in physfn_cfg.get_cap("SR-IOV").vf_bars: + resource_path = os.path.join(device_path, f"resource{idx}") + resource_type = bar.resource_type + if os.path.exists(resource_path): + if bar.base == 0: + logging.debug(f"PCI {device_name}: BAR {idx} exists but is programmed with all 0. This device cannot be passed through to any VM.") + else: size = os.path.getsize(resource_path) + base = bar.base + vf_id * size 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.debug(f"PCI {device_name}: Cannot detect the size of BAR {idx}") - if isinstance(bar, MemoryBar64): - idx += 2 - else: - idx += 1 + 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.debug(f"PCI {device_name}: Cannot detect the size of BAR {idx}") + if isinstance(bar, MemoryBar64): + idx += 2 + else: + idx += 1 + + add_child(device_node, "physfn", bus=hex(pf_bus), address=hex((pf_device << 16) | pf_function)) # 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) + cap_parsers[cap.name](cap_node, cap, device_path=device_path) 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) + cap_parsers[cap.name](cap_node, cap, device_path=device_path) # Interrupt pin pin = cfg.header.interrupt_pin diff --git a/misc/config_tools/board_inspector/extractors/90-sorting.py b/misc/config_tools/board_inspector/extractors/90-sorting.py index 1060e6413..c73853291 100644 --- a/misc/config_tools/board_inspector/extractors/90-sorting.py +++ b/misc/config_tools/board_inspector/extractors/90-sorting.py @@ -28,7 +28,7 @@ def getkey(child): tags = ["vendor", "identifier", "subsystem_vendor", "subsystem_identifier", "class", "acpi_object", "compatible_id", "acpi_uid", "aml_template", "status", - "resource", "capability", "interrupt_pin_routing", "dependency", "bus", "device"] + "resource", "capability", "interrupt_pin_routing", "dependency", "bus", "device", "physfn"] if child.tag == "resource": return (tags.index(child.tag), child.get("type"), resource_subkey(child))