def pnp_uart(path, uid, ddn, port, irq): resources = [] cls = rdt.SmallResourceItemIOPort length = ctypes.sizeof(cls) data = bytearray(length) res = cls.from_buffer(data) res.type = 0 res.name = rdt.SMALL_RESOURCE_ITEM_IO_PORT res.length = 7 res._DEC = 1 res._MIN = port res._MAX = port res._ALN = 1 res._LEN = 8 resources.append(data) cls = rdt.SmallResourceItemIRQ_factory(2) length = ctypes.sizeof(cls) data = bytearray(length) res = cls.from_buffer(data) res.type = 0 res.name = rdt.SMALL_RESOURCE_ITEM_IRQ_FORMAT res.length = 2 res._INT = 1 << irq resources.append(data) resources.append(bytes([0x79, 0])) resource_buf = bytearray().join(resources) checksum = (256 - (sum(resource_buf) % 256)) % 256 resource_buf[-1] = checksum uart = builder.DefDevice( builder.PkgLength(), path, builder.TermList( builder.DefName( "_HID", builder.DWordConst(encode_eisa_id("PNP0501"))), builder.DefName( "_UID", builder.build_value(uid)), builder.DefName( "_DDN", builder.String(ddn)), builder.DefName( "_CRS", builder.DefBuffer( builder.PkgLength(), builder.WordConst(len(resource_buf)), builder.ByteList(resource_buf))))) return uart
def gen_dsdt(board_etree, scenario_etree, allocation_etree, vm_id, dest_path): interrupt_pin_ids = { "INTA#": 0, "INTB#": 1, "INTC#": 2, "INTD#": 3, } header = builder.DefBlockHeader( int.from_bytes(bytearray("DSDT", "ascii"), sys.byteorder), # Signature 0x0badbeef, # Length, will calculate later 3, # Revision 0, # Checksum, will calculate later int.from_bytes(bytearray("ACRN ", "ascii"), sys.byteorder), # OEM ID int.from_bytes(bytearray("ACRNDSDT", "ascii"), sys.byteorder), # OEM Table ID 1, # OEM Revision int.from_bytes(bytearray("INTL", "ascii"), sys.byteorder), # Compiler ID 0x20190703, # Compiler Version ) objects = ObjectCollector() prt_packages = [] pci_dev_regex = re.compile(r"([0-9a-f]{2}):([0-1][0-9a-f]{1}).([0-7]) .*") for pci_dev in scenario_etree.xpath( f"//vm[@id='{vm_id}']/pci_devs/pci_dev/text()"): m = pci_dev_regex.match(pci_dev) if m: device_number = int(m.group(2), 16) function_number = int(m.group(3)) bus_number = int(m.group(1), 16) bdf = f"{bus_number:02x}:{device_number:02x}.{function_number}" address = hex((device_number << 16) | (function_number)) device_node = common.get_node( f"//bus[@address='{hex(bus_number)}']/device[@address='{address}']", board_etree) alloc_node = common.get_node( f"/acrn-config/vm[@id='{vm_id}']/device[@name='PTDEV_{bdf}']", allocation_etree) if device_node is not None and alloc_node is not None: assert int(alloc_node.find("bus").text, 16) == 0, "Virtual PCI devices must be on bus 0." vdev = int(alloc_node.find("dev").text, 16) vfunc = int(alloc_node.find("func").text, 16) # The AML object template, with _ADR replaced to vBDF template = device_node.find("aml_template") if template is not None: tree = parse_tree("DefDevice", bytes.fromhex(template.text)) vaddr = (vdev << 16) | vfunc add_or_replace_object( tree, builder.DefName("_ADR", builder.build_value(vaddr))) objects.add_device_object(tree) # The _PRT remapping package, if necessary intr_pin_node = common.get_node( "resource[@type='interrupt_pin']", device_node) virq_node = common.get_node("pt_intx", alloc_node) if intr_pin_node is not None and virq_node is not None: pin_id = interrupt_pin_ids[intr_pin_node.get("pin")] vaddr = (vdev << 16) | 0xffff pirq = int(intr_pin_node.get("source", -1)) virq_mapping = dict( eval( f"[{virq_node.text.replace(' ','').replace(')(', '), (')}]" )) if pirq in virq_mapping.keys(): virq = virq_mapping[pirq] prt_packages.append( builder.DefPackage( builder.PkgLength(), builder.ByteData(4), builder.PackageElementList( builder.build_value(vaddr), builder.build_value(pin_id), builder.build_value(0), builder.build_value(virq)))) for peer_device_node in collect_dependent_devices( board_etree, device_node): template = peer_device_node.find("aml_template") if template is not None: tree = parse_tree("DefDevice", bytes.fromhex(template.text)) objects.add_device_object(tree) root_pci_bus = board_etree.xpath("//bus[@type='pci' and @address='0x0']") if root_pci_bus: acpi_object = root_pci_bus[0].find("acpi_object") if acpi_object is not None: path = acpi_object.text objects.add_device_object(gen_root_pci_bus(path, prt_packages)) # If TPM is assigned to the VM, copy the TPM2 device object to vACPI as well. # # FIXME: Today the TPM2 MMIO registers are always located at 0xFED40000 with length 0x5000. The same address is used # as the guest physical address of the passed through TPM2. Thus, it works for now to reuse the host TPM2 device # object without updating the addresses of operation regions or resource descriptors. It is, however, necessary to # introduce a pass to translate such address to arbitrary guest physical ones in the future. has_tpm2 = common.get_node(f"//vm[@id='{vm_id}']//TPM2/text()", scenario_etree) if has_tpm2 == "y": # TPM2 devices should have "MSFT0101" as hardware id or compatible ID template = common.get_node("//device[@id='MSFT0101']/aml_template", board_etree) if template is None: template = common.get_node( "//device[compatible_id='MSFT0101']/aml_template", board_etree) if template is not None: tree = parse_tree("DefDevice", bytes.fromhex(template.text)) objects.add_device_object(tree) s5_object = builder.DefName( "_S5_", builder.DefPackage( builder.PkgLength(), 2, builder.PackageElementList(builder.build_value(5), builder.build_value(0)))) objects.add_object("\\", s5_object) rtvm = False for guest_flag in scenario_etree.xpath( f"//vm[@id='{vm_id}']/guest_flags/guest_flag/text()"): if guest_flag == 'GUEST_FLAG_LAPIC_PASSTHROUGH': rtvm = True break # RTVM cannot set IRQ because no INTR is sent with LAPIC PT if rtvm is False: objects.add_object("\\_SB_", pnp_uart("UAR0", 0, "COM1", 0x3f8, 4)) objects.add_object("\\_SB_", pnp_uart("UAR1", 1, "COM2", 0x2f8, 3)) objects.add_object("\\_SB_", pnp_rtc("RTC0")) amlcode = builder.AMLCode(header, *objects.get_term_list()) with open(dest_path, "wb") as dest: visitor = GenerateBinaryVisitor() binary = bytearray(visitor.generate(amlcode)) # Overwrite length binary[4:8] = len(binary).to_bytes(4, sys.byteorder) # Overwrite checksum checksum = (256 - (sum(binary) % 256)) % 256 binary[9] = checksum dest.write(binary)
def aux(device_path, obj_name, result): # This is the main function that recursively scans dependent object definitions and include either their # original definition or calculated values into the AML template. The algorithm is as follows: # # 1. Collect the objects that are used (either directly or indirectly) by the given object. # # 2. Determine how this object should go to the AML template by the following rules. # # a. If the object depends on any global object (i.e. not in the scope of any device), the object will not # be put to the AML template at all. Up to now we are not aware of any safe way to expose the object to # VMs as global objects can be operation fields within arbitrary memory-mapped regions. # # b. If the object depends on any operation field that is exposed to a VM, the object will be copied as is # in the AML template. # # c. Otherwise, it will be replaced with the current evaluated value as it is unlikely to change due to # guest software activities. # # Operation regions and its fields, when necessary, are always copied as is. # # Dependency among devices are also collected along the way. if not obj_name in device_objects[device_path].keys(): visitor = CollectDependencyVisitor(interpreter) deps = visitor.analyze(device_path, obj_name) copy_object = False if deps.all: # If the object refers to any operation region directly or indirectly, it is generally necessary to copy # the original definition of the object. for dev, decls in deps.all.items(): if next(filter(lambda x: isinstance(x, context.OperationFieldDecl) and visitor.is_exposed_field(x), decls), None): copy_object = True break evaluated = (result != None) need_global = ("global" in deps.all.keys()) formatter = lambda x: '+' if x else '-' logging.info(f"{device_path}.{obj_name}: Evaluated{formatter(evaluated)} Copy{formatter(copy_object)} NeedGlobal{formatter(need_global)}") if result == None or copy_object: if need_global: global_objs = ', '.join(map(lambda x: x.name, deps.all["global"])) raise NotImplementedError(f"{device_path}.{obj_name}: references to global objects: {global_objs}") # Add directly referred objects first for peer_device, peer_decls in deps.direct.items(): if peer_device == "global": peer_device = device_path for peer_decl in peer_decls: peer_obj_name = peer_decl.name[-4:] if isinstance(peer_decl, context.OperationRegionDecl): aux(peer_device, peer_obj_name, None) elif isinstance(peer_decl, context.OperationFieldDecl): op_region_name = peer_decl.region # Assume an operation region has at most one DefField object defining its fields device_objects[peer_device][f"{op_region_name}_fields"] = peer_decl.parent_tree else: if isinstance(peer_decl, context.MethodDecl) and peer_decl.nargs > 0: raise NotImplementedError(f"{peer_decl.name}: copy of methods with arguments is not supported") value = interpreter.interpret_method_call(peer_decl.name) aux(peer_device, peer_obj_name, value) # If decl is of another device, declare decl as an external symbol in the template of # device_path so that the template can be parsed on its own if peer_device != device_path: device_objects[device_path][peer_decl.name] = builder.DefExternal( peer_decl.name, peer_decl.object_type(), peer_decl.nargs if isinstance(peer_decl, context.MethodDecl) else 0) device_deps[device_path][DEP_TYPE_USES].add(peer_device) device_deps[peer_device][DEP_TYPE_USED_BY].add(device_path) decl = interpreter.context.lookup_symbol(obj_name, device_path) device_objects[device_path][obj_name] = decl.tree else: tree = builder.build_value(result) if tree: device_objects[device_path][obj_name] = builder.DefName(obj_name, tree) else: raise NotImplementedError(f"{device_path}.{obj_name}: unrecognized type: {result.__class__.__name__}")