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