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