192 lines
10 KiB
Python
192 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2022 Intel Corporation.
|
|
#
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
#
|
|
|
|
import acrn_config_utilities, board_cfg_lib
|
|
from acrn_config_utilities import get_node
|
|
|
|
# CPU frequency dependency
|
|
# Some CPU cores may share the same clock domain/group with others, which makes them always run at
|
|
# the same frequency of the highest on in the group. Including those known conditions:
|
|
# 1. CPU in the clock domain described in ACPI _PSD.
|
|
# Like _PSS, board_inspector extracted this data from Linux cpufreq driver
|
|
# (see Linux document 'sysfs-devices-system-cpu' about freqdomain_cpus)
|
|
# 2. CPU hyper threads sharing the same physical core.
|
|
# The data is extracted form apic id.
|
|
# 3. E-cores residents in the same topological group.
|
|
# The data is extracted form CPU model type and apic id.
|
|
# CPU frequency dependency may have some impacts on our frequency limits.
|
|
#
|
|
# Returns a list that contains each CPU's "dependency data". The "dependency data" is also a list
|
|
# containing CPU_IDs that share frequency with the current one.
|
|
# e.g. CPU 8 is sharing with CPU 9,10,11, so dependency_data[8] = ['8', '9', '10', '11']
|
|
def get_dependency(board_etree):
|
|
cpus = board_etree.xpath("//processors//thread")
|
|
dep_ret = []
|
|
for cpu in cpus:
|
|
cpu_id = get_node("./cpu_id/text()", cpu)
|
|
psd_cpus = [cpu_id]
|
|
psd_cpus_list = get_node("./freqdomain_cpus/text()", cpu)
|
|
if psd_cpus_list != None:
|
|
psd_cpus = psd_cpus_list.split(' ')
|
|
apic_id = int(get_node("./apic_id/text()", cpu)[2:], base=16)
|
|
is_hybrid = (len(board_etree.xpath("//processors//capability[@id='hybrid']")) != 0)
|
|
core_type = get_node("./core_type/text()", cpu)
|
|
for other_cpu in cpus:
|
|
other_cpu_id = get_node("./cpu_id/text()", other_cpu)
|
|
if cpu_id != other_cpu_id:
|
|
other_apic_id = int(get_node("./apic_id/text()", other_cpu)[2:], base=16)
|
|
other_core_type = get_node("./core_type/text()", other_cpu)
|
|
# threads at same core
|
|
if (apic_id & ~1) == (other_apic_id & ~1):
|
|
psd_cpus.append(other_cpu_id)
|
|
# e-cores in the same group. Infered from Atom cores share the same L2 cache
|
|
share_cache = 0
|
|
if is_hybrid and core_type == 'Atom' and other_core_type == 'Atom':
|
|
l2cache_nodes = board_etree.xpath("//caches/cache[@level='2']")
|
|
for l2cache in l2cache_nodes:
|
|
processors = l2cache.xpath("./processors/processor/text()")
|
|
if '{:#x}'.format(apic_id) in processors and '{:#x}'.format(other_apic_id) in processors:
|
|
share_cache = 1
|
|
if share_cache == 1:
|
|
psd_cpus.append(other_cpu_id)
|
|
|
|
if psd_cpus != None:
|
|
psd_cpus = list(set(psd_cpus))
|
|
psd_cpus.sort()
|
|
dep_ret.insert(int(cpu_id), psd_cpus)
|
|
else:
|
|
dep_ret.insert(int(cpu_id), None)
|
|
return dep_ret
|
|
|
|
# CPU frequency limits:
|
|
#
|
|
# Frequency limits is a per CPU data type. Hypervisor uses this data to quickly decide what performance
|
|
# level/p-state range it should apply.
|
|
#
|
|
# Those limits are decided by hardware and scenario config.
|
|
#
|
|
# When the CPU is assigned to a RTVM, we want to set its frequency fixed.(to get more certainty
|
|
# in latency). To do this, we just let highest_lvl = lowest_lvl.
|
|
# Some CPU cores' frequency may be linked to each other in a frequency domain or group(eg. e-cores in a group).
|
|
# In this condition, RTVM's CPU frequency might be influenced by other VMs. So we fix all of them to the value of
|
|
# the RTVM's CPU frequence.
|
|
#
|
|
# Both HWP and ACPI p-state are supported in ACRN CPU performance management. So here we generate two sets of
|
|
# data:
|
|
#
|
|
# - 'limit_guaranteed_lvl', 'limit_highest_lvl' and 'limit_lowest_lvl' are for HWP. The values represent
|
|
# HWP performance level used in IA32_HWP_CAPABILITIES and IA32_HWP_REQUEST.
|
|
#
|
|
# - 'limit_nominal_pstate', 'limit_highest_pstate' and 'limit_lowest_pstate' are for ACPI p-state.
|
|
# Those values represent the performance state's index P(x).
|
|
# ACPI p-state does not define a 'guaranteed p-state' or a 'base p-state'. Here the 'nominal p-state' refers
|
|
# to a state whose frequency is closest to the max none-turbo frequency.
|
|
def alloc_limits(board_etree, scenario_etree, allocation_etree):
|
|
cpu_has_eist = (len(board_etree.xpath("//processors//capability[@id='est']")) != 0)
|
|
cpu_has_hwp = (len(board_etree.xpath("//processors//capability[@id='hwp_supported']")) != 0)
|
|
cpu_has_turbo = (len(board_etree.xpath("//processors//capability[@id='turbo_boost_available']")) != 0)
|
|
rtvm_cpus = scenario_etree.xpath(f"//vm[vm_type = 'RTVM']//cpu_affinity//pcpu_id/text()")
|
|
cpus = board_etree.xpath("//processors//thread")
|
|
|
|
for cpu in cpus:
|
|
cpu_id = get_node("./cpu_id/text()", cpu)
|
|
if cpu_has_hwp:
|
|
guaranteed_performance_lvl = get_node("./guaranteed_performance_lvl/text()", cpu)
|
|
highest_performance_lvl = get_node("./highest_performance_lvl/text()", cpu)
|
|
lowest_performance_lvl = get_node("./lowest_performance_lvl/text()", cpu)
|
|
if cpu_id in rtvm_cpus:
|
|
# for CPUs in RTVM, fix to base performance
|
|
limit_lowest_lvl = guaranteed_performance_lvl
|
|
limit_highest_lvl = guaranteed_performance_lvl
|
|
limit_guaranteed_lvl = guaranteed_performance_lvl
|
|
elif cpu_has_turbo:
|
|
limit_lowest_lvl = lowest_performance_lvl
|
|
limit_highest_lvl = highest_performance_lvl
|
|
limit_guaranteed_lvl = guaranteed_performance_lvl
|
|
else:
|
|
limit_lowest_lvl = lowest_performance_lvl
|
|
limit_highest_lvl = guaranteed_performance_lvl
|
|
limit_guaranteed_lvl = guaranteed_performance_lvl
|
|
else:
|
|
limit_lowest_lvl = 1
|
|
limit_highest_lvl = 0xff
|
|
limit_guaranteed_lvl = 0xff
|
|
|
|
cpu_node = acrn_config_utilities.append_node(f"//hv/cpufreq/CPU", None, allocation_etree, id = cpu_id)
|
|
limit_node = acrn_config_utilities.append_node("./limits", None, cpu_node)
|
|
acrn_config_utilities.append_node("./limit_guaranteed_lvl", limit_guaranteed_lvl, limit_node)
|
|
acrn_config_utilities.append_node("./limit_highest_lvl", limit_highest_lvl, limit_node)
|
|
acrn_config_utilities.append_node("./limit_lowest_lvl", limit_lowest_lvl, limit_node)
|
|
|
|
limit_highest_pstate = 0
|
|
limit_nominal_pstate = 0
|
|
limit_lowest_pstate = 0
|
|
if cpu_has_eist:
|
|
mntr = board_etree.xpath("//processors//attribute[@id='max_none_turbo_ratio']/text()")
|
|
none_turbo_p = 0
|
|
p_count = board_cfg_lib.get_p_state_count()
|
|
if len(mntr) != 0:
|
|
none_turbo_p = board_cfg_lib.get_p_state_index_from_ratio(int(mntr[0]))
|
|
if p_count != 0:
|
|
# P0 is the highest stat
|
|
if cpu_id in rtvm_cpus:
|
|
# for CPUs in RTVM, fix to nominal performance(max none turbo frequency if turbo on)
|
|
if cpu_has_turbo:
|
|
limit_highest_pstate = none_turbo_p
|
|
limit_nominal_pstate = none_turbo_p
|
|
limit_lowest_pstate = none_turbo_p
|
|
else:
|
|
limit_highest_pstate = 0
|
|
limit_nominal_pstate = 0
|
|
limit_lowest_pstate = 0
|
|
else:
|
|
if cpu_has_turbo:
|
|
limit_highest_pstate = 0
|
|
limit_nominal_pstate = none_turbo_p
|
|
limit_lowest_pstate = p_count -1
|
|
else:
|
|
limit_highest_pstate = 0
|
|
limit_nominal_pstate = 0
|
|
limit_lowest_pstate = p_count -1
|
|
|
|
acrn_config_utilities.append_node("./limit_nominal_pstate", str(limit_nominal_pstate), limit_node)
|
|
acrn_config_utilities.append_node("./limit_highest_pstate", str(limit_highest_pstate), limit_node)
|
|
acrn_config_utilities.append_node("./limit_lowest_pstate", str(limit_lowest_pstate), limit_node)
|
|
|
|
# Let CPUs in the same frequency dependency group have the same limits. So that RTVM's frequency can be fixed
|
|
dep_info = get_dependency(board_etree)
|
|
for alloc_cpu in allocation_etree.xpath("//cpufreq/CPU"):
|
|
dependency_cpus = dep_info[int(alloc_cpu.attrib['id'])]
|
|
if get_node("./limits", alloc_cpu) != None:
|
|
highest_lvl = int(get_node(".//limit_highest_lvl/text()", alloc_cpu))
|
|
lowest_lvl = int(get_node(".//limit_lowest_lvl/text()", alloc_cpu))
|
|
highest_pstate = int(get_node(".//limit_highest_pstate/text()", alloc_cpu))
|
|
lowest_pstate = int(get_node(".//limit_lowest_pstate/text()", alloc_cpu))
|
|
|
|
for dep_cpu_id in dependency_cpus:
|
|
dep_highest_lvl = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_highest_lvl/text()", allocation_etree))
|
|
dep_lowest_lvl = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_lowest_lvl/text()", allocation_etree))
|
|
if highest_lvl > dep_highest_lvl:
|
|
highest_lvl = dep_highest_lvl
|
|
if lowest_lvl < dep_lowest_lvl:
|
|
lowest_lvl = dep_lowest_lvl
|
|
dep_highest_pstate = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_highest_pstate/text()", allocation_etree))
|
|
dep_lowest_pstate = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_lowest_pstate/text()", allocation_etree))
|
|
if highest_pstate < dep_highest_pstate:
|
|
highest_pstate = dep_highest_pstate
|
|
if lowest_pstate > dep_lowest_pstate:
|
|
lowest_pstate = dep_lowest_pstate
|
|
|
|
acrn_config_utilities.update_text("./limits/limit_highest_lvl", str(highest_lvl), alloc_cpu, True)
|
|
acrn_config_utilities.update_text("./limits/limit_lowest_lvl", str(lowest_lvl), alloc_cpu, True)
|
|
acrn_config_utilities.update_text("./limits/limit_highest_pstate", str(highest_pstate), alloc_cpu, True)
|
|
acrn_config_utilities.update_text("./limits/limit_lowest_pstate", str(lowest_pstate), alloc_cpu, True)
|
|
|
|
def fn(board_etree, scenario_etree, allocation_etree):
|
|
acrn_config_utilities.append_node("/acrn-config/hv/cpufreq", None, allocation_etree)
|
|
alloc_limits(board_etree, scenario_etree, allocation_etree)
|