1# Copyright (C) 2019-2022 Intel Corporation.
2#
3# SPDX-License-Identifier: BSD-3-Clause
4#
5
6import os
7import sys
8import getopt
9import shutil
10import subprocess # nosec
11import defusedxml.ElementTree as ET
12import re
13import lxml
14
15
16ACRN_CONFIG_TARGET = ''
17SOURCE_ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../')
18HV_LICENSE_FILE = SOURCE_ROOT_DIR + 'misc/config_tools/library/hypervisor_license'
19SCENARIO_SCHEMA_FILE = SOURCE_ROOT_DIR + 'misc/config_tools/schema/config.xsd'
20DATACHECK_SCHEMA_FILE = SOURCE_ROOT_DIR + 'misc/config_tools/schema/datachecks.xsd'
21
22
23PY_CACHES = ["__pycache__", "../board_config/__pycache__", "../scenario_config/__pycache__"]
24GUEST_FLAG = ["0", "0UL", "GUEST_FLAG_SECURE_WORLD_ENABLED", "GUEST_FLAG_LAPIC_PASSTHROUGH",
25              "GUEST_FLAG_IO_COMPLETION_POLLING", "GUEST_FLAG_NVMX_ENABLED", "GUEST_FLAG_HIDE_MTRR",
26              "GUEST_FLAG_RT", "GUEST_FLAG_SECURITY_VM", "GUEST_FLAG_VCAT_ENABLED",
27              "GUEST_FLAG_TEE", "GUEST_FLAG_REE"]
28
29MULTI_ITEM = ["guest_flag", "pcpu_id", "vcpu_clos", "input", "block", "network", "pci_dev", "shm_region", "communication_vuart"]
30
31SIZE_K = 1024
32SIZE_M = SIZE_K * 1024
33SIZE_2G = 2 * SIZE_M * SIZE_K
34SIZE_4G = 2 * SIZE_2G
35SIZE_G = SIZE_M * 1024
36
37VM_COUNT = 0
38BOARD_INFO_FILE = ""
39SCENARIO_INFO_FILE = ""
40LAUNCH_INFO_FILE = ""
41LOAD_ORDER = {}
42RTVM = {}
43MAX_VM_NUM = 16
44
45MAX_VUART_NUM = 8
46
47HV_BASE_RAM_SIZE = 0x1400000
48VM_RAM_SIZE = 0x400000
49TRUSTY_RAM_SIZE = 0x1000000
50
51class MultiItem():
52
53    def __init__(self):
54        self.guest_flag = []
55        self.pcpu_id = []
56        self.vcpu_clos = []
57        self.vir_input = []
58        self.vir_block = []
59        self.vir_console = []
60        self.vir_network = []
61        self.pci_dev = []
62        self.shm_region = []
63        self.communication_vuart = []
64
65class TmpItem():
66
67    def __init__(self):
68        self.tag = {}
69        self.multi = MultiItem()
70
71def open_license():
72    """ Get the license """
73    with open(HV_LICENSE_FILE, 'r') as f_licence:
74        license_s = f_licence.read().strip()
75        return license_s
76
77
78def print_yel(msg, warn=False):
79    """
80    Print the message with 'Warning' if warn is true
81    :param msg: the stings which will be output to STDOUT
82    :param warn: the condition if needs to be output the color of yellow with 'Warning'
83    """
84    if warn:
85        print("\033[1;33mWarning\033[0m: "+msg)
86    else:
87        print("\033[1;33m{0}\033[0m".format(msg))
88
89
90def print_red(msg, err=False):
91    """
92    Print the message with 'Error' if err is true
93    :param msg: the stings which will be output to STDOUT
94    :param err: the condition if needs to be output the color of red with 'Error'
95    """
96    if err:
97        print("\033[1;31mError\033[0m: "+msg)
98    else:
99        print("\033[1;31m{0}\033[0m".format(msg))
100
101
102def usage(file_name):
103    """ This is usage for how to use this tool """
104    print("usage= {} [h] ".format(file_name), end="")
105    print("--board <board_info_file> --scenario <scenario_info_file> --out [output folder]")
106    print('board_info_file :  file name of the board info')
107    print('scenario_info_file :  file name of the scenario info')
108    print('output folder :  path to acrn-hypervisor_folder')
109
110
111def get_param(args):
112    """
113    Get the script parameters from command line
114    :param args: this the command line of string for the script without script name
115    """
116    err_dic = {}
117    params = {'--board':'', '--scenario':'', '--out':''}
118    args_list = args[1:]
119
120    try:
121        (optlist, args_list) = getopt.getopt(args_list, '', ['hv=', 'board=', 'scenario=', 'out='])
122    except getopt.GetoptError as err:
123        usage(args[0])
124        sys.exit(2)
125    for arg_k, arg_v in optlist:
126        if arg_k == '--board':
127            params['--board'] = arg_v
128        if arg_k == '--scenario':
129            params['--scenario'] = arg_v
130        if arg_k == '--out':
131            params['--out'] = arg_v
132
133    for par_k, par_v in params.items():
134        if par_k == '--out':
135            continue
136
137        if not par_v:
138            usage(args[0])
139            err_dic['wrong usage'] = "Parameter for {} should not empty".format(par_k)
140            return (err_dic, params)
141
142        if not os.path.exists(par_v):
143            err_dic['wrong usage'] = "{} does not exist!".format(par_v)
144            return (err_dic, params)
145
146    return (err_dic, params)
147
148
149def prepare():
150    """ Prepare to check the environment """
151    err_dic = {}
152    bin_list = []
153
154    for excute in bin_list:
155        res = subprocess.Popen("which {}".format(excute), shell=True, stdout=subprocess.PIPE,
156                               stderr=subprocess.PIPE, close_fds=True)
157
158        line = res.stdout.readline().decode('ascii')
159
160        if not line:
161            err_dic['commn error: check env failed'] = "'{}' not found, please install it!".format(excute)
162
163    for py_cache in PY_CACHES:
164        if os.path.exists(py_cache):
165            shutil.rmtree(py_cache)
166
167    return err_dic
168
169def get_xml_attrib(config_file, attrib):
170    """
171    Get attribute from xml at fist line
172    :param config_file: it is a file what contains board information for script to read from
173    :param attrib: attribute of item in xml
174    """
175    value = ''
176    err_dic = {}
177    with open(config_file, 'rt') as fp_info:
178        while True:
179            line = fp_info.readline()
180            if not line:
181                break
182
183            if 'board=' in line or 'scenario=' in line:
184
185                if attrib not in line:
186                    err_dic['acrn_config_utilities error'] = "The {} attribute is not in xml file".format(attrib)
187                    return (err_dic, value)
188
189                attrib_list = line.split()
190                for attrib_value in attrib_list:
191                    if attrib in attrib_value:
192                        value = attrib_value.split('"')[1].strip('"')
193
194    return (err_dic, value)
195
196def count_nodes(xpath, etree):
197    return int(etree.xpath(f"count({xpath})"))
198
199def get_node(xpath, etree):
200    result = etree.xpath(f"{xpath}")
201    assert len(result) <= 1, f"Internal error: multiple element nodes are found for {xpath}"
202    return result[0] if len(result) == 1 else None
203
204def update_text(xpath, value, etree, overwrite=False):
205    result = etree.xpath(f"{xpath}")
206    assert len(result) == 1, "Internal error: cannot set text to multiple nodes at a time"
207    if overwrite or not result[0].text:
208        result[0].text = str(value)
209
210def append_node(xpath, value, etree, **attribute):
211    # Look for an existing ancestor node
212    parts = xpath.split("/")
213    ancestor_level = 1
214    ancestor = None
215    while ancestor_level < len(parts):
216        result = etree.xpath("/".join(parts[:-ancestor_level]))
217        assert len(result) <= 1, "Internal error: cannot append element nodes to multiple ancestors"
218        if len(result) == 1:
219            ancestor = result[0]
220            break
221        ancestor_level += 1
222
223    assert ancestor is not None, f"Internal error: cannot find an existing ancestor for {xpath}"
224    for tag in parts[-ancestor_level:]:
225        child = lxml.etree.Element(tag)
226        ancestor.append(child)
227        ancestor = child
228    if value:
229        child.text = str(value)
230    for key, value in attribute.items():
231        child.set(key, value)
232    return ancestor
233
234def get_board_name():
235    """
236    Get board name from board.xml at fist line
237    :param board_info: it is a file what contains board information for script to read from
238    """
239    (err_dic, board) = get_xml_attrib(BOARD_INFO_FILE, "board")
240    return (err_dic, board)
241
242
243def get_scenario_name():
244    """
245    Get scenario name from scenario.xml at fist line
246    :param scenario_info: it is a file what contains board information for script to read from
247    """
248    (err_dic, scenario) = get_xml_attrib(SCENARIO_INFO_FILE, "scenario")
249    return (err_dic, scenario)
250
251
252def find_tmp_flag(flag):
253    """
254    Find the index in GUEST_FLAG by flag
255    :param flag: flag contained by GUEST_FLAG
256    :return: index of GUEST_FLAG
257    """
258    if flag == None or flag == '0':
259        return '0UL'
260
261    for i in range(len(GUEST_FLAG)):
262        if flag == GUEST_FLAG[i]:
263            return flag
264
265
266def get_config_root(config_file):
267    """
268    This is get root of xml config
269    :param config_file: it is a file what contains information for script to read from
270    :return: top of root entry
271    """
272    # create element tree object
273    tree = ET.parse(config_file)
274    # get root element
275    root = tree.getroot()
276
277    return root
278
279
280def get_vm_num(config_file):
281    """
282    Get vm number
283    :param config_file: it is a file what contains information for script to read from
284    :return: total vm number
285    """
286    global VM_COUNT, MAX_VM_NUM
287    vm_count = 0
288    root = get_config_root(config_file)
289    for item in root:
290        # vm number in scenario
291        if item.tag == "vm":
292            vm_count += 1
293    VM_COUNT = vm_count
294    MAX_VM_NUM = int(root.find(".//MAX_VM_NUM").text)
295
296
297def get_leaf_value(tmp, tag_str, leaf):
298
299    # get guest flag for logical partition vm1
300    if leaf.tag == "guest_flag" and tag_str == "guest_flag":
301        t_flag = find_tmp_flag(leaf.text)
302        tmp.multi.guest_flag.append(t_flag)
303
304    # get cpu for vm
305    if leaf.tag == "pcpu_id" and tag_str == "pcpu_id":
306        tmp.multi.pcpu_id.append(leaf.text)
307
308    # get vcpu_clos for vm
309    if leaf.tag == "vcpu_clos" and tag_str == "vcpu_clos":
310        tmp.multi.vcpu_clos.append(leaf.text)
311
312    # get virtio-input for vm
313    if leaf.tag == "input" and tag_str == "input":
314        tmp.multi.vir_input.append(leaf.text)
315
316    # get virtio-blk for vm
317    if leaf.tag == "block" and tag_str == "block":
318        tmp.multi.vir_block.append(leaf.text)
319
320    # get virtio-net for vm
321    if leaf.tag == "network" and tag_str == "network":
322        tmp.multi.vir_network.append(leaf.text)
323
324    # get pci_dev for vm
325    if leaf.tag == "pci_dev" and tag_str == "pci_dev":
326        tmp.multi.pci_dev.append(leaf.text)
327
328    # get shm_region for vm
329    if leaf.tag == "shm_region" and tag_str == "shm_region":
330        tmp.multi.shm_region.append(leaf.text)
331
332    # get communication_vuart for vm
333    if leaf.tag == "communication_vuart" and tag_str == "communication_vuart":
334        tmp.multi.communication_vuart.append(leaf.text)
335
336
337def get_sub_value(tmp, tag_str, vm_id):
338
339    # append guest flags for each vm
340    if tmp.multi.guest_flag and tag_str == "guest_flag":
341        tmp.tag[vm_id] = tmp.multi.guest_flag
342
343    # append cpus for vm
344    if tmp.multi.pcpu_id and tag_str == "pcpu_id":
345        tmp.tag[vm_id] = tmp.multi.pcpu_id
346
347    # append cpus for vm
348    if tmp.multi.vcpu_clos and tag_str == "vcpu_clos":
349        tmp.tag[vm_id] = tmp.multi.vcpu_clos
350
351    # append virtio input for vm
352    if tmp.multi.vir_input and tag_str == "input":
353        tmp.tag[vm_id] = tmp.multi.vir_input
354
355    # append virtio block for vm
356    if tmp.multi.vir_block and tag_str == "block":
357        tmp.tag[vm_id] = tmp.multi.vir_block
358
359    # append virtio network for vm
360    if tmp.multi.vir_network and tag_str == "network":
361        tmp.tag[vm_id] = tmp.multi.vir_network
362
363    # append pci_dev for vm
364    if tmp.multi.pci_dev and tag_str == "pci_dev":
365        tmp.tag[vm_id] = tmp.multi.pci_dev
366
367    # append shm_region for vm
368    if tmp.multi.shm_region and tag_str == "shm_region":
369        tmp.tag[vm_id] = tmp.multi.shm_region
370
371    # append communication_vuart for vm
372    if tmp.multi.communication_vuart and tag_str == "communication_vuart":
373        tmp.tag[vm_id] = tmp.multi.communication_vuart
374
375
376def get_leaf_tag_map(config_file, branch_tag, tag_str=''):
377    """
378     This is get tag value by tag_str from config file
379     :param config_file: it is a file what contains information for script to read from
380     :param branch_tag: it is key of patter to config file branch tag item
381     :param tag_str: it is key of pattern to config file leaf tag item
382     :return: value of tag_str item map
383     """
384    tmp = TmpItem()
385    root = get_config_root(config_file)
386    for item in root:
387        if not 'id' in item.attrib.keys():
388            continue
389        vm_id = int(item.attrib['id'])
390        # for each 2th level item
391        for sub in item:
392            tmp.multi = MultiItem()
393            if sub.tag == branch_tag:
394                if not tag_str:
395                    if sub.text == None or not sub.text:
396                        tmp.tag[vm_id] = ''
397                    else:
398                        tmp.tag[vm_id] = sub.text
399                    continue
400
401                # for each 3rd level item
402                for leaf in sub:
403                    if leaf.tag == tag_str and tag_str not in MULTI_ITEM and sub.tag not in ["legacy_vuart","vuart"]:
404                        if leaf.text == None or not leaf.text:
405                            tmp.tag[vm_id] = ''
406                        else:
407                            tmp.tag[vm_id] = leaf.text
408                        continue
409
410                    get_leaf_value(tmp, tag_str, leaf)
411
412                get_sub_value(tmp, tag_str, vm_id)
413
414    return dict(sorted(tmp.tag.items()))
415
416
417def get_vuart_id(tmp_vuart, leaf_tag, leaf_text):
418    """
419    Get all vuart id member of class
420    :param tmp_vuart: a dictionary to store member:value
421    :param leaf_tag: key pattern of item tag
422    :param leaf_text: key pattern of item tag's value
423    :return: a dictionary to which stored member:value
424    """
425    if leaf_tag == "type":
426        tmp_vuart['type'] = leaf_text
427    if leaf_tag == "base":
428        tmp_vuart['base'] = leaf_text
429    if leaf_tag == "irq":
430        tmp_vuart['irq'] = leaf_text
431
432    if leaf_tag == "target_vm_id":
433        tmp_vuart['target_vm_id'] = leaf_text
434    if leaf_tag == "target_uart_id":
435        tmp_vuart['target_uart_id'] = leaf_text
436
437    return tmp_vuart
438
439
440def get_vuart_info_id(config_file, idx):
441    """
442    Get vuart information by vuart id indexx
443    :param config_file: it is a file what contains information for script to read from
444    :param idx: vuart index in range: [0,1]
445    :return: dictionary which stored the vuart-id
446    """
447    tmp_tag = {}
448    vm_id = 0
449    root = get_config_root(config_file)
450    for item in root:
451        if item.tag == "vm":
452            vm_id = int(item.attrib['id'])
453
454        for sub in item:
455            tmp_vuart = {}
456            for leaf in sub:
457                if sub.tag in ["legacy_vuart","vuart"] and int(sub.attrib['id']) == idx:
458                    tmp_vuart = get_vuart_id(tmp_vuart, leaf.tag, leaf.text)
459
460            # append vuart for each vm
461            if tmp_vuart and sub.tag in ["legacy_vuart","vuart"]:
462                tmp_tag[vm_id] = tmp_vuart
463
464    return tmp_tag
465
466def get_vuart_info(config_file):
467    tmp_tag = {}
468    vm_id = 0
469    root = get_config_root(config_file)
470    for item in root:
471        if item.tag == "vm":
472            vm_id = int(item.attrib['id'])
473            tmp_tag[vm_id] = {}
474
475        for sub in item:
476            tmp_vuart = {}
477            for leaf in sub:
478                if sub.tag == "console_vuart" or sub.tag == "communication_vuart":
479                    vuart_id = int(sub.attrib['id'])
480                    tmp_vuart = get_vuart_id(tmp_vuart, leaf.tag, leaf.text)
481
482                    if tmp_vuart:
483                        tmp_tag[vm_id][vuart_id] = tmp_vuart
484
485    return tmp_tag
486
487
488def get_hv_item_tag(config_file, branch_tag, tag_str='', leaf_str=''):
489
490    tmp = ''
491    root = get_config_root(config_file)
492
493    for item in root:
494        # for each 2th level item
495        for sub in item:
496            if sub.tag == branch_tag:
497                if not tag_str:
498                    if sub.text == None or not sub.text:
499                        tmp = ''
500                    else:
501                        tmp = sub.text
502                    continue
503
504                # for each 3rd level item
505                tmp_list = []
506                for leaf in sub:
507                    if leaf.tag == tag_str:
508                        if not leaf_str:
509                            if leaf.tag == tag_str and leaf.text and leaf.text != None:
510                                if tag_str == "IVSHMEM_REGION":
511                                    tmp_list.append(leaf.text)
512                                else:
513                                    tmp = leaf.text
514
515                        else:
516                            # for each 4rd level item
517                            tmp_list = []
518                            for leaf_s in leaf:
519                                if leaf_s.tag == leaf_str and leaf_s.text and leaf_s.text != None:
520                                    if leaf_str == "CLOS_MASK" or leaf_str == "MBA_DELAY" or leaf_str == "IVSHMEM_REGION":
521                                        tmp_list.append(leaf_s.text)
522                                    else:
523                                        tmp = leaf_s.text
524                                continue
525
526                            if leaf_str == "CLOS_MASK" or leaf_str == "MBA_DELAY" or leaf_str == "IVSHMEM_REGION":
527                                tmp = tmp_list
528                                break
529
530                if tag_str == "IVSHMEM_REGION":
531                    tmp = tmp_list
532                    break
533
534    return tmp
535
536
537def undline_name(name):
538    """
539    This convert name which has contain '-' to '_'
540    :param name: name which contain '-' and ' '
541    :return: name_str which contain'_'
542    """
543    # convert '-' to '_' in name string
544    name_str = "_".join(name.split('-')).upper()
545
546    # stitch '_' while ' ' in name string
547    if ' ' in name_str:
548        name_str = "_".join(name_str.split()).upper()
549
550    return name_str
551
552def round_down(addr, mem_align):
553    """Keep memory align"""
554    return (addr & (~(mem_align - 1)))
555
556def round_up(addr, mem_align):
557    """Keep memory align"""
558    return ((addr + (mem_align - 1)) & (~(mem_align - 1)))
559
560
561def mkdir(path):
562
563    if not os.path.exists(path):
564        import platform
565        try:
566            if platform.system().lower() == 'windows':
567                os.makedirs(path)
568            else:
569                subprocess.check_call('mkdir -p {}'.format(path), shell=True, stdout=subprocess.PIPE)
570        except subprocess.CalledProcessError:
571            print_red("{} file create failed!".format(path), err=True)
572
573
574def num2int(str_value):
575
576    val = 0
577    if isinstance(str_value, int):
578        val = str_value
579        return val
580    if str_value.isnumeric():
581        val = int(str_value)
582    else:
583        # hex value
584        val = int(str_value, 16)
585
586    return val
587
588
589def get_load_order():
590    global LOAD_ORDER
591    LOAD_ORDER = get_leaf_tag_map(SCENARIO_INFO_FILE, "load_order")
592
593def get_RTVM():
594    global RTVM
595    RTVM = get_leaf_tag_map(SCENARIO_INFO_FILE, "vm_type")
596
597
598def get_avl_dev_info(bdf_desc_map, pci_sub_class):
599
600    tmp_pci_desc = []
601    for sub_class in pci_sub_class:
602        for pci_desc_value in bdf_desc_map.values():
603            pci_desc_sub_class = ' '.join(pci_desc_value.strip().split(':')[1].split()[1:])
604            if sub_class == pci_desc_sub_class:
605                tmp_pci_desc.append(pci_desc_value.strip())
606
607    return tmp_pci_desc
608
609
610def str2bool(v):
611    return v.lower() in ("yes", "true", "t", "y", "1") if v else False
612
613
614def get_leaf_tag_map_bool(config_file, branch_tag, tag_str=''):
615    """
616    This convert and return map's value from string to bool
617    """
618
619    result = {}
620
621    tag_map = get_leaf_tag_map(config_file, branch_tag, tag_str)
622    for vm_i, s in tag_map.items():
623        result[vm_i] = str2bool(s)
624
625    return result
626
627
628def hpa2gpa(vm_id, hpa, size):
629    return hpa
630
631
632def str2int(x):
633    s = x.replace(" ", "").lower()
634
635    if s:
636        base = 10
637        if s.startswith('0x'): base = 16
638        return int(s, base)
639
640    return 0
641
642
643def get_pt_intx_table(config_file):
644    pt_intx_map = get_leaf_tag_map(config_file, "pt_intx")
645
646    # translation table to normalize the paired phys_gsi and virt_gsi string
647    table = {ord('[') : ord('('), ord(']') : ord(')'), ord('{') : ord('('),
648        ord('}') : ord(')'), ord(';') : ord(','),
649        ord('\n') : None, ord('\r') : None, ord(' ') : None}
650
651    phys_gsi = {}
652    virt_gsi = {}
653
654    for vm_i, s in pt_intx_map.items():
655        #normalize the phys_gsi and virt_gsi pair string
656        s = s.translate(table)
657
658        #extract the phys_gsi and virt_gsi pairs between parenthesis to a list
659        s = re.findall(r'\(([^)]+)', s)
660
661        for part in s:
662            if not part: continue
663            assert ',' in part, "you need to use ',' to separate phys_gsi and virt_gsi!"
664            a, b = part.split(',')
665            if not a and not b: continue
666            assert a and b, "you need to specify both phys_gsi and virt_gsi!"
667            a, b = str2int(a), str2int(b)
668
669            if vm_i not in phys_gsi and vm_i not in virt_gsi:
670                phys_gsi[vm_i] = []
671                virt_gsi[vm_i] = []
672            phys_gsi[vm_i].append(a)
673            virt_gsi[vm_i].append(b)
674
675    return phys_gsi, virt_gsi
676