acrn-hypervisor/misc/config_tools/static_allocators/intx.py
Yang,Yu-chu 4ca2b3a860 config-tools: allow SERIAL_CONSOLE to use any native ttyS#
Remove the restriction that SERIAL_CONSOLE needs to be ttys0, ttys1,
ttys2 or ttys3.

1. Lossen the restriction in xsd.
2. Rewrite the document.
3. Refine the intx.py. Refine the logic which take effect if the <irq>
is specified in "SOS_COM#_IRQ" for SOS VM's legacy vuart 0.

Tracked-On: #6610
Signed-off-by: Yang,Yu-chu <yu-chu.yang@intel.com>
2021-10-23 09:34:43 +08:00

225 lines
11 KiB
Python

#!/usr/bin/env python3
#
# Copyright (C) 2021 Intel Corporation. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
import sys, os
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'library'))
import common, board_cfg_lib, lib.error, lib.lib
import re
from collections import defaultdict
from itertools import combinations
LEGACY_IRQ_MAX = 16
def get_native_valid_irq():
"""
This is get available irq from board info file
:return: native available irq list
"""
val_irq = []
irq_info_lines = board_cfg_lib.get_info(common.BOARD_INFO_FILE, "<AVAILABLE_IRQ_INFO>", "</AVAILABLE_IRQ_INFO>")
for irq_string in irq_info_lines:
val_irq = [int(x.strip()) for x in irq_string.split(',')]
return val_irq
def alloc_irq(irq_list):
try:
irq = irq_list[0]
remove_irq(irq_list, irq)
return irq
except IndexError as e:
raise lib.error.ResourceError("Cannot allocate legacy irq, the available irq list: {}, {}".format(e, irq_list)) from e
def remove_irq(irq_list, irq):
try:
irq_list.remove(irq)
except ValueError as e:
raise ValueError("Cannot remove irq:{} from available irq list:{}, {}". format(irq, e, irq_list)) from e
def create_vuart_irq_node(etree, vm_id, vm_type, vuart_id, irq):
allocation_vm_node = common.get_node(f"/acrn-config/vm[@id = '{vm_id}']", etree)
if allocation_vm_node is None:
allocation_vm_node = common.append_node("/acrn-config/vm", None, etree, id = vm_id)
if common.get_node("./vm_type", allocation_vm_node) is None:
common.append_node("./vm_type", vm_type, allocation_vm_node)
if common.get_node(f"./legacy_vuart[@id = '{vuart_id}']", allocation_vm_node) is None:
common.append_node("./legacy_vuart", None, allocation_vm_node, id = vuart_id)
common.append_node(f"./legacy_vuart[@id = '{vuart_id}']/irq", irq, allocation_vm_node)
def assign_legacy_vuart_irqs(vm_node, legacy_vuart_id, irq_list):
legacy_vuart_node_irq_text = common.get_node(f"legacy_vuart[@id = '{legacy_vuart_id}']/irq/text()", vm_node)
legacy_vuart_irq = ''
if legacy_vuart_node_irq_text == 'COM1_IRQ' or legacy_vuart_node_irq_text == 'SOS_COM1_IRQ' \
or legacy_vuart_node_irq_text == 'COM3_IRQ' or legacy_vuart_node_irq_text == 'SOS_COM3_IRQ':
legacy_vuart_irq = '4'
if legacy_vuart_irq in irq_list:
remove_irq(irq_list, legacy_vuart_irq)
elif legacy_vuart_node_irq_text == 'COM2_IRQ' or legacy_vuart_node_irq_text == 'SOS_COM2_IRQ' \
or legacy_vuart_node_irq_text == 'COM4_IRQ' or legacy_vuart_node_irq_text == 'SOS_COM4_IRQ':
legacy_vuart_irq = '3'
if legacy_vuart_irq in irq_list:
remove_irq(irq_list, legacy_vuart_irq)
else:
legacy_vuart_irq = alloc_irq(irq_list)
return legacy_vuart_irq
def alloc_legacy_vuart_irqs(board_etree, scenario_etree, allocation_etree):
native_ttys = lib.lib.get_native_ttys()
hv_debug_console = lib.lib.parse_hv_console(scenario_etree)
vm_node_list = scenario_etree.xpath("//vm")
for vm_node in vm_node_list:
vm_type = common.get_node("./vm_type/text()", vm_node)
irq_list = get_native_valid_irq() if vm_type == "SOS_VM" else [f"{d}" for d in list(range(1,15))]
legacy_vuart_id_list = vm_node.xpath("legacy_vuart[base != 'INVALID_COM_BASE']/@id")
legacy_vuart_irq = ''
for legacy_vuart_id in legacy_vuart_id_list:
if legacy_vuart_id == '0' and vm_type == "SOS_VM":
if hv_debug_console in native_ttys.keys():
if native_ttys[hv_debug_console]['irq'] < LEGACY_IRQ_MAX:
legacy_vuart_irq = native_ttys[hv_debug_console]['irq']
if legacy_vuart_irq in irq_list:
remove_irq(irq_list, legacy_vuart_irq)
else:
legacy_vuart_irq = assign_legacy_vuart_irqs(vm_node, legacy_vuart_id, irq_list)
else:
raise lib.error.ResourceError(f"{hv_debug_console} is not in the native environment! The ttyS available are: {native_ttys.keys()}")
else:
legacy_vuart_irq = assign_legacy_vuart_irqs(vm_node, legacy_vuart_id, irq_list)
create_vuart_irq_node(allocation_etree, common.get_node("./@id", vm_node), vm_type, legacy_vuart_id, legacy_vuart_irq)
def get_irqs_of_device(device_node):
irqs = set()
# IRQs in ACPI
for res in device_node.xpath("resource[@type='irq']"):
irqs.update(set(map(int, res.get("int").split(", "))))
# PCI interrupt pin
for res in device_node.xpath("resource[@type='interrupt_pin']"):
irq = res.get("source", None)
if irq is not None:
irqs.add(int(irq))
return irqs
def alloc_device_irqs(board_etree, scenario_etree, allocation_etree):
service_vm_id = -1
irq_allocation = defaultdict(lambda: defaultdict(lambda: [])) # vm_id -> irq -> [device]
# Collect the list of devices that have to use INTx, excluding legacy UART which is to be emulated.
device_nodes = set(board_etree.xpath("//device[count(resource[@type='irq' or @type='interrupt_pin']) > 0 and count(capability[@id='MSI' or @id='MSI-X']) = 0]"))
uart_nodes = set(board_etree.xpath("//device[@id='PNP0501']"))
device_nodes -= uart_nodes
#
# Identify the interrupt lines each pre-launched VM uses
#
for vm in scenario_etree.xpath("//vm"):
vm_type = vm.find("vm_type").text
vm_id = int(vm.get("id"))
if lib.lib.is_pre_launched_vm(vm_type):
pt_intx_text = common.get_node("pt_intx/text()", vm)
if pt_intx_text is not None:
pt_intx_mapping = dict(eval(f"[{pt_intx_text.replace(')(', '), (')}]"))
for irq in pt_intx_mapping.keys():
irq_allocation[vm_id][irq].append("(Explicitly assigned in scenario configuration)")
for pci_dev in vm.xpath("pci_devs/pci_dev/text()"):
bdf = lib.lib.BusDevFunc.from_str(pci_dev.split(" ")[0])
address = hex((bdf.dev << 16) | (bdf.func))
device_node = common.get_node(f"//bus[@address='{hex(bdf.bus)}']/device[@address='{address}']", board_etree)
if device_node in device_nodes:
irqs = get_irqs_of_device(device_node)
for irq in irqs:
irq_allocation[vm_id][irq].append(pci_dev)
device_nodes.discard(device_node)
# Raise error when any pre-launched VM with LAPIC passthrough requires any interrupt line.
lapic_passthru_flag = common.get_node("guest_flags[guest_flag='GUEST_FLAG_LAPIC_PASSTHROUGH']", vm)
if lapic_passthru_flag is not None and irq_allocation[vm_id]:
for irq, devices in irq_allocation[vm_id].items():
print(f"Interrupt line {irq} is used by the following device(s).")
for device in devices:
print(f"\t{device}")
raise lib.error.ResourceError(f"Pre-launched VM {vm_id} with LAPIC_PASSTHROUGH flag cannot use interrupt lines.")
elif lib.lib.is_sos_vm(vm_type):
service_vm_id = vm_id
#
# Detect interrupt line conflicts
#
conflicts = defaultdict(lambda: defaultdict(lambda: set())) # irq -> vm_id -> devices
# If a service VM exists, collect its interrupt lines as well
if service_vm_id >= 0:
# Collect the interrupt lines that may be used by the service VM
for device_node in device_nodes:
acpi_object = device_node.find("acpi_object")
description = ""
if acpi_object is not None:
description = acpi_object.text
description = device_node.get("description", description)
# Guess BDF of the device
bus = device_node.getparent()
if bus.tag == "bus" and bus.get("type") == "pci" and device_node.get("address") is not None:
bus_number = int(bus.get("address"), 16)
address = int(device_node.get("address"), 16)
device_number = address >> 16
function_number = address & 0xffff
description = f"{bus_number:02x}:{device_number:02x}.{function_number} {description}"
for irq in get_irqs_of_device(device_node):
irq_allocation[service_vm_id][irq].append(description)
# Identify and report conflicts among interrupt lines of the VMs
for vm1, vm2 in combinations(irq_allocation.keys(), 2):
common_irqs = set(irq_allocation[vm1].keys()) & set(irq_allocation[vm2].keys())
for irq in common_irqs:
conflicts[irq][vm1].update(set(irq_allocation[vm1][irq]))
conflicts[irq][vm2].update(set(irq_allocation[vm2][irq]))
if conflicts:
print("Interrupt line conflicts detected!")
for irq, vm_devices in sorted(conflicts.items()):
print(f"Interrupt line {irq} is shared by the following devices.")
for vm_id, devices in vm_devices.items():
for device in sorted(devices):
print(f"\tVM {vm_id}: {device}")
raise lib.error.ResourceError(f"VMs have conflicting interrupt lines.")
#
# Dump allocations to allocation_etree. The virtual interrupt line is the same as the physical one unless otherwise
# stated in the scenario configuration.
#
for vm_id, alloc in irq_allocation.items():
vm_node = common.get_node(f"/acrn-config/vm[@id = '{vm_id}']", allocation_etree)
if vm_node is None:
vm_node = common.append_node("/acrn-config/vm", None, allocation_etree, id = str(vm_id))
pt_intx_text = common.get_node(f"//vm[@id='{vm_id}']/pt_intx/text()", scenario_etree)
pt_intx_mapping = dict(eval(f"[{pt_intx_text.replace(')(', '), (')}]")) if pt_intx_text is not None else {}
for irq, devs in alloc.items():
for dev in devs:
if dev.startswith("("): # Allocation in the scenario configuration need not go to allocation.xml
continue
bdf = dev.split(" ")[0]
dev_name = f"PTDEV_{bdf}"
dev_node = common.get_node(f"device[@name = '{dev_name}']", vm_node)
if dev_node is None:
dev_node = common.append_node("./device", None, vm_node, name = dev_name)
pt_intx_node = common.get_node(f"pt_intx", dev_node)
virq = pt_intx_mapping.get(irq, irq)
if pt_intx_node is None:
common.append_node(f"./pt_intx", f"({irq}, {virq})", dev_node)
else:
pt_intx_node.text += f" ({irq}, {virq})"
def fn(board_etree, scenario_etree, allocation_etree):
alloc_legacy_vuart_irqs(board_etree, scenario_etree, allocation_etree)
alloc_device_irqs(board_etree, scenario_etree, allocation_etree)