[RFC][PATCH v021 8/9] cpufreq: intel_pstate: Introduce hybrid domains

Rafael J. Wysocki
2024-11-29T16:28:27Z

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Hybrid platforms contain different types of CPUs. They may differ
by micro-architecture, by cache topology, by manufacturing process, by
the interconnect access design etc. Of course, this means that power-
performance curves for CPUs of different types are generally different.

Because of these differences, CPUs of different types need to be handled
differently in certain situations and so it is convenient to operate
groups of CPUs that each contain CPUs of the same type. In intel_pstate,
each of them will be represented by a struct hybrid_domain object and
referred to as a hybrid domain.

A key problem is how to identify the type of a CPUs so as to know which
hybrid domain it belongs to. In principle, there are a few ways to do
it, but none of them is perfectly reliable.

From the computational perspective, an important factor is how many
instructions (on average) can be executed by the given CPU when it is
running at a specific frequency, often referred to as the IPC
(instructions per cycle) ratio of the given CPU to the least-capable
CPU in the system. In intel_pstate this ratio is represented by the
performance-to-frequency scaling factor which needs to be used to get
a frequency in kHz for a given HWP performance level of the given CPU.
Since HWP performance levels are in the same units for all CPUs in a
hybrid system, the smaller the scaling factor, the larger the IPC ratio
for the given CPU.

Of course, the performance-to-frequency scaling factor must be the
same for all CPUs of the same type. While it may be the same for CPUs
of different types, there is only one case in which that actually
happens (Meteor Lake platforms with two types of E-cores) and it is not
expected to happen again in the future. Moreover, when it happens,
there is no straightforward way to distinguish CPUs of different types
with the same scaling factor in general.

For this reason, the scaling factor is as good as it gets for CPU
type identification and so it is used for building hybrid domains in
intel_pstate.

On hybrid systems, every CPU is added to a hybrid domain at the
initialization time. If a hybrid domain with a matching scaling
factor is already present at that point, the CPU will be added to it.
Otherwise, a new hybrid domain will be created and the CPU will be
put into it. The domain's scaling factor will then be set to the
one of the new CPU.

So far, the new code doesn't do much beyond printing debud messages,
but subsequently the EAS support for intel_pstate will be based on it.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---
drivers/cpufreq/intel_pstate.c | 57 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)

Index: linux-pm/drivers/cpufreq/intel_pstate.c
===================================================================
--- linux-pm.orig/drivers/cpufreq/intel_pstate.c
+++ linux-pm/drivers/cpufreq/intel_pstate.c
@@ -943,6 +943,62 @@ static struct cpudata *hybrid_max_perf_c
*/
static DEFINE_MUTEX(hybrid_capacity_lock);

+#ifdef CONFIG_ENERGY_MODEL
+/*
+ * A hybrid domain is a collection of CPUs with the same perf-to-frequency
+ * scaling factor.
+ */
+struct hybrid_domain {
+ struct hybrid_domain *next;
+ cpumask_t cpumask;
+ int scaling;
+};
+
+static struct hybrid_domain *hybrid_domains;
+
+static void hybrid_add_to_domain(struct cpudata *cpudata)
+{
+ int scaling = cpudata->pstate.scaling;
+ int cpu = cpudata->cpu;
+ struct hybrid_domain *hd;
+
+ /* Do this only on hubrid platforms. */
+ if (!cpu_feature_enabled(X86_FEATURE_HYBRID_CPU))
+ return;
+
+ guard(mutex)(&hybrid_capacity_lock);
+
+ /* Look for an existing hybrid domain matching this CPU. */
+ for (hd = hybrid_domains; hd; hd = hd->next) {
+ if (hd->scaling == scaling) {
+ if (cpumask_test_cpu(cpu, &hd->cpumask))
+ return;
+
+ cpumask_set_cpu(cpu, &hd->cpumask);
+
+ pr_debug("CPU %d added to hybrid domain %*pbl\n", cpu,
+ cpumask_pr_args(&hd->cpumask));
+ return;
+ }
+ }
+
+ /* No match. Add a new one. */
+ hd = kzalloc(sizeof(*hd), GFP_KERNEL);
+ if (!hd)
+ return;
+
+ cpumask_set_cpu(cpu, &hd->cpumask);
+ hd->scaling = scaling;
+ hd->next = hybrid_domains;
+ hybrid_domains = hd;
+
+ pr_debug("New hybrid domain %*pbl: scaling = %d\n",
+ cpumask_pr_args(&hd->cpumask), hd->scaling);
+}
+#else /* CONFIG_ENERGY_MODEL */
+static inline void hybrid_add_to_domain(struct cpudata *cpudata) {}
+#endif /* !CONFIG_ENERGY_MODEL */
+
static void hybrid_set_cpu_capacity(struct cpudata *cpu)
{
arch_set_cpu_capacity(cpu->cpu, cpu->capacity_perf,
@@ -2273,6 +2329,7 @@ static void intel_pstate_get_cpu_pstates
intel_pstate_hybrid_hwp_adjust(cpu);
hwp_is_hybrid = true;
}
+ hybrid_add_to_domain(cpu);
} else {
cpu->pstate.scaling = perf_ctl_scaling;
}