1#!/usr/bin/env python3
2#
3# Copyright (C) 2022 Intel Corporation.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8import acrn_config_utilities, board_cfg_lib
9from acrn_config_utilities import get_node
10
11# CPU frequency dependency
12# Some CPU cores may share the same clock domain/group with others, which makes them always run at
13# the same frequency of the highest on in the group. Including those known conditions:
14#   1. CPU in the clock domain described in ACPI _PSD.
15#      Like _PSS, board_inspector extracted this data from Linux cpufreq driver
16#      (see Linux document 'sysfs-devices-system-cpu' about freqdomain_cpus)
17#   2. CPU hyper threads sharing the same physical core.
18#      The data is extracted form apic id.
19#   3. E-cores residents in the same topological group.
20#      The data is extracted form CPU model type and apic id.
21# CPU frequency dependency may have some impacts on our frequency limits.
22#
23# Returns a list that contains each CPU's "dependency data". The "dependency data" is also a list
24# containing CPU_IDs that share frequency with the current one.
25# e.g. CPU 8 is sharing with CPU 9,10,11, so dependency_data[8] = ['8', '9', '10', '11']
26def get_dependency(board_etree):
27    cpus = board_etree.xpath("//processors//thread")
28    dep_ret = []
29    for cpu in cpus:
30        cpu_id = get_node("./cpu_id/text()", cpu)
31        psd_cpus = [cpu_id]
32        psd_cpus_list = get_node("./freqdomain_cpus/text()", cpu)
33        if psd_cpus_list != None:
34            psd_cpus = psd_cpus_list.split(' ')
35        apic_id = int(get_node("./apic_id/text()", cpu)[2:], base=16)
36        is_hybrid = (len(board_etree.xpath("//processors//capability[@id='hybrid']")) != 0)
37        core_type = get_node("./core_type/text()", cpu)
38        for other_cpu in cpus:
39            other_cpu_id = get_node("./cpu_id/text()", other_cpu)
40            if cpu_id != other_cpu_id:
41                other_apic_id = int(get_node("./apic_id/text()", other_cpu)[2:], base=16)
42                other_core_type = get_node("./core_type/text()", other_cpu)
43                # threads at same core
44                if (apic_id & ~1) == (other_apic_id & ~1):
45                    psd_cpus.append(other_cpu_id)
46                # e-cores in the same group. Infered from Atom cores share the same L2 cache
47                share_cache = 0
48                if is_hybrid and core_type == 'Atom' and other_core_type == 'Atom':
49                    l2cache_nodes = board_etree.xpath("//caches/cache[@level='2']")
50                    for l2cache in l2cache_nodes:
51                        processors = l2cache.xpath("./processors/processor/text()")
52                        if '{:#x}'.format(apic_id) in processors and '{:#x}'.format(other_apic_id) in processors:
53                            share_cache = 1
54                if share_cache == 1:
55                    psd_cpus.append(other_cpu_id)
56
57        if psd_cpus != None:
58            psd_cpus = list(set(psd_cpus))
59            psd_cpus.sort()
60            dep_ret.insert(int(cpu_id), psd_cpus)
61        else:
62            dep_ret.insert(int(cpu_id), None)
63    return dep_ret
64
65# CPU frequency limits:
66#
67# Frequency limits is a per CPU data type. Hypervisor uses this data to quickly decide what performance
68# level/p-state range it should apply.
69#
70# Those limits are decided by hardware and scenario config.
71#
72# When the CPU is assigned to a RTVM, we want to set its frequency fixed.(to get more certainty
73# in latency). To do this, we just let highest_lvl = lowest_lvl.
74# Some CPU cores' frequency may be linked to each other in a frequency domain or group(eg. e-cores in a group).
75# In this condition, RTVM's CPU frequency might be influenced by other VMs. So we fix all of them to the value of
76# the RTVM's CPU frequence.
77#
78# Both HWP and ACPI p-state are supported in ACRN CPU performance management. So here we generate two sets of
79# data:
80#
81#   - 'limit_guaranteed_lvl', 'limit_highest_lvl' and 'limit_lowest_lvl' are for HWP. The values represent
82#     HWP performance level used in IA32_HWP_CAPABILITIES and IA32_HWP_REQUEST.
83#
84#   - 'limit_nominal_pstate', 'limit_highest_pstate' and 'limit_lowest_pstate' are for ACPI p-state.
85#     Those values represent the performance state's index P(x).
86#     ACPI p-state does not define a 'guaranteed p-state' or a 'base p-state'. Here the 'nominal p-state' refers
87#     to a state whose frequency is closest to the max none-turbo frequency.
88def alloc_limits(board_etree, scenario_etree, allocation_etree):
89    cpu_has_eist = (len(board_etree.xpath("//processors//capability[@id='est']")) != 0)
90    cpu_has_hwp = (len(board_etree.xpath("//processors//capability[@id='hwp_supported']")) != 0)
91    cpu_has_turbo = (len(board_etree.xpath("//processors//capability[@id='turbo_boost_available']")) != 0)
92    rtvm_cpus = scenario_etree.xpath(f"//vm[vm_type = 'RTVM']//cpu_affinity//pcpu_id/text()")
93    cpus = board_etree.xpath("//processors//thread")
94
95    for cpu in cpus:
96        cpu_id = get_node("./cpu_id/text()", cpu)
97        if cpu_has_hwp:
98            guaranteed_performance_lvl = get_node("./guaranteed_performance_lvl/text()", cpu)
99            highest_performance_lvl = get_node("./highest_performance_lvl/text()", cpu)
100            lowest_performance_lvl = get_node("./lowest_performance_lvl/text()", cpu)
101            if cpu_id in rtvm_cpus:
102                # for CPUs in RTVM, fix to base performance
103                limit_lowest_lvl = guaranteed_performance_lvl
104                limit_highest_lvl = guaranteed_performance_lvl
105                limit_guaranteed_lvl = guaranteed_performance_lvl
106            elif cpu_has_turbo:
107                limit_lowest_lvl = lowest_performance_lvl
108                limit_highest_lvl = highest_performance_lvl
109                limit_guaranteed_lvl = guaranteed_performance_lvl
110            else:
111                limit_lowest_lvl = lowest_performance_lvl
112                limit_highest_lvl = guaranteed_performance_lvl
113                limit_guaranteed_lvl = guaranteed_performance_lvl
114        else:
115                limit_lowest_lvl = 1
116                limit_highest_lvl = 0xff
117                limit_guaranteed_lvl = 0xff
118
119        cpu_node = acrn_config_utilities.append_node(f"//hv/cpufreq/CPU", None, allocation_etree, id = cpu_id)
120        limit_node = acrn_config_utilities.append_node("./limits", None, cpu_node)
121        acrn_config_utilities.append_node("./limit_guaranteed_lvl", limit_guaranteed_lvl, limit_node)
122        acrn_config_utilities.append_node("./limit_highest_lvl", limit_highest_lvl, limit_node)
123        acrn_config_utilities.append_node("./limit_lowest_lvl", limit_lowest_lvl, limit_node)
124
125        limit_highest_pstate = 0
126        limit_nominal_pstate = 0
127        limit_lowest_pstate = 0
128        if cpu_has_eist:
129            mntr = board_etree.xpath("//processors//attribute[@id='max_none_turbo_ratio']/text()")
130            none_turbo_p = 0
131            p_count = board_cfg_lib.get_p_state_count()
132            if len(mntr) != 0:
133                none_turbo_p = board_cfg_lib.get_p_state_index_from_ratio(int(mntr[0]))
134            if p_count != 0:
135                # P0 is the highest stat
136                if cpu_id in rtvm_cpus:
137                    # for CPUs in RTVM, fix to nominal performance(max none turbo frequency if turbo on)
138                    if cpu_has_turbo:
139                        limit_highest_pstate = none_turbo_p
140                        limit_nominal_pstate = none_turbo_p
141                        limit_lowest_pstate = none_turbo_p
142                    else:
143                        limit_highest_pstate = 0
144                        limit_nominal_pstate = 0
145                        limit_lowest_pstate = 0
146                else:
147                    if cpu_has_turbo:
148                        limit_highest_pstate = 0
149                        limit_nominal_pstate = none_turbo_p
150                        limit_lowest_pstate = p_count -1
151                    else:
152                        limit_highest_pstate = 0
153                        limit_nominal_pstate = 0
154                        limit_lowest_pstate = p_count -1
155
156        acrn_config_utilities.append_node("./limit_nominal_pstate", str(limit_nominal_pstate), limit_node)
157        acrn_config_utilities.append_node("./limit_highest_pstate", str(limit_highest_pstate), limit_node)
158        acrn_config_utilities.append_node("./limit_lowest_pstate", str(limit_lowest_pstate), limit_node)
159
160    # Let CPUs in the same frequency dependency group have the same limits. So that RTVM's frequency can be fixed
161    dep_info = get_dependency(board_etree)
162    for alloc_cpu in allocation_etree.xpath("//cpufreq/CPU"):
163        dependency_cpus = dep_info[int(alloc_cpu.attrib['id'])]
164        if get_node("./limits", alloc_cpu) != None:
165            highest_lvl = int(get_node(".//limit_highest_lvl/text()", alloc_cpu))
166            lowest_lvl = int(get_node(".//limit_lowest_lvl/text()", alloc_cpu))
167            highest_pstate = int(get_node(".//limit_highest_pstate/text()", alloc_cpu))
168            lowest_pstate = int(get_node(".//limit_lowest_pstate/text()", alloc_cpu))
169
170            for dep_cpu_id in dependency_cpus:
171                dep_highest_lvl = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_highest_lvl/text()", allocation_etree))
172                dep_lowest_lvl = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_lowest_lvl/text()", allocation_etree))
173                if highest_lvl > dep_highest_lvl:
174                    highest_lvl = dep_highest_lvl
175                if lowest_lvl < dep_lowest_lvl:
176                    lowest_lvl = dep_lowest_lvl
177                dep_highest_pstate = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_highest_pstate/text()", allocation_etree))
178                dep_lowest_pstate = int(get_node(f"//cpufreq/CPU[@id={dep_cpu_id}]//limit_lowest_pstate/text()", allocation_etree))
179                if highest_pstate < dep_highest_pstate:
180                    highest_pstate = dep_highest_pstate
181                if lowest_pstate > dep_lowest_pstate:
182                    lowest_pstate = dep_lowest_pstate
183
184            acrn_config_utilities.update_text("./limits/limit_highest_lvl", str(highest_lvl), alloc_cpu, True)
185            acrn_config_utilities.update_text("./limits/limit_lowest_lvl", str(lowest_lvl), alloc_cpu, True)
186            acrn_config_utilities.update_text("./limits/limit_highest_pstate", str(highest_pstate), alloc_cpu, True)
187            acrn_config_utilities.update_text("./limits/limit_lowest_pstate", str(lowest_pstate), alloc_cpu, True)
188
189def fn(board_etree, scenario_etree, allocation_etree):
190    acrn_config_utilities.append_node("/acrn-config/hv/cpufreq", None, allocation_etree)
191    alloc_limits(board_etree, scenario_etree, allocation_etree)
192