acrn-hypervisor/misc/config_tools/static_allocators/cpu_freq.py

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)