1# Copyright (C) 2019-2022 Intel Corporation. 2# SPDX-License-Identifier: BSD-3-Clause 3 4"""the tool to generate ACPI binary for Pre-launched VMs. 5 6""" 7 8import logging 9import subprocess # nosec 10import os, sys, argparse, re, shutil 11sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'board_inspector')) 12from defusedxml.lxml import parse 13from acpi_const import * 14import acpiparser.tpm2 15import inspectorlib.cdata 16import acpiparser.rtct 17import acrn_config_utilities 18from acrn_config_utilities import get_node 19 20def move_rtct_ssram_and_bin_entries(rtct, new_base_addr, new_area_max_size): 21 ''' 22 move the guest ssram and ctl bin entries to a new base addr. the entries keeps their relative layout 23 :param rtct: parsed rtct bit struct 24 :param new_base_addr: the top address of the new area 25 :param new_area_max_size: max size of the new area. for valid check 26 :return: 27 ''' 28 if rtct.version == 1: 29 expect_ssram_type = acpiparser.rtct.ACPI_RTCT_V1_TYPE_SoftwareSRAM 30 elif rtct.version == 2: 31 expect_ssram_type = acpiparser.rtct.ACPI_RTCT_V2_TYPE_SoftwareSRAM 32 else: 33 raise Exception("RTCT version error! ", rtct.version) 34 top = 0 35 base = 0 36 for entry in rtct.entries: 37 if entry.type == expect_ssram_type: 38 top = (entry.base + entry.size) if top < (entry.base + entry.size) else top 39 base = entry.base if base == 0 or entry.base < base else base 40 if new_area_max_size < (top - base): 41 raise Exception("not enough space in guest VE820 SSRAM area!") 42 rtct_move_offset = new_base_addr - base 43 for entry in rtct.entries: 44 if entry.type == expect_ssram_type: 45 entry.base += rtct_move_offset 46 # re-calculate checksum 47 rtct.header.checksum = 0 48 rtct.header.checksum = 0 - sum(bytes(rtct)) 49 50def asl_to_aml(dest_vm_acpi_path, dest_vm_acpi_bin_path, scenario_etree, allocation_etree, iasl_path): 51 ''' 52 compile asl code of ACPI table to aml code. 53 :param dest_vm_acpi_path: the path of the asl code of ACPI tables 54 :param dest_vm_acpi_bin_path: the path of the aml code of ACPI tables 55 :param passthru_devices: passthrough devce list 56 :return: 57 ''' 58 curr_path = os.getcwd() 59 rmsg = '' 60 61 os.chdir(dest_vm_acpi_path) 62 for acpi_table in ACPI_TABLE_LIST: 63 if acpi_table[0] == 'tpm2.asl': 64 if 'tpm2.asl' in os.listdir(dest_vm_acpi_path): 65 rc = exec_command('{} {}'.format(iasl_path, acpi_table[0])) 66 if rc == 0 and os.path.isfile(os.path.join(dest_vm_acpi_path, acpi_table[1])): 67 shutil.move(os.path.join(dest_vm_acpi_path, acpi_table[1]), 68 os.path.join(dest_vm_acpi_bin_path, acpi_table[1])) 69 else: 70 if os.path.isfile(os.path.join(dest_vm_acpi_path, acpi_table[1])): 71 os.remove(os.path.join(dest_vm_acpi_path, acpi_table[1])) 72 rmsg = 'failed to compile {}'.format(acpi_table[0]) 73 break 74 elif acpi_table[0] in ['ptct.aml', 'rtct.aml']: 75 if acpi_table[0] in os.listdir(dest_vm_acpi_path): 76 rtct = acpiparser.rtct.RTCT(os.path.join(dest_vm_acpi_path, acpi_table[0])) 77 outfile = os.path.join(dest_vm_acpi_bin_path, acpi_table[1]) 78 # move the guest ssram area to the area next to ACPI region 79 pre_rt_vms = get_node("//vm[load_order ='PRE_LAUNCHED_VM' and vm_type ='RTVM']", scenario_etree) 80 vm_id = pre_rt_vms.get("id") 81 allocation_vm_node = get_node(f"/acrn-config/vm[@id = '{vm_id}']", allocation_etree) 82 ssram_start_gpa = get_node("./ssram/start_gpa/text()", allocation_vm_node) 83 ssram_max_size = get_node("./ssram/max_size/text()", allocation_vm_node) 84 move_rtct_ssram_and_bin_entries(rtct, int(ssram_start_gpa, 16), int(ssram_max_size, 16)) 85 fp = open(outfile, mode='wb') 86 fp.write(rtct) 87 fp.close() 88 else: 89 if acpi_table[0].endswith(".asl"): 90 rc = exec_command('{} {}'.format(iasl_path, acpi_table[0])) 91 if rc == 0 and os.path.isfile(os.path.join(dest_vm_acpi_path, acpi_table[1])): 92 shutil.move(os.path.join(dest_vm_acpi_path, acpi_table[1]), 93 os.path.join(dest_vm_acpi_bin_path, acpi_table[1])) 94 else: 95 if os.path.isfile(os.path.join(dest_vm_acpi_path, acpi_table[1])): 96 os.remove(os.path.join(dest_vm_acpi_path, acpi_table[1])) 97 rmsg = 'failed to compile {}'.format(acpi_table[0]) 98 break 99 elif acpi_table[0].endswith(".aml") and acpi_table[0] in os.listdir(dest_vm_acpi_path): 100 shutil.copy(os.path.join(dest_vm_acpi_path, acpi_table[0]), 101 os.path.join(dest_vm_acpi_bin_path, acpi_table[1])) 102 103 os.chdir(curr_path) 104 if not rmsg: 105 print('compile ACPI ASL code to {} successfully'.format(dest_vm_acpi_bin_path)) 106 return rmsg 107 108def tpm2_acpi_gen(acpi_bin, board_etree, scenario_etree, allocation_etree): 109 tpm2_enabled = get_node("//vm[@id = '0']/mmio_resources/TPM2/text()", scenario_etree) 110 if tpm2_enabled is not None and tpm2_enabled == 'y': 111 tpm2_node = get_node("//device[@id = 'MSFT0101' or compatible_id = 'MSFT0101']", board_etree) 112 if tpm2_node is not None: 113 _data_len = 0x4c if get_node("//capability[@id = 'log_area']", board_etree) is not None else 0x40 114 _data = bytearray(_data_len) 115 ctype_data = acpiparser.tpm2.TPM2(_data) 116 ctype_data.header.signature = "TPM2".encode() 117 ctype_data.header.length = _data_len 118 ctype_data.header.revision = 4 119 ctype_data.header.oemid = "ACRN ".encode() 120 ctype_data.header.oemtableid = "ACRNTPM2".encode() 121 ctype_data.header.oemrevision = 0x1 122 ctype_data.header.creatorid = "INTL".encode() 123 ctype_data.header.creatorrevision = 0x20190703 124 ctype_data.address_of_control_area = 0xFED40040 125 ctype_data.start_method = int(get_node("//capability[@id = 'start_method']/value/text()", tpm2_node), 16) 126 start_method_parameters = tpm2_node.xpath("//capability[@id = 'start_method']/parameter/text()") 127 for i in range(len(start_method_parameters)): 128 ctype_data.start_method_specific_parameters[i] = int(start_method_parameters[i], 16) 129 if get_node("//capability[@id = 'log_area']", board_etree) is not None: 130 ctype_data.log_area_minimum_length = int(get_node("//log_area_minimum_length/text()", allocation_etree), 16) 131 ctype_data.log_area_start_address = int(get_node("//log_area_start_address/text()", allocation_etree), 16) 132 ctype_data.header.checksum = (~(sum(inspectorlib.cdata.to_bytes(ctype_data))) + 1) & 0xFF 133 acpi_bin.seek(ACPI_TPM2_ADDR_OFFSET) 134 acpi_bin.write(inspectorlib.cdata.to_bytes(ctype_data)) 135 else: 136 logging.warning("Passtrhough tpm2 is enabled in scenario but the device is not presented on board.") 137 logging.warning("Check there is tpm2 device on board and re-generate the xml using board inspector with --advanced option.") 138 139def aml_to_bin(dest_vm_acpi_path, dest_vm_acpi_bin_path, acpi_bin_name, board_etree, scenario_etree, allocation_etree): 140 ''' 141 create the binary of ACPI table. 142 :param dest_vm_acpi_bin_path: the path of the aml code of ACPI tables 143 :param acpi_bin: the binary file name of ACPI tables 144 :param passthru_devices: passthrough devce list 145 :return: 146 ''' 147 acpi_bin_file = os.path.join(dest_vm_acpi_bin_path, acpi_bin_name) 148 if os.path.isfile(acpi_bin_file): 149 os.remove(acpi_bin_file) 150 with open(acpi_bin_file, 'wb') as acpi_bin: 151 # acpi_bin.seek(ACPI_RSDP_ADDR_OFFSET) 152 # with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[0][1]), 'rb') as asl: 153 # acpi_bin.write(asl.read()) 154 155 acpi_bin.seek(ACPI_XSDT_ADDR_OFFSET) 156 with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[1][1]), 'rb') as asl: 157 acpi_bin.write(asl.read()) 158 159 acpi_bin.seek(ACPI_FADT_ADDR_OFFSET) 160 with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[2][1]), 'rb') as asl: 161 acpi_bin.write(asl.read()) 162 163 acpi_bin.seek(ACPI_MCFG_ADDR_OFFSET) 164 with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[3][1]), 'rb') as asl: 165 acpi_bin.write(asl.read()) 166 167 acpi_bin.seek(ACPI_MADT_ADDR_OFFSET) 168 with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[4][1]), 'rb') as asl: 169 acpi_bin.write(asl.read()) 170 171 acpi_bin.seek(ACPI_DSDT_ADDR_OFFSET) 172 with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[6][1]), 'rb') as asl: 173 acpi_bin.write(asl.read()) 174 175 if ACPI_TABLE_LIST[7][1] in os.listdir(dest_vm_acpi_path): 176 acpi_bin.seek(ACPI_RTCT_ADDR_OFFSET) 177 with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[7][1]), 'rb') as asl: 178 acpi_bin.write(asl.read()) 179 elif ACPI_TABLE_LIST[8][1] in os.listdir(dest_vm_acpi_path): 180 acpi_bin.seek(ACPI_RTCT_ADDR_OFFSET) 181 with open(os.path.join(dest_vm_acpi_bin_path, ACPI_TABLE_LIST[8][1]), 'rb') as asl: 182 acpi_bin.write(asl.read()) 183 184 vm_id = acpi_bin_name.split('.')[0].split('ACPI_VM')[1] 185 if vm_id == '0': 186 tpm2_acpi_gen(acpi_bin, board_etree, scenario_etree, allocation_etree) 187 188 acpi_bin.seek(0xfffff) 189 acpi_bin.write(b'\0') 190 shutil.move(acpi_bin_file, os.path.join(dest_vm_acpi_bin_path, '..', acpi_bin_name)) 191 print('write ACPI binary to {} successfully'.format(os.path.join(dest_vm_acpi_bin_path, '..', acpi_bin_name))) 192 193 194def exec_command(cmd): 195 ''' 196 execute the command and output logs. 197 :param cmd: the command to execute. 198 :return: 199 ''' 200 print('exec: ', cmd) 201 p_compile_result = r'Compilation successful. (\d+) Errors, (\d+) Warnings, (\d+) Remarks' 202 cmd_list = cmd.split() 203 rc = 1 204 r_lines = [] 205 try: 206 for line in subprocess.check_output(cmd_list).decode('utf8').split('\n'): 207 r_lines.append(line) 208 m = re.match(p_compile_result, line) 209 if m and len(m.groups()) == 3: 210 rc = int(m.groups()[0]) 211 break 212 except Exception as e: 213 print('exception when exec {}'.format(cmd), e) 214 rc = -1 215 216 if rc > 0: 217 print('\n'.join(r_lines)) 218 219 return rc 220 221 222def check_iasl(iasl_path, iasl_min_ver): 223 ''' 224 check iasl installed 225 :return: True if iasl installed. 226 ''' 227 try: 228 p_version = 'ASL+ Optimizing Compiler/Disassembler version' 229 min_version = int(iasl_min_ver) 230 output = subprocess.check_output([iasl_path, '-v']).decode('utf8') 231 if p_version in output: 232 try: 233 for line in output.split('\n'): 234 if line.find(p_version) >= 0: 235 version = int(line.split(p_version)[1].strip()) 236 print('iasl version is {}'.format(version)) 237 if version >= min_version: 238 return True 239 except: 240 pass 241 return False 242 elif 'command not found' in output: 243 return False 244 else: 245 print(output) 246 return False 247 except Exception as e: 248 print(e) 249 return False 250 251 252def main(args): 253 254 board_etree = parse(args.board) 255 scenario_etree = parse(args.scenario) 256 257 scenario_name = get_node("//@scenario", scenario_etree) 258 259 if args.asl is None: 260 DEST_ACPI_PATH = os.path.join(VM_CONFIGS_PATH, 'scenarios', scenario_name) 261 else: 262 DEST_ACPI_PATH = os.path.join(acrn_config_utilities.SOURCE_ROOT_DIR, args.asl, 'scenarios', scenario_name) 263 if args.out is None: 264 hypervisor_out = os.path.join(acrn_config_utilities.SOURCE_ROOT_DIR, 'build', 'hypervisor') 265 else: 266 hypervisor_out = args.out 267 DEST_ACPI_BIN_PATH = os.path.join(hypervisor_out, 'acpi') 268 269 allocation_etree = parse(os.path.join(hypervisor_out, 'configs', 'allocation.xml')) 270 271 if os.path.isdir(DEST_ACPI_BIN_PATH): 272 shutil.rmtree(DEST_ACPI_BIN_PATH) 273 274 if not check_iasl(args.iasl_path, args.iasl_min_ver): 275 print('Please install iasl tool with version >= {} from https://www.acpica.org/downloads ' 276 'before ACPI generation.'.format(args.iasl_min_ver)) 277 return 1 278 279 for config in os.listdir(DEST_ACPI_PATH): 280 if os.path.isdir(os.path.join(DEST_ACPI_PATH, config)) and config.startswith('ACPI_VM'): 281 print('start to generate ACPI binary for {}'.format(config)) 282 dest_vm_acpi_path = os.path.join(DEST_ACPI_PATH, config) 283 dest_vm_acpi_bin_path = os.path.join(DEST_ACPI_BIN_PATH, config) 284 os.makedirs(dest_vm_acpi_bin_path) 285 if asl_to_aml(dest_vm_acpi_path, dest_vm_acpi_bin_path, scenario_etree, allocation_etree, args.iasl_path): 286 return 1 287 aml_to_bin(dest_vm_acpi_path, dest_vm_acpi_bin_path, config+'.bin', board_etree, scenario_etree, allocation_etree) 288 289 return 0 290 291if __name__ == '__main__': 292 parser = argparse.ArgumentParser(usage="python3 bin_gen.py --board [board] --scenario [scenario]" 293 " --iasl_path [the path to the iasl compiler]" 294 " --iasl_min_ver [the minimum iasl version]" 295 "[ --out [output dir of acpi ASL code]]", 296 description="the tool to generate ACPI binary for Pre-launched VMs") 297 parser.add_argument("--board", required=True, help="the XML file summarizing characteristics of the target board") 298 parser.add_argument("--scenario", required=True, help="the XML file specifying the scenario to be set up") 299 parser.add_argument("--asl", default=None, help="the input folder to store the ACPI ASL code. ") 300 parser.add_argument("--iasl_path", default=None, help="the path to the iasl compiler.") 301 parser.add_argument("--iasl_min_ver", default=None, help="the minimum iasl version.") 302 parser.add_argument("--out", default=None, help="the output folder to store the ACPI binary code. " 303 "If not specified, the path for the binary code is" 304 "build/hypervisor/acpi/") 305 306 args = parser.parse_args() 307 rc = main(args) 308 sys.exit(rc) 309