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)
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 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 extract_model(processors_node, cpu_id, family_id, model_id, core_type, native_model_id): n = get_node( processors_node, f"//model[family_id='{family_id}' and model_id='{model_id}' and core_type='{core_type}' and native_model_id='{native_model_id}']" ) if n is None: n = add_child(processors_node, "model") add_child(n, "family_id", family_id) add_child(n, "model_id", model_id) add_child(n, "core_type", core_type) add_child(n, "native_model_id", native_model_id) brandstring = b"" for leaf in [0x80000002, 0x80000003, 0x80000004]: leaf_data = parse_cpuid(leaf, 0, cpu_id) brandstring += leaf_data.brandstring n.set("description", brandstring.decode()) leaves = [(1, 0), (7, 0), (0x80000001, 0), (0x80000007, 0)] for leaf in leaves: leaf_data = parse_cpuid(leaf[0], leaf[1], cpu_id) for cap in leaf_data.capability_bits: if getattr(leaf_data, cap) == 1: add_child(n, "capability", id=cap)
def extract_tcc_capabilities(caches_node): try: rtct = parse_rtct() if rtct.version == 1: for entry in rtct.entries: if entry.type == acpiparser.rtct.ACPI_RTCT_V1_TYPE_SoftwareSRAM: cache_node = get_node( caches_node, f"cache[@level='{entry.cache_level}' and processors/processor='{hex(entry.apic_id_tbl[0])}']" ) if cache_node is None: logging.warning( f"Cannot find the level {entry.cache_level} cache of physical processor with apic ID {entry.apic_id_tbl[0]}" ) continue cap = add_child(cache_node, "capability", None, id="Software SRAM") add_child(cap, "start", "0x{:08x}".format(entry.base)) add_child(cap, "end", "0x{:08x}".format(entry.base + entry.size - 1)) add_child(cap, "size", str(entry.size)) elif rtct.version == 2: for entry in rtct.entries: if entry.type == acpiparser.rtct.ACPI_RTCT_V2_TYPE_SoftwareSRAM: cache_node = get_node( caches_node, f"cache[@level='{entry.level}' and @id='{hex(entry.cache_id)}']" ) if cache_node is None: logging.warning( f"Cannot find the level {entry.level} cache with cache ID {entry.cache_id}" ) continue cap = add_child(cache_node, "capability", None, id="Software SRAM") add_child(cap, "start", "0x{:08x}".format(entry.base)) add_child(cap, "end", "0x{:08x}".format(entry.base + entry.size - 1)) add_child(cap, "size", str(entry.size)) except FileNotFoundError: pass
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 ids else "" bus_id = int(get_node(element, "ancestor-or-self::bus[1]/@address"), base=16) dev_func_id = int(get_node(element, "./@address"), base=16) dev_id = dev_func_id >> 16 func_id = dev_func_id & 0xf if element.tag == "bus": if desc: element.set("description", desc) else: if desc: element.set("description", f"{bus_id:02x}:{dev_id:02x}.{func_id} {desc}") else: element.set("description", f"{bus_id:02x}:{dev_id:02x}.{func_id}")
def extract(args, board_etree): try: tables = parse_apic() except Exception as e: logging.warning(f"Parse ACPI tables failed: {str(e)}") logging.warning(f"Will not extract information from ACPI tables") return ioapics_node = get_node(board_etree, "//ioapics") extract_topology(ioapics_node, tables)
def extract(args, board_etree): devices_node = get_node(board_etree, "//devices") try: namespace = parse_dsdt() except Exception as e: logging.warning(f"Parse ACPI DSDT/SSDT failed: {str(e)}") logging.warning(f"Will not extract information from ACPI DSDT/SSDT") 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, args) except Exception as e: logging.info(f"Fetch information about device object {device.name} failed: {str(e)}") visitor = GenerateBinaryVisitor() for dev, objs in device_objects.items(): element = get_node(devices_node, f"//device[acpi_object='{dev}']") if element is not None: tree = builder.DefDevice( builder.PkgLength(), dev, builder.TermList(*list(objs.values()))) add_child(element, "aml_template", visitor.generate(tree).hex()) for dev, deps in device_deps.items(): element = get_node(devices_node, f"//device[acpi_object='{dev}']") if element is not None: for kind, targets in deps.items(): for target in targets: if dev != target: add_child(element, "dependency", target, type=kind)
def extract(args, board_etree): # Assume we only care about PCI devices under domain 0, as the hypervisor only uses BDF (without domain) for device # identification. root_regex = re.compile("pci0000:([0-9a-f]{2})") for root in filter(lambda x: x.startswith("pci"), os.listdir(SYS_DEVICES_PATH)): m = root_regex.match(root) if m: bus_number = int(m.group(1), 16) bus_node = get_node( board_etree, f"//bus[@type='pci' and @address='{hex(bus_number)}']") if bus_node is None: devices_node = get_node(board_etree, "//devices") bus_node = add_child(devices_node, "bus", type="pci", address=hex(bus_number)) collect_hostbridge_resources(bus_node, bus_number) enum_devices(bus_node, os.path.join(SYS_DEVICES_PATH, root))
def extract(args, board_etree): root_node = board_etree.getroot() caches_node = get_node(board_etree, "//caches") extract_topology(root_node, caches_node) extract_tcc_capabilities(caches_node) # Inject the explicitly specified CAT capability if exists if args.add_llc_cat: llc_node = get_node(root_node, "//caches/cache[@level='3']") llc_cat_node = get_node(llc_node, "capability[@id='CAT']") if llc_cat_node is None: llc_cat_node = add_child(llc_node, "capability", None, id="CAT") add_child(llc_cat_node, "capacity_mask_length", str(args.add_llc_cat.capacity_mask_length)) add_child(llc_cat_node, "clos_number", str(args.add_llc_cat.clos_number)) if args.add_llc_cat.has_CDP: add_child(llc_node, "capability", None, id="CDP") else: logging.warning( "The last level cache already reports CAT capability. The explicit settings from the command line options are ignored." )
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)}" )
def extract_model(processors_node, cpu_id, family_id, model_id, core_type, native_model_id): n = get_node(processors_node, f"//model[family_id='{family_id}' and model_id='{model_id}' and core_type='{core_type}' and native_model_id='{native_model_id}']") if n is None: n = add_child(processors_node, "model") add_child(n, "family_id", family_id) add_child(n, "model_id", model_id) add_child(n, "core_type", core_type) add_child(n, "native_model_id", native_model_id) brandstring = b"" for leaf in [0x80000002, 0x80000003, 0x80000004]: leaf_data = parse_cpuid(leaf, 0, cpu_id) brandstring += leaf_data.brandstring n.set("description", re.sub('[^!-~]+', ' ', brandstring.decode()).strip()) leaves = [(1, 0), (7, 0), (0x80000001, 0), (0x80000007, 0)] for leaf in leaves: leaf_data = parse_cpuid(leaf[0], leaf[1], cpu_id) for cap in leaf_data.capability_bits: if getattr(leaf_data, cap) == 1: add_child(n, "capability", id=cap) msr_regs = [MSR_IA32_MISC_ENABLE, MSR_IA32_FEATURE_CONTROL, MSR_IA32_VMX_BASIC, MSR_IA32_VMX_PINBASED_CTLS, MSR_IA32_VMX_PROCBASED_CTLS, MSR_IA32_VMX_EXIT_CTLS, MSR_IA32_VMX_ENTRY_CTLS, MSR_IA32_VMX_MISC, MSR_IA32_VMX_PROCBASED_CTLS2, MSR_IA32_VMX_EPT_VPID_CAP] for msr_reg in msr_regs: msr_data = msr_reg.rdmsr(cpu_id) for cap in msr_data.capability_bits: if getattr(msr_data, cap) == 1: add_child(n, "capability", id=cap) leaves = [(0, 0), (0x80000008, 0)] for leaf in leaves: leaf_data = parse_cpuid(leaf[0], leaf[1], cpu_id) for cap in leaf_data.attribute_bits: add_child(n, "attribute", str(getattr(leaf_data, cap)), id=cap)
def extract(args, board_etree): dev_regex = re.compile(USB_DEVICES_REGEX) for dev in os.listdir(USB_DEVICES_PATH): m = dev_regex.match(dev) if m: d = m.group(0) devpath = os.path.join(USB_DEVICES_PATH, d) with open(os.path.join(devpath, 'devnum'), 'r') as f: devnum = f.read().strip() with open(os.path.join(devpath, 'busnum'), 'r') as f: busnum = f.read().strip() cmd_out = os.popen('lsusb -s {b}:{d}'.format(b=busnum, d=devnum)).read() desc = cmd_out.split(':', maxsplit=1)[1].strip('\n') with open(devpath + '/port/firmware_node/path') as f: acpi_path = f.read().strip() usb_port_node = get_node(board_etree, f"//device[acpi_object='{acpi_path}']") if usb_port_node is not None: add_child(usb_port_node, "usb_device", location=d, description=d + desc)
def extract(args, board_etree): processors_node = get_node(board_etree, "//processors") extract_topology(processors_node)
def parse_device(bus_node, device_path): device_name = os.path.basename(device_path) cfg = parse_config_space(device_path) # 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. if cfg is None: return None 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) for cap in cfg.caps: # If the device is not in D0, power it on and reparse its configuration space. if cap.name == "Power Management" and cap.power_state != 0: logging.info(f"Try resuming {device_path}") try: with open(os.path.join(device_path, "power", "control"), "w") as f: f.write("on") cfg = parse_config_space(device_path) except Exception as e: 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) 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): 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: 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) # 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")) if "index" in mapping[0].keys(): res_node.set("index", mapping[0].get("index")) # 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)) # 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
def extract(args, board_etree): device_classes_node = get_node(board_etree, "//device-classes") extract_topology(device_classes_node)
def extract(board_etree): memory_node = get_node(board_etree, "//memory") extract_layout(memory_node)
def extract(args, board_etree): root_node = board_etree.getroot() caches_node = get_node(board_etree, "//caches") extract_topology(root_node, caches_node) extract_tcc_capabilities(caches_node)
def extract_topology(root_node, caches_node): threads = root_node.xpath("//processors//*[cpu_id]") for thread in threads: subleaf = 0 while True: cpu_id = int(get_node(thread, "cpu_id/text()"), base=16) leaf_4 = parse_cpuid(4, subleaf, cpu_id) cache_type = leaf_4.cache_type if cache_type == 0: break cache_level = leaf_4.cache_level shift_width = leaf_4.max_logical_processors_sharing_cache.bit_length( ) - 1 cache_id = hex( int(get_node(thread, "apic_id/text()"), base=16) >> shift_width ) n = get_node( caches_node, f"cache[@id='{cache_id}' and @type='{cache_type}' and @level='{cache_level}']" ) if n is None: n = add_child(caches_node, "cache", None, level=str(cache_level), id=cache_id, type=str(cache_type)) add_child(n, "cache_size", str(leaf_4.cache_size)) add_child(n, "line_size", str(leaf_4.line_size)) add_child(n, "ways", str(leaf_4.ways)) add_child(n, "sets", str(leaf_4.sets)) add_child(n, "partitions", str(leaf_4.partitions)) add_child(n, "self_initializing", str(leaf_4.self_initializing)) add_child(n, "fully_associative", str(leaf_4.fully_associative)) add_child(n, "write_back_invalidate", str(leaf_4.write_back_invalidate)) add_child(n, "cache_inclusiveness", str(leaf_4.cache_inclusiveness)) add_child(n, "complex_cache_indexing", str(leaf_4.complex_cache_indexing)) add_child(n, "processors") # Check support of Cache Allocation Technology leaf_10 = parse_cpuid(0x10, 0, cpu_id) if cache_level == 2: leaf_10 = parse_cpuid( 0x10, 2, cpu_id) if leaf_10.l2_cache_allocation == 1 else None elif cache_level == 3: leaf_10 = parse_cpuid( 0x10, 1, cpu_id) if leaf_10.l3_cache_allocation == 1 else None else: leaf_10 = None if leaf_10 is not None: cap = add_child(n, "capability", None, id="CAT") add_child(cap, "capacity_mask_length", str(leaf_10.capacity_mask_length)) add_child(cap, "clos_number", str(leaf_10.clos_number)) if leaf_10.code_and_data_prioritization == 1: add_child(n, "capability", None, id="CDP") add_child(get_node(n, "processors"), "processor", get_node(thread, "apic_id/text()")) subleaf += 1 def getkey(n): level = int(n.get("level")) id = int(n.get("id"), base=16) type = int(n.get("type")) return (level, id, type) caches_node[:] = sorted(caches_node, key=getkey)
def get_parent(processors_node, topo_level, topo_id): n = get_node(processors_node, f"//{topo_level}[@id='{topo_id}']") return n
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