[Patch 5/5] cpufreq: CPPC: Add cppc_cpufreq_epp instance for Autonomous mode

Sumit Gupta
2025-02-11T10:38:43Z

Add new 'cppc_cpufreq_epp' instance of the 'cppc_cpufreq' driver for
supporting the Autonomous Performance Level Selection and Engergy
Performance Preference (EPP) as per the ACPI specification.
Autonomous selection will get enabled during boot if 'cppc_auto_sel'
bootarg is present or the 'Autonomous Selection Enable' register is
set before kernel boot.

When Autonomous selection capability is enabled, then the hardware is
allowed to autonomously select the CPU frequency within the min and
max perf boundaries according to EPP hints.

EPP values range from '0x0'(performance preference) to '0xFF'(energy
efficiency preference). It influences the rate of performance
increase/decrease and the result of the hardware's energy efficiency
and performance optimization policies.

Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
.../admin-guide/kernel-parameters.txt | 11 +
drivers/cpufreq/cppc_cpufreq.c | 234 +++++++++++++++++-
2 files changed, 241 insertions(+), 4 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index aa7447f8837c..8777970e6e35 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -904,6 +904,17 @@
Format:
<first_slot>,<last_slot>,<port>,<enum_bit>[,<debug>]

+ cppc_auto_sel= [CPU_FREQ] Autonomous Performance Level Selection.
+ When Autonomous selection is enabled, then the hardware is
+ allowed to autonomously select the CPU frequency.
+ In Autonomous mode, Engergy Performance Preference(EPP)
+ provides input to the hardware to favour performance (0x0)
+ or energy efficiency (0xff).
+ Format: <bool>
+ Default: disabled.
+ 0: force disabled
+ 1: force enabled
+
cpuidle.off=1 [CPU_IDLE]
disable the cpuidle sub-system

diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 17c49653a3c4..a4fa46cc2a6f 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -34,7 +34,10 @@
*/
static LIST_HEAD(cpu_data_list);

-static struct cpufreq_driver cppc_cpufreq_driver;
+/* Autonomous Selection */
+static bool auto_sel_mode;
+
+static struct cpufreq_driver *current_cppc_cpufreq_driver;

#ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
static enum {
@@ -519,7 +522,7 @@ static int populate_efficiency_class(void)
}
index++;
}
- cppc_cpufreq_driver.register_em = cppc_cpufreq_register_em;
+ current_cppc_cpufreq_driver->register_em = cppc_cpufreq_register_em;

return 0;
}
@@ -567,6 +570,12 @@ static struct cppc_cpudata *cppc_cpufreq_get_cpu_data(unsigned int cpu)
goto free_mask;
}

+ ret = cppc_get_perf_ctrls(cpu, &cpu_data->perf_ctrls);
+ if (ret) {
+ pr_debug("Err reading CPU%d perf ctrls: ret:%d\n", cpu, ret);
+ goto free_mask;
+ }
+
list_add(&cpu_data->node, &cpu_data_list);

return cpu_data;
@@ -672,6 +681,171 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
return ret;
}

+static int cppc_cpufreq_epp_cpu_init(struct cpufreq_policy *policy)
+{
+ unsigned int cpu = policy->cpu;
+ struct cppc_cpudata *cpu_data;
+ struct cppc_perf_ctrls *ctrls;
+ struct cppc_perf_caps *caps;
+ int ret;
+
+ cpu_data = cppc_cpufreq_get_cpu_data(cpu);
+ if (!cpu_data) {
+ pr_err("Error in acquiring _CPC/_PSD data for CPU%d.\n", cpu);
+ return -ENODEV;
+ }
+ caps = &cpu_data->perf_caps;
+ ctrls = &cpu_data->perf_ctrls;
+ policy->driver_data = cpu_data;
+
+ policy->min = cppc_perf_to_khz(caps, ctrls->min_perf);
+ policy->max = cppc_perf_to_khz(caps, ctrls->max_perf);
+
+ /*
+ * Set cpuinfo.min_freq to Lowest to make the full range of performance
+ * available if userspace wants to use any perf between lowest & minimum perf
+ */
+ policy->cpuinfo.min_freq = cppc_perf_to_khz(caps, caps->lowest_perf);
+ policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, caps->highest_perf);
+
+ policy->transition_delay_us = cppc_cpufreq_get_transition_delay_us(cpu);
+ policy->shared_type = cpu_data->shared_type;
+
+ switch (policy->shared_type) {
+ case CPUFREQ_SHARED_TYPE_HW:
+ case CPUFREQ_SHARED_TYPE_NONE:
+ /* Nothing to be done - we'll have a policy for each CPU */
+ break;
+ case CPUFREQ_SHARED_TYPE_ANY:
+ /*
+ * All CPUs in the domain will share a policy and all cpufreq
+ * operations will use a single cppc_cpudata structure stored
+ * in policy->driver_data.
+ */
+ cpumask_copy(policy->cpus, cpu_data->shared_cpu_map);
+ break;
+ default:
+ pr_debug("Unsupported CPU co-ord type: %d\n",
+ policy->shared_type);
+ ret = -EFAULT;
+ goto out;
+ }
+
+ /* Set policy->cur to max now. The governors will adjust later. */
+ policy->cur = cppc_perf_to_khz(caps, caps->highest_perf);
+
+ ret = cppc_set_perf_ctrls(cpu, &cpu_data->perf_ctrls);
+ if (ret) {
+ pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n",
+ caps->highest_perf, cpu, ret);
+ goto out;
+ }
+
+ cppc_cpufreq_cpu_fie_init(policy);
+ return 0;
+
+out:
+ cppc_cpufreq_put_cpu_data(policy);
+ return ret;
+}
+
+static int cppc_cpufreq_epp_update_perf_ctrls(struct cpufreq_policy *policy,
+ u32 highest_perf, u32 lowest_perf)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ int ret;
+
+ pr_debug("cpu%d, curr max_perf:%u, curr min_perf:%u, highest_perf:%u, lowest_perf:%u\n",
+ policy->cpu, cpu_data->perf_ctrls.max_perf, cpu_data->perf_ctrls.min_perf,
+ highest_perf, lowest_perf);
+
+ cpu_data->perf_ctrls.max_perf = highest_perf;
+ cpu_data->perf_ctrls.min_perf = lowest_perf;
+
+ ret = cppc_set_perf_ctrls(policy->cpu, &cpu_data->perf_ctrls);
+ if (ret) {
+ pr_debug("Failed to set perf_ctrls on CPU:%d. ret:%d\n", policy->cpu, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int cppc_cpufreq_epp_update_auto_mode(struct cpufreq_policy *policy, int auto_sel, u32 epp)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ int ret, curr_epp;
+
+ curr_epp = cpu_data->perf_ctrls.energy_perf;
+ pr_debug("cpu%d, curr epp:%u, new epp:%u, curr mode:%u, new mode:%u\n",
+ curr_epp, epp, cpu_data->perf_caps.auto_sel, auto_sel);
+
+ /* set Performance preference as default */
+ cpu_data->perf_ctrls.energy_perf = epp;
+ ret = cppc_set_epp_perf(policy->cpu, &cpu_data->perf_ctrls, auto_sel);
+ if (ret < 0) {
+ pr_err("failed to set energy perf value (%d)\n", ret);
+ cpu_data->perf_ctrls.energy_perf = curr_epp;
+ return ret;
+ }
+ cpu_data->perf_caps.auto_sel = auto_sel;
+
+ return ret;
+}
+
+static int cppc_cpufreq_epp_update_perf(struct cpufreq_policy *policy, int auto_sel, u32 epp,
+ u32 highest_perf, u32 lowest_perf)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ int ret;
+
+ ret = cppc_cpufreq_epp_update_perf_ctrls(policy, highest_perf, lowest_perf);
+ if (ret)
+ return ret;
+
+ ret = cppc_cpufreq_epp_update_auto_mode(policy, auto_sel, epp);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int cppc_cpufreq_epp_set_policy(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ int ret;
+
+ if (!cpu_data)
+ return -ENODEV;
+
+ ret = cppc_cpufreq_epp_update_perf(policy, true, CPPC_EPP_PERFORMANCE_PREF,
+ cpu_data->perf_caps.highest_perf,
+ cpu_data->perf_caps.lowest_perf);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static void cppc_cpufreq_epp_cpu_exit(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ int ret;
+
+ cppc_cpufreq_cpu_fie_exit(policy);
+
+ cpu_data->perf_ctrls.desired_perf = cpu_data->perf_caps.lowest_perf;
+
+ cppc_cpufreq_epp_update_perf_ctrls(policy, cpu_data->perf_caps.highest_perf,
+ cpu_data->perf_caps.lowest_perf);
+
+ ret = cppc_set_auto_sel(policy->cpu, false);
+ if (ret)
+ pr_debug("failed to disable autonomous selection (%d)\n", ret);
+
+ cppc_cpufreq_put_cpu_data(policy);
+}
+
static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
@@ -828,17 +1002,57 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
.name = "cppc_cpufreq",
};

+static struct cpufreq_driver cppc_cpufreq_epp_driver = {
+ .flags = CPUFREQ_CONST_LOOPS,
+ .verify = cppc_verify_policy,
+ .setpolicy = cppc_cpufreq_epp_set_policy,
+ .get = cppc_cpufreq_get_rate,
+ .init = cppc_cpufreq_epp_cpu_init,
+ .exit = cppc_cpufreq_epp_cpu_exit,
+ .attr = cppc_cpufreq_attr,
+ .name = "cppc_cpufreq_epp",
+};
+
+static int cppc_cpufreq_auto_sel_enable(bool auto_sel_mode)
+{
+ struct cppc_cpudata *cpu_data;
+ int cpu, pref, ret = 0;
+
+ if (auto_sel_mode) {
+ for_each_present_cpu(cpu) {
+ /* Enable autonomous mode */
+ ret = cppc_set_auto_sel(cpu, true);
+ if (ret)
+ pr_debug("failed to enable autonomous selection (%d)\n", ret);
+ }
+ }
+
+ return ret;
+}
+
static int __init cppc_cpufreq_init(void)
{
+ struct cppc_perf_caps caps;
int ret;

if (!acpi_cpc_valid())
return -ENODEV;

+ cppc_get_auto_sel_caps(0, &caps);
+ if (auto_sel_mode || caps.auto_sel) {
+ ret = cppc_cpufreq_auto_sel_enable(true);
+ if (ret)
+ return ret;
+
+ current_cppc_cpufreq_driver = &cppc_cpufreq_epp_driver;
+ } else {
+ current_cppc_cpufreq_driver = &cppc_cpufreq_driver;
+ }
+
cppc_freq_invariance_init();
populate_efficiency_class();

- ret = cpufreq_register_driver(&cppc_cpufreq_driver);
+ ret = cpufreq_register_driver(current_cppc_cpufreq_driver);
if (ret)
cppc_freq_invariance_exit();

@@ -859,12 +1073,24 @@ static inline void free_cpu_data(void)

static void __exit cppc_cpufreq_exit(void)
{
- cpufreq_unregister_driver(&cppc_cpufreq_driver);
+ cpufreq_unregister_driver(current_cppc_cpufreq_driver);
cppc_freq_invariance_exit();

free_cpu_data();
}

+static int __init cppc_auto_sel_setup(char *buf)
+{
+ int ret = 0;
+
+ ret = kstrtobool(buf, &auto_sel_mode);
+ if (!ret)
+ pr_err("Wrong autonomous selection param\n");
+
+ return ret;
+}
+__setup("cppc_auto_sel=", cppc_auto_sel_setup);
+
module_exit(cppc_cpufreq_exit);
MODULE_AUTHOR("Ashwin Chaugule");
MODULE_DESCRIPTION("CPUFreq driver based on the ACPI CPPC v5.0+ spec");
--
2.25.1