1#!/usr/bin/env python3
2#
3# Copyright (C) 2021-2022 Intel Corporation.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8import sys, os, re
9sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'library'))
10import acrn_config_utilities, lib.error, lib.lib
11from acrn_config_utilities import get_node
12
13# Constants for device name prefix
14IVSHMEM = "IVSHMEM"
15VUART = "VUART"
16
17# Exception bdf list
18# Some hardware drivers' bdf is hardcoded, the bdf cannot be changed even it is passtrhough devices.
19HARDCODED_BDF_LIST = ["00:0e.0"]
20
21def find_unused_bdf(used_bdf):
22    # never assign 0:00.0 to any emulated devices, it's reserved for pci hostbridge
23    for dev in range(0x1, 0x20):
24        bdf = lib.lib.BusDevFunc(bus=0x00, dev=dev, func=0x0)
25        if all((bdf.dev != in_use_bdf.dev for in_use_bdf in used_bdf)):
26            return bdf
27    raise lib.error.ResourceError(f"Cannot find free bdf, used bdf: {sorted(used_bdf)}")
28
29def insert_vuart_to_dev_dict(scenario_etree, devdict, used):
30    console_vuart =  scenario_etree.xpath(f"./console_vuart[base != 'INVALID_PCI_BASE']/@id")
31    for vuart_id in console_vuart:
32        free_bdf = find_unused_bdf(used)
33        devdict[f"{VUART}_{vuart_id}"] = free_bdf
34        used.append(free_bdf)
35
36def insert_ivsheme_to_dev_dict(scenario_etree, devdict, vm_id, used):
37    shmem_regions = lib.lib.get_ivshmem_regions_by_tree(scenario_etree)
38    if vm_id not in shmem_regions:
39        return
40    shmems = shmem_regions.get(vm_id)
41    for shm in shmems.values():
42        bdf = lib.lib.BusDevFunc.from_str(shm.get('vbdf'))
43        devdict[f"{IVSHMEM}_{shm.get('id')}"] = bdf
44        used.append(bdf)
45
46def insert_pt_devs_to_dev_dict(vm_node_etree, devdict, used):
47    """
48    Assign an unused bdf to each of passtrhough devices.
49    If a passtrhough device's bdf is in the list of HARDCODED_BDF_LIST, this device should apply the same bdf as native one.
50    Calls find_unused_bdf to assign an unused bdf for the rest of passtrhough devices except the ones in HARDCODED_BDF_LIST.
51    """
52    pt_devs = vm_node_etree.xpath(f".//pci_dev/text()")
53    # assign the bdf of the devices in HARDCODED_BDF_LIST
54    for pt_dev in pt_devs:
55        bdf_string = pt_dev.split()[0]
56        if bdf_string in HARDCODED_BDF_LIST:
57            bdf = lib.lib.BusDevFunc.from_str(bdf_string)
58            dev_name = str(bdf)
59            devdict[dev_name] = bdf
60            used.append(bdf)
61
62    # remove the pt_dev nodes which are in HARDCODED_BDF_LIST
63    pt_devs = [pt_dev for pt_dev in pt_devs if lib.lib.BusDevFunc.from_str(bdf_string) not in used]
64
65    # call find_unused_bdf to assign an unused bdf for other passthrough devices except the ones in HARDCODED_BDF_LIST
66    for pt_dev in pt_devs:
67        bdf = lib.lib.BusDevFunc.from_str(pt_dev.split()[0])
68        free_bdf = find_unused_bdf(used)
69        dev_name = str(bdf)
70        devdict[dev_name] = free_bdf
71        used.append(free_bdf)
72
73def get_devs_bdf_native(board_etree):
74    """
75    Get all pci devices' bdf in native environment.
76    return: list of pci devices' bdf
77    """
78    nodes = board_etree.xpath(f"//bus[@type = 'pci' and @address = '0x0']/device[@address]")
79    dev_list = []
80    for node in nodes:
81        address = node.get('address')
82        bus = int(get_node("../@address", node), 16)
83        dev = int(address, 16) >> 16
84        func = int(address, 16) & 0xffff
85
86        # According to section 6.1.1, ACPI Spec 6.4, _ADR of a device object under PCI/PCIe bus can use a special
87        # function number 0xFFFF to refer to all functions of a certain device. Such objects will have their own nodes
88        # in the board XML, but are out of the scope here as we are only interested in concrete BDFs that are already
89        # occupied.
90        #
91        # Thus, if the function number is 0xffff, we simply skip it.
92        if func != 0xffff:
93            dev_list.append(lib.lib.BusDevFunc(bus = bus, dev = dev, func = func))
94    return dev_list
95
96def get_devs_bdf_passthrough(scenario_etree):
97    """
98    Get all pre-launched vms' passthrough devices' bdf in native environment.
99    return: list of passtrhough devices' bdf.
100    """
101    dev_list = []
102    pt_devs = scenario_etree.xpath(f"//vm[load_order = 'PRE_LAUNCHED_VM']/pci_devs/pci_dev/text()")
103    for pt_dev in pt_devs:
104        bdf = lib.lib.BusDevFunc.from_str(pt_dev.split()[0])
105        dev_list.append(bdf)
106    return dev_list
107
108def create_device_node(allocation_etree, vm_id, devdict):
109    for dev in devdict:
110        dev_name = dev
111        bdf = devdict.get(dev)
112        vm_node = get_node(f"/acrn-config/vm[@id = '{vm_id}']", allocation_etree)
113        if vm_node is None:
114            vm_node = acrn_config_utilities.append_node("/acrn-config/vm", None, allocation_etree, id = vm_id)
115        dev_node = get_node(f"./device[@name = '{dev_name}']", vm_node)
116        if dev_node is None:
117            dev_node = acrn_config_utilities.append_node("./device", None, vm_node, name = dev_name)
118        if get_node(f"./bus", dev_node) is None:
119            acrn_config_utilities.append_node(f"./bus",  f"{bdf.bus:#04x}", dev_node)
120        if get_node(f"./dev", dev_node) is None:
121            acrn_config_utilities.append_node(f"./dev", f"{bdf.dev:#04x}", dev_node)
122        if get_node(f"./func", dev_node) is None:
123            acrn_config_utilities.append_node(f"./func", f"{bdf.func:#04x}", dev_node)
124
125def create_igd_sbdf(board_etree, allocation_etree):
126    """
127    Extract the integrated GPU bdf from board.xml. If the device is not present, set bdf to "0xFFFF" which indicates the device
128    doesn't exist.
129    """
130    bus = "0x0"
131    device_node = get_node(f"//bus[@type='pci' and @address='{bus}']/device[@address='0x20000' and vendor='0x8086' and class='0x030000']", board_etree)
132    if device_node is None:
133        acrn_config_utilities.append_node("/acrn-config/hv/MISC_CFG/IGD_SBDF", '0xFFFF', allocation_etree)
134    else:
135        address = device_node.get('address')
136        dev = int(address, 16) >> 16
137        func = int(address, 16) & 0xffff
138        acrn_config_utilities.append_node("/acrn-config/hv/MISC_CFG/IGD_SBDF", f"{(int(bus, 16) << 8) | (dev << 3) | func:#06x}", allocation_etree)
139
140def fn(board_etree, scenario_etree, allocation_etree):
141    create_igd_sbdf(board_etree, allocation_etree)
142    vm_nodes = scenario_etree.xpath("//vm")
143    for vm_node in vm_nodes:
144        vm_id = vm_node.get('id')
145        devdict = {}
146        used = []
147        load_order = get_node("./load_order/text()", vm_node)
148        if load_order is not None and lib.lib.is_post_launched_vm(load_order):
149            continue
150
151        if load_order is not None and lib.lib.is_service_vm(load_order):
152            native_used = get_devs_bdf_native(board_etree)
153            passthrough_used = get_devs_bdf_passthrough(scenario_etree)
154            used = [bdf for bdf in native_used if bdf not in passthrough_used]
155            if get_node("//@board", scenario_etree) == "tgl-rvp":
156                used.append(lib.lib.BusDevFunc(bus = 0, dev = 1, func = 0))
157
158        insert_vuart_to_dev_dict(vm_node, devdict, used)
159        insert_ivsheme_to_dev_dict(scenario_etree, devdict, vm_id, used)
160        insert_pt_devs_to_dev_dict(vm_node, devdict, used)
161        create_device_node(allocation_etree, vm_id, devdict)
162