From 80652b0de2570a89e6ce7443a51e46e3c10bcd14 Mon Sep 17 00:00:00 2001 From: Varadarajan Narayanan Date: Wed, 1 Jul 2026 14:16:25 +0530 Subject: [PATCH 01/76] cpufreq: qcom-nvmem: Add IPQ5210 support IPQ5210 SoCs expose CPU frequency limits through an eFuse speed bin, and the valid CPU OPPs depend on the SoC variant. Add IPQ5210 support to the Qualcomm NVMEM cpufreq driver so the supported OPPs can be selected at runtime using the eFuse value and the opp- supported-hw OPP property. Also block the generic cpufreq-dt platform device for IPQ5210 so the NVMEM-based driver is used. Signed-off-by: Varadarajan Narayanan Reviewed-by: Konrad Dybcio [ Viresh: Converted `!=` to `==` ] Signed-off-by: Viresh Kumar --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + drivers/cpufreq/qcom-cpufreq-nvmem.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index ff1204c666b1..284eece9e230 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -200,6 +200,7 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "ti,am62l3", }, { .compatible = "ti,am62p5", }, + { .compatible = "qcom,ipq5210", }, { .compatible = "qcom,ipq5332", }, { .compatible = "qcom,ipq5424", }, { .compatible = "qcom,ipq6018", }, diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c index e6d28d162442..efa766e98d86 100644 --- a/drivers/cpufreq/qcom-cpufreq-nvmem.c +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c @@ -200,6 +200,13 @@ static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, case QCOM_ID_IPQ9574: drv->versions = 1 << (unsigned int)(*speedbin); break; + case QCOM_ID_IPQ5200: + case QCOM_ID_IPQ5210: + case QCOM_ID_QCF2200: + case QCOM_ID_QCF3200: + case QCOM_ID_QCF3210: + drv->versions = (*speedbin == 0xcd) ? BIT(1) : BIT(0); + break; case QCOM_ID_IPQ5424: case QCOM_ID_IPQ5404: drv->versions = (*speedbin == 0x3b) ? BIT(1) : BIT(0); @@ -618,6 +625,7 @@ static const struct of_device_id qcom_cpufreq_match_list[] __initconst __maybe_u { .compatible = "qcom,msm8909", .data = &match_data_msm8909 }, { .compatible = "qcom,msm8996", .data = &match_data_kryo }, { .compatible = "qcom,qcs404", .data = &match_data_qcs404 }, + { .compatible = "qcom,ipq5210", .data = &match_data_kryo }, { .compatible = "qcom,ipq5332", .data = &match_data_kryo }, { .compatible = "qcom,ipq5424", .data = &match_data_kryo }, { .compatible = "qcom,ipq6018", .data = &match_data_ipq6018 }, From d87cb889dc7ab1f2deecadf2a5e9023184bd7900 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Fri, 3 Jul 2026 14:20:49 +0800 Subject: [PATCH 02/76] cpufreq: apple-soc: Fix OPP table cleanup apple_soc_cpufreq_init() adds OPP tables from firmware, but some failure paths do not remove them. The driver also uses dev_pm_opp_remove_all_dynamic(), which is not the right cleanup helper for OPP tables loaded from firmware. Use the cpumask OPP helper after the policy CPU mask has been populated. Pair it with the matching cpumask remove helper on failure paths and in apple_soc_cpufreq_exit(). This also removes the separate dev_pm_opp_set_sharing_cpus() call, as the cpumask helper loads the DT OPP tables for all CPUs in the policy. Fixes: 6286bbb40576 ("cpufreq: apple-soc: Add new driver to control Apple SoC CPU P-states") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Viresh Kumar --- drivers/cpufreq/apple-soc-cpufreq.c | 36 +++++++++++------------------ 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c index 638e5bf72185..3f64f266e695 100644 --- a/drivers/cpufreq/apple-soc-cpufreq.c +++ b/drivers/cpufreq/apple-soc-cpufreq.c @@ -249,21 +249,19 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) return -ENODEV; } - ret = dev_pm_opp_of_add_table(cpu_dev); - if (ret < 0) { - dev_err(cpu_dev, "%s: failed to add OPP table: %d\n", __func__, ret); - return ret; - } + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; ret = apple_soc_cpufreq_find_cluster(policy, ®_base, &info); if (ret) { dev_err(cpu_dev, "%s: failed to get cluster info: %d\n", __func__, ret); - return ret; + goto out_free_priv; } - ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); - if (ret) { - dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", __func__, ret); + ret = dev_pm_opp_of_cpumask_add_table(policy->cpus); + if (ret < 0) { + dev_err(cpu_dev, "%s: failed to add OPP table: %d\n", __func__, ret); goto out_iounmap; } @@ -271,19 +269,13 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) if (ret <= 0) { dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n"); ret = -EPROBE_DEFER; - goto out_free_opp; - } - - priv = kzalloc_obj(*priv); - if (!priv) { - ret = -ENOMEM; - goto out_free_opp; + goto out_free_table; } ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); if (ret) { dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); - goto out_free_priv; + goto out_free_table; } /* Get OPP levels (p-state indexes) and stash them in driver_data */ @@ -318,12 +310,12 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) out_free_cpufreq_table: dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); -out_free_priv: - kfree(priv); -out_free_opp: - dev_pm_opp_remove_all_dynamic(cpu_dev); +out_free_table: + dev_pm_opp_of_cpumask_remove_table(policy->cpus); out_iounmap: iounmap(reg_base); +out_free_priv: + kfree(priv); return ret; } @@ -332,7 +324,7 @@ static void apple_soc_cpufreq_exit(struct cpufreq_policy *policy) struct apple_cpu_priv *priv = policy->driver_data; dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); - dev_pm_opp_remove_all_dynamic(priv->cpu_dev); + dev_pm_opp_of_cpumask_remove_table(policy->cpus); iounmap(priv->reg_base); kfree(priv); } From f456178290356167c7349d51432889113565d1ba Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Sun, 5 Jul 2026 20:08:15 -0700 Subject: [PATCH 03/76] rust: rcpufreq_dt: use vertical import style Convert `use` imports to vertical layout for better readability and maintainability. Signed-off-by: Guru Das Srinagesh Signed-off-by: Viresh Kumar --- drivers/cpufreq/rcpufreq_dt.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs index 10106fa13095..5d27857261da 100644 --- a/drivers/cpufreq/rcpufreq_dt.rs +++ b/drivers/cpufreq/rcpufreq_dt.rs @@ -4,12 +4,19 @@ use kernel::{ clk::Clk, - cpu, cpufreq, + cpu, + cpufreq, // cpumask::CpumaskVar, - device::{Core, Device}, + device::{ + Core, + Device, // + }, error::code::*, macros::vtable, - module_platform_driver, of, opp, platform, + module_platform_driver, + of, + opp, + platform, // prelude::*, str::CString, sync::Arc, From b2067d4d54bc7caf8c8b68a0de2aa61a5bd7f3d5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 17 Jun 2026 19:02:35 +0200 Subject: [PATCH 04/76] cpufreq: intel_pstate: Rearrange checks in hybrid_get_cost() Make the checks in hybrid_get_cost() more straightforward. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2832945.mvXUDI8C0e@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 5a0eeb84d382..fd524d0267a3 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -979,12 +979,10 @@ static int hybrid_get_cost(struct device *dev, unsigned long freq, * capacity. Similarly, P-cores start to be populated when E-cores are * utilized above 60% of the capacity. */ - if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_ATOM) { - if (hybrid_has_l3(dev->id)) /* E-core */ - *cost += 1; - } else { /* P-core */ + if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_CORE) /* P-core */ *cost += 2; - } + else if (hybrid_has_l3(dev->id)) /* E-core */ + *cost += 1; return 0; } From 6058646587dded0ce0ba91bd5a6afbf14fe42055 Mon Sep 17 00:00:00 2001 From: Haowen Tu Date: Wed, 24 Jun 2026 13:38:39 +0800 Subject: [PATCH 05/76] PM: sleep: Fix off-by-one in wakelocks number limit check CONFIG_PM_WAKELOCKS_LIMIT is documented as the maximum number of user-space wakeup sources, but the limit check is performed before the counter is incremented and only rejects new wakeup sources when the current number is greater than the limit. This allows one extra wakeup source to be created. Reject new wakeup sources once the counter has reached the limit. Fixes: b86ff9820fd5 ("PM / Sleep: Add user space interface for manipulating wakeup sources, v3") Signed-off-by: Haowen Tu [ rjw: Subject edits ] Link: https://patch.msgid.link/20260624053839.2150567-1-tuhaowen@uniontech.com Signed-off-by: Rafael J. Wysocki --- kernel/power/wakelock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c index fd763da06a87..a8b6bd5ec46b 100644 --- a/kernel/power/wakelock.c +++ b/kernel/power/wakelock.c @@ -63,7 +63,7 @@ static unsigned int number_of_wakelocks; static inline bool wakelocks_limit_exceeded(void) { - return number_of_wakelocks > CONFIG_PM_WAKELOCKS_LIMIT; + return number_of_wakelocks >= CONFIG_PM_WAKELOCKS_LIMIT; } static inline void increment_wakelocks_number(void) From eef1d74bf88f120f4f63a83a5201fcda67bf3b52 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 6 Jul 2026 14:23:09 +0530 Subject: [PATCH 06/76] PM: runtime: Only set runtime_error on suspend callback failures When a runtime resume callback returns an error, rpm_callback() sets power.runtime_error on the device. This causes all subsequent calls to rpm_resume() to return -EINVAL immediately at the top of the function without invoking the callback again, making the failure permanent until runtime PM is explicitly re-initialized. Unlike suspend failures, resume failures should be retryable. If a device's resume callback fails, there is no reason to permanently block future resume attempts on that device and all of its consumers. Fix this by moving the power.runtime_error assignment out of the generic rpm_callback() and into rpm_suspend() at its fail label, where suspend callback failures are handled. Resume callback failures now return the error to the caller but leave power.runtime_error clear, allowing the next resume attempt to invoke the callback normally. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260706-fix_sticky_-einval_after_pm_runtime_api_failure-v3-1-92feb5a7b926@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 335288e8b5b3..fab38bc98113 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -469,9 +469,6 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) if (retval == -EACCES) retval = -EAGAIN; - if (retval != -EAGAIN && retval != -EBUSY) - dev->power.runtime_error = retval; - return retval; } @@ -751,6 +748,9 @@ static int rpm_suspend(struct device *dev, int rpmflags) dev->power.deferred_resume = false; wake_up_all(&dev->power.wait_queue); + if (retval != -EAGAIN && retval != -EBUSY) + dev->power.runtime_error = retval; + /* * On transient errors, if the callback routine failed an autosuspend, * and if the last_busy time has been updated so that there is a new From 5a83170c8795056a401d7bfb9529af22a53fd9a4 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 13 Jul 2026 21:11:31 +0800 Subject: [PATCH 07/76] cpufreq: brcmstb-avs: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Reviewed-by: Zhongqiu Han [ Viresh: Fixed Subject ] Signed-off-by: Viresh Kumar --- drivers/cpufreq/brcmstb-avs-cpufreq.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/cpufreq/brcmstb-avs-cpufreq.c b/drivers/cpufreq/brcmstb-avs-cpufreq.c index 71450cca8e9f..7d902856ac01 100644 --- a/drivers/cpufreq/brcmstb-avs-cpufreq.c +++ b/drivers/cpufreq/brcmstb-avs-cpufreq.c @@ -584,11 +584,8 @@ static int brcm_avs_prepare_init(struct platform_device *pdev) ret = devm_request_irq(dev, priv->host_irq, irq_handler, IRQF_TRIGGER_RISING, BRCM_AVS_HOST_INTR, priv); - if (ret && priv->host_irq >= 0) { - dev_err(dev, "IRQ request failed: %s (%d) -- %d\n", - BRCM_AVS_HOST_INTR, priv->host_irq, ret); + if (ret && priv->host_irq >= 0) goto unmap_intr_base; - } if (brcm_avs_is_firmware_loaded(priv)) return 0; From 6a9e0e0f7592313ace66303cf5eca68e04c10f30 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 14 Jul 2026 18:46:22 +0300 Subject: [PATCH 08/76] cpufreq: spear: Fix an IS_ERR() vs NULL bug in spear1340_set_cpu_rate() The clk_get_parent() function doesn't return error pointers, it returns NULL on error. Update the error checking to match. Fixes: 420993221175 ("cpufreq: SPEAr: Add CPUFreq driver") Signed-off-by: Dan Carpenter Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/spear-cpufreq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/spear-cpufreq.c b/drivers/cpufreq/spear-cpufreq.c index 81a0780b2ebf..ffe5eda82f0b 100644 --- a/drivers/cpufreq/spear-cpufreq.c +++ b/drivers/cpufreq/spear-cpufreq.c @@ -79,9 +79,9 @@ static int spear1340_set_cpu_rate(struct clk *sys_pclk, unsigned long newfreq) int ret = 0; sys_clk = clk_get_parent(spear_cpufreq.clk); - if (IS_ERR(sys_clk)) { + if (!sys_clk) { pr_err("failed to get cpu's parent (sys) clock\n"); - return PTR_ERR(sys_clk); + return -EINVAL; } /* Set the rate of the source clock before changing the parent */ From ce46fede7792cedd247e34b48bc8a02eb90c7848 Mon Sep 17 00:00:00 2001 From: Gregor Herburger Date: Wed, 15 Jul 2026 09:35:36 +0200 Subject: [PATCH 09/76] OPP: Fix cleanup ordering Commit 173e02d67494 ("OPP: Initialize scope-based pointers inline") added initialization for all pointers. In some cases, the ordering was changed so that *opp_table was initialized after *opp. This also changes the order of the registered cleanup functions. When the cleanup happens, this can cause use-after-free errors when the last reference is released and the release function _opp_kref_release tries to access the already freed opp->opp_table. Initialize *opp_table before *opp again to fix this and ensure the correct cleanup order. Fixes: 173e02d67494 ("OPP: Initialize scope-based pointers inline") Signed-off-by: Gregor Herburger Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index ab0b0a2f85a1..b6966e509f7d 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1412,13 +1412,12 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, */ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) { + struct opp_table *opp_table __free(put_opp_table) = + _find_opp_table(dev); struct dev_pm_opp *opp __free(put_opp) = NULL; unsigned long freq = 0, temp_freq; bool forced = false; - struct opp_table *opp_table __free(put_opp_table) = - _find_opp_table(dev); - if (IS_ERR(opp_table)) { dev_err(dev, "%s: device's opp table doesn't exist\n", __func__); return PTR_ERR(opp_table); @@ -2870,11 +2869,10 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_add_dynamic); static int _opp_set_availability(struct device *dev, unsigned long freq, bool availability_req) { - struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; - /* Find the opp_table */ struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); + struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; if (IS_ERR(opp_table)) { dev_warn(dev, "%s: Device OPP not found (%ld)\n", __func__, @@ -2932,12 +2930,11 @@ int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq, unsigned long u_volt_max) { - struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; - int r; - /* Find the opp_table */ struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); + struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; + int r; if (IS_ERR(opp_table)) { r = PTR_ERR(opp_table); From 222e951b8692f2422b8cb7fc096cf45acb8db461 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 19 Jun 2026 16:52:08 +0200 Subject: [PATCH 10/76] cpufreq: intel_pstate: Adjust the .adjust_perf() driver callback In some cases, the processor may not actually stick to the "desired" performance level programmed through the driver's .adjust_perf() callback and may go above it, which may not be desirable (for instance, there may be a UCLAMP_MAX limit set for the task currently running on the given CPU which should be respected). Address that by adjusting the .adjust_perf() callback to take an additional argument, max_perf, representing the maximum allowed performance level of the CPU and update the intel_pstate driver to take that argument into account as appropriate. Accordingly, adjust cpufreq_driver_adjust_perf() and the other existing user of .adjust_perf(), which is the amd-pstate driver (but the behavior of that driver is not changed). While at it, also update the cpufreq_driver_adjust_perf() documentation to reflect this change and some previous code changes that have not been taken into account in it. Signed-off-by: Rafael J. Wysocki Acked-by: Viresh Kumar Reviewed-by: Zhongqiu Han Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/6277654.lOV4Wx5bFT@rafael.j.wysocki [ rjw: Adjusted Rust function formatting ] Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/amd-pstate.c | 1 + drivers/cpufreq/cpufreq.c | 14 +++++++++----- drivers/cpufreq/intel_pstate.c | 9 ++++++++- include/linux/cpufreq.h | 2 ++ kernel/sched/cpufreq_schedutil.c | 4 +++- rust/kernel/cpufreq.rs | 11 +++++++++-- 6 files changed, 32 insertions(+), 9 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index a74a4cf99d22..f8d8288d644f 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -782,6 +782,7 @@ static unsigned int amd_pstate_fast_switch(struct cpufreq_policy *policy, static void amd_pstate_adjust_perf(struct cpufreq_policy *policy, unsigned long _min_perf, unsigned long target_perf, + unsigned long _max_perf, unsigned long capacity) { u8 max_perf, min_perf, des_perf, cap_perf; diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 507224c9ecd3..f07b73694cec 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2225,14 +2225,17 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); * @policy: cpufreq policy object of the target CPU. * @min_perf: Minimum (required) performance level (units of @capacity). * @target_perf: Target (desired) performance level (units of @capacity). + * @max_perf: Maximum (allowed) performance level (units of @capacity). * @capacity: Capacity of the target CPU. * - * Carry out a fast performance level switch of @cpu without sleeping. + * Carry out a fast performance level adjustment for the CPU represented by + * @policy without sleeping. * * The driver's ->adjust_perf() callback invoked by this function must be - * suitable for being called from within RCU-sched read-side critical sections - * and it is expected to select a suitable performance level equal to or above - * @min_perf and preferably equal to or below @target_perf. + * suitable for calling from within RCU-sched read-side critical sections and + * it is expected to program the processor to select suitable performance + * levels between @min_perf and @max_perf inclusive and preferably close to + * @target_perf going forward for the CPU represented by @policy. * * This function must not be called if policy->fast_switch_enabled is unset. * @@ -2244,9 +2247,10 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, unsigned long min_perf, unsigned long target_perf, + unsigned long max_perf, unsigned long capacity) { - cpufreq_driver->adjust_perf(policy, min_perf, target_perf, capacity); + cpufreq_driver->adjust_perf(policy, min_perf, target_perf, max_perf, capacity); } /** diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index fd524d0267a3..fe3e6accd9a5 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -3239,6 +3239,7 @@ static unsigned int intel_cpufreq_fast_switch(struct cpufreq_policy *policy, static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, unsigned long min_perf, unsigned long target_perf, + unsigned long max_perf, unsigned long capacity) { struct cpudata *cpu = all_cpu_data[policy->cpu]; @@ -3269,7 +3270,13 @@ static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, if (min_pstate > cpu->max_perf_ratio) min_pstate = cpu->max_perf_ratio; - max_pstate = min(cap_pstate, cpu->max_perf_ratio); + max_pstate = cap_pstate; + if (max_perf < capacity) + max_pstate = DIV_ROUND_UP(cap_pstate * max_perf, capacity); + + if (max_pstate > cpu->max_perf_ratio) + max_pstate = cpu->max_perf_ratio; + if (max_pstate < min_pstate) max_pstate = min_pstate; diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index ae9d1ce4f49c..35ce665edfd8 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -379,6 +379,7 @@ struct cpufreq_driver { void (*adjust_perf)(struct cpufreq_policy *policy, unsigned long min_perf, unsigned long target_perf, + unsigned long max_perf, unsigned long capacity); /* @@ -624,6 +625,7 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, unsigned long min_perf, unsigned long target_perf, + unsigned long max_perf, unsigned long capacity); bool cpufreq_driver_has_adjust_perf(void); int cpufreq_driver_target(struct cpufreq_policy *policy, diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c index a4e689eefdfb..7271cf24879d 100644 --- a/kernel/sched/cpufreq_schedutil.c +++ b/kernel/sched/cpufreq_schedutil.c @@ -50,6 +50,7 @@ struct sugov_cpu { unsigned long util; unsigned long bw_min; + unsigned long bw_max; /* The field below is for single-CPU policies only: */ #ifdef CONFIG_NO_HZ_COMMON @@ -232,6 +233,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) util = effective_cpu_util(sg_cpu->cpu, util, &min, &max); util = max(util, boost); sg_cpu->bw_min = min; + sg_cpu->bw_max = max; sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); } @@ -484,7 +486,7 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time, sg_cpu->util = prev_util; cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min, - sg_cpu->util, max_cap); + sg_cpu->util, sg_cpu->bw_max, max_cap); sg_policy->need_freq_update = false; sg_policy->last_freq_update_time = time; diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index 58ac04c650a1..d624cccb6540 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -792,7 +792,13 @@ fn fast_switch(_policy: &mut Policy, _target_freq: u32) -> u32 { } /// Driver's `adjust_perf` callback. - fn adjust_perf(_policy: &mut Policy, _min_perf: usize, _target_perf: usize, _capacity: usize) { + fn adjust_perf( + _policy: &mut Policy, + _min_perf: usize, + _target_perf: usize, + _max_perf: usize, + _capacity: usize, + ) { build_error!(VTABLE_DEFAULT_ERROR) } @@ -1263,12 +1269,13 @@ impl Registration { ptr: *mut bindings::cpufreq_policy, min_perf: c_ulong, target_perf: c_ulong, + max_perf: c_ulong, capacity: c_ulong, ) { // SAFETY: The `ptr` is guaranteed to be valid by the contract with the C code for the // lifetime of `policy`. let policy = unsafe { Policy::from_raw_mut(ptr) }; - T::adjust_perf(policy, min_perf, target_perf, capacity); + T::adjust_perf(policy, min_perf, target_perf, max_perf, capacity); } /// Driver's `get_intermediate` callback. From 76d70f65e3bfa9f7aa7bd1981fec850f879bc077 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 23 Jun 2026 19:34:40 +0200 Subject: [PATCH 11/76] cpufreq: intel_pstate: Simplify HWP handling on Broadwell After commit de5bcf404ace ("cpufreq: intel_pstate: Clean up frequency computations"), the Broadwell HWP mode does not actually do anything useful because intel_pstate_get_hwp_cap() is called in intel_pstate_verify_cpu_policy() without checking the Broadwell mode and it overrides the initial turbo and max pstate values read from MSR_PLATFORM_INFO (the minimum P-state value still comes from MSR_PLATFORM_INFO if HWP is used even without the Broadwell mode). Moreover, hwp_cap_cached is used in some places for updating MSR_HWP_REQUEST without checking the Broadwell mode either. Effectively, the only difference made by the Broadwell HWP mode is skipping the hybrid initialization which may as well be achieved by avoiding to set pstate_funcs.get_cpu_scaling on Broadwell. Link: https://sashiko.dev/#/patchset/6005456.DvuYhMxLoT%40rafael.j.wysocki Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3057514.e9J7NaK4W3@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index fe3e6accd9a5..db3466aeb34e 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -299,7 +299,6 @@ struct pstate_funcs { static struct pstate_funcs pstate_funcs __read_mostly; static bool hwp_active __ro_after_init; -static int hwp_mode_bdw __ro_after_init; static bool per_cpu_limits __ro_after_init; static bool hwp_forced __ro_after_init; static bool hwp_boost __read_mostly; @@ -2321,7 +2320,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; - if (hwp_active && !hwp_mode_bdw) { + if (hwp_active) { __intel_pstate_get_hwp_cap(cpu); if (pstate_funcs.get_cpu_scaling) { @@ -3811,7 +3810,6 @@ static int __init intel_pstate_init(void) if (!no_hwp) { hwp_active = true; - hwp_mode_bdw = id->driver_data; intel_pstate.attr = hwp_cpufreq_attrs; intel_cpufreq.attr = hwp_cpufreq_attrs; intel_cpufreq.flags |= CPUFREQ_NEED_UPDATE_LIMITS; @@ -3819,7 +3817,8 @@ static int __init intel_pstate_init(void) if (!default_driver) default_driver = &intel_pstate; - pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; + if (!id->driver_data) + pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; goto hwp_cpu_matched; } From 12a7f1aa0641ed3fcdc702d7021bdee78dd55a5d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 23 Jun 2026 19:34:47 +0200 Subject: [PATCH 12/76] cpufreq: intel_pstate: Rename INTEL_PSTATE_HWP_BROADWELL Since the only role of INTEL_PSTATE_HWP_BROADWELL is to indicate that hybrid HWP should not be used, rename it to INTEL_PSTATE_HWP_NOT_HYBRID. No functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3431755.44csPzL39Z@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index db3466aeb34e..9220411606d7 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -3678,14 +3678,14 @@ static inline bool intel_pstate_has_acpi_ppc(void) { return false; } static inline void intel_pstate_request_control_from_smm(void) {} #endif /* CONFIG_ACPI */ -#define INTEL_PSTATE_HWP_BROADWELL 0x01 +#define INTEL_PSTATE_HWP_NOT_HYBRID 0x01 #define X86_MATCH_HWP(vfm, hwp_mode) \ X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_HWP, hwp_mode) static const struct x86_cpu_id hwp_support_ids[] __initconst = { - X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_BROADWELL), - X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_BROADWELL), + X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_NOT_HYBRID), + X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_NOT_HYBRID), X86_MATCH_HWP(INTEL_ANY, 0), {} }; From db53c573d31d07d5d782c5312d37cb33be788eba Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 24 Jun 2026 19:33:08 +0200 Subject: [PATCH 13/76] cpufreq: intel_pstate: Fix setting minimum P-state at init time If HWP is enabled, writes to MSR_IA32_PERF_CTL have no effect, so intel_pstate_get_cpu_pstates() should not attempt to call intel_pstate_set_min_pstate() to set the minimum P-state for the given CPU in that case. Accordingly, remove the intel_pstate_set_min_pstate() call from intel_pstate_get_cpu_pstates() and make both intel_pstate_cpu_init() and intel_cpufreq_cpu_init() call that function in their non-HWP code paths. The HWP code path in intel_pstate_cpu_init() does not need to update the current P-state of the CPU directly at all because it is taken care of the processor automatically, but the HWP code path of intel_cpufreq_cpu_init() should update it in principle to initialize the DESIRED_PERF field in MSR_HWP_REQUEST. For this purpose, make it call intel_cpufreq_hwp_update() and pass the minimum P-state limit to it as the current target value along with the current minimum and maximum limits. Fixes: f6ebbcf08f37 ("cpufreq: intel_pstate: Implement passive mode with HWP enabled") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/5090465.GXAFRqVoOG@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 9220411606d7..b243ff9ebe69 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -2351,8 +2351,6 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) if (pstate_funcs.get_vid) pstate_funcs.get_vid(cpu); - - intel_pstate_set_min_pstate(cpu); } /* @@ -3058,6 +3056,7 @@ static int __intel_pstate_cpu_init(struct cpufreq_policy *policy) static int intel_pstate_cpu_init(struct cpufreq_policy *policy) { int ret = __intel_pstate_cpu_init(policy); + struct cpudata *cpu; if (ret) return ret; @@ -3068,11 +3067,11 @@ static int intel_pstate_cpu_init(struct cpufreq_policy *policy) */ policy->policy = CPUFREQ_POLICY_POWERSAVE; - if (hwp_active) { - struct cpudata *cpu = all_cpu_data[policy->cpu]; - + cpu = all_cpu_data[policy->cpu]; + if (hwp_active) cpu->epp_cached = intel_pstate_get_epp(cpu, 0); - } + else + intel_pstate_set_min_pstate(cpu); return 0; } @@ -3303,8 +3302,6 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) return ret; policy->cpuinfo.transition_latency = INTEL_CPUFREQ_TRANSITION_LATENCY; - /* This reflects the intel_pstate_get_cpu_pstates() setting. */ - policy->cur = policy->cpuinfo.min_freq; req = kzalloc_objs(*req, 2); if (!req) { @@ -3325,9 +3322,15 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) WRITE_ONCE(cpu->hwp_req_cached, value); cpu->epp_cached = intel_pstate_get_epp(cpu, value); + + intel_cpufreq_hwp_update(cpu, cpu->pstate.min_pstate, + cpu->pstate.max_pstate, + cpu->pstate.min_pstate, false); } else { policy->transition_delay_us = INTEL_CPUFREQ_TRANSITION_DELAY; + intel_pstate_set_min_pstate(cpu); } + policy->cur = policy->cpuinfo.min_freq; freq = DIV_ROUND_UP(cpu->pstate.turbo_freq * global.min_perf_pct, 100); From 773e4847876bc78fad9b2747b3d8d40c62e9d677 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 24 Jun 2026 19:34:10 +0200 Subject: [PATCH 14/76] cpufreq: intel_pstate: Introduce intel_pstate_update_freq_limits() Introduce a new helper function, intel_pstate_update_freq_limits(), for updating the max and turbo frequency values for the given CPU after updating the corresponding P-states. Use it in intel_pstate_get_hwp_cap() and intel_pstate_get_cpu_pstates(), in the latter case instead of the direct updates of the max and turbo frequency values in intel_pstate_hybrid_hwp_adjust(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2277328.irdbgypaU6@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index b243ff9ebe69..a7bb7be59848 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -586,11 +586,6 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) hwp_is_hybrid = true; - cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_pstate * scaling, - perf_ctl_scaling); - cpu->pstate.max_freq = rounddown(cpu->pstate.max_pstate * scaling, - perf_ctl_scaling); - freq = perf_ctl_max_phys * perf_ctl_scaling; cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); @@ -1180,6 +1175,22 @@ static bool hybrid_clear_max_perf_cpu(void) return ret; } +static void intel_pstate_update_freq_limits(struct cpudata *cpu) +{ + int scaling = cpu->pstate.scaling; + unsigned int turbo_freq = cpu->pstate.turbo_pstate * scaling; + unsigned int max_freq = cpu->pstate.max_pstate * scaling; + int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; + + if (scaling != perf_ctl_scaling) { + turbo_freq = rounddown(turbo_freq, perf_ctl_scaling); + max_freq = rounddown(max_freq, perf_ctl_scaling); + } + + cpu->pstate.turbo_freq = turbo_freq; + cpu->pstate.max_freq = max_freq; +} + static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) { u64 cap; @@ -1192,20 +1203,8 @@ static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) static void intel_pstate_get_hwp_cap(struct cpudata *cpu) { - int scaling = cpu->pstate.scaling; - __intel_pstate_get_hwp_cap(cpu); - - cpu->pstate.max_freq = cpu->pstate.max_pstate * scaling; - cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * scaling; - if (scaling != cpu->pstate.perf_ctl_scaling) { - int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; - - cpu->pstate.max_freq = rounddown(cpu->pstate.max_freq, - perf_ctl_scaling); - cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_freq, - perf_ctl_scaling); - } + intel_pstate_update_freq_limits(cpu); } static void hybrid_update_capacity(struct cpudata *cpu) @@ -2326,6 +2325,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) if (pstate_funcs.get_cpu_scaling) { cpu->pstate.scaling = pstate_funcs.get_cpu_scaling(cpu->cpu); intel_pstate_hybrid_hwp_adjust(cpu); + intel_pstate_update_freq_limits(cpu); } else { cpu->pstate.scaling = perf_ctl_scaling; } From 39b28ab6e91cd09270f8f8d7858a6a1508053804 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 24 Jun 2026 19:35:23 +0200 Subject: [PATCH 15/76] cpufreq: intel_pstate: Consolidate frequency values computation Update intel_pstate_get_cpu_pstates() to use intel_pstate_update_freq_limits() for computing the max and turbo frequency values in all cases, including non-hybrid HWP and HWP disabled. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3059329.e9J7NaK4W3@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index a7bb7be59848..7f7a5ccc930c 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -589,13 +589,11 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) freq = perf_ctl_max_phys * perf_ctl_scaling; cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); - freq = cpu->pstate.min_pstate * perf_ctl_scaling; - cpu->pstate.min_freq = freq; /* * Cast the min P-state value retrieved via pstate_funcs.get_min() to * the effective range of HWP performance levels. */ - cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); + cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); } static bool turbo_is_disabled(void) @@ -2317,6 +2315,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); + cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; if (hwp_active) { @@ -2325,7 +2324,6 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) if (pstate_funcs.get_cpu_scaling) { cpu->pstate.scaling = pstate_funcs.get_cpu_scaling(cpu->cpu); intel_pstate_hybrid_hwp_adjust(cpu); - intel_pstate_update_freq_limits(cpu); } else { cpu->pstate.scaling = perf_ctl_scaling; } @@ -2340,11 +2338,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); } - if (cpu->pstate.scaling == perf_ctl_scaling) { - cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; - cpu->pstate.max_freq = cpu->pstate.max_pstate * perf_ctl_scaling; - cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * perf_ctl_scaling; - } + intel_pstate_update_freq_limits(cpu); if (pstate_funcs.get_aperf_mperf_shift) cpu->aperf_mperf_shift = pstate_funcs.get_aperf_mperf_shift(); From 3ff72cac7321053c1fcb76301c55a802a8a6581b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 24 Jun 2026 19:36:05 +0200 Subject: [PATCH 16/76] cpufreq: intel_pstate: Move two functions closer to callers Move intel_pstate_set_pstate() and intel_pstate_set_min_pstate() closer to their first callers. No intentional functional impact. Signed-off-by: Rafael J. Wysocki [ rjw: Changelog adjustment ] Link: https://patch.msgid.link/3433588.44csPzL39Z@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/intel_pstate.c | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 7f7a5ccc930c..2f048929ff6b 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -2291,24 +2291,6 @@ static int hwp_get_cpu_scaling(int cpu) return intel_pstate_cppc_get_scaling(cpu); } -static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) -{ - trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); - cpu->pstate.current_pstate = pstate; - /* - * Generally, there is no guarantee that this code will always run on - * the CPU being updated, so force the register update to run on the - * right CPU. - */ - wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, - pstate_funcs.get_val(cpu, pstate)); -} - -static void intel_pstate_set_min_pstate(struct cpudata *cpu) -{ - intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); -} - static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) { int perf_ctl_scaling = pstate_funcs.get_scaling(); @@ -2871,6 +2853,19 @@ static void intel_pstate_update_perf_limits(struct cpudata *cpu, cpu->min_perf_ratio); } +static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) +{ + trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); + cpu->pstate.current_pstate = pstate; + /* + * Generally, there is no guarantee that this code will always run on + * the CPU being updated, so force the register update to run on the + * right CPU. + */ + wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, + pstate_funcs.get_val(cpu, pstate)); +} + static int intel_pstate_set_policy(struct cpufreq_policy *policy) { struct cpudata *cpu; @@ -2958,6 +2953,11 @@ static int intel_pstate_verify_policy(struct cpufreq_policy_data *policy) return 0; } +static void intel_pstate_set_min_pstate(struct cpudata *cpu) +{ + intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); +} + static int intel_cpufreq_cpu_offline(struct cpufreq_policy *policy) { struct cpudata *cpu = all_cpu_data[policy->cpu]; From dd242ab6a557900fd45fc43c753dac2e032d5429 Mon Sep 17 00:00:00 2001 From: wangxiaodong Date: Sun, 21 Jun 2026 10:25:03 +0800 Subject: [PATCH 17/76] Documentation: admin-guide: cpufreq: fix sampling_rate example command The example shell command for setting ondemand's sampling_rate wraps an arithmetic expansion $((...)) in command-substitution backticks. The arithmetic result is then executed as a command, which fails and writes an empty value. Drop the surrounding backticks so the computed value is passed to echo as intended. Fixes: e54ac586674d ("cpufreq: editing corrections to cpufreq.rst") Signed-off-by: wangxiaodong Reviewed-by: Randy Dunlap Reviewed-by: Zhongqiu Han [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260621022515.10137-1-wangxiaodong827546786@gmail.com Signed-off-by: Rafael J. Wysocki --- Documentation/admin-guide/pm/cpufreq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/pm/cpufreq.rst b/Documentation/admin-guide/pm/cpufreq.rst index 8831cface585..34baf20cc202 100644 --- a/Documentation/admin-guide/pm/cpufreq.rst +++ b/Documentation/admin-guide/pm/cpufreq.rst @@ -497,7 +497,7 @@ This governor exposes the following tunables: represented by it to be 1.5 times as high as the transition latency (the default):: - # echo `$(($(cat cpuinfo_transition_latency) * 3 / 2))` > ondemand/sampling_rate + # echo $(($(cat cpuinfo_transition_latency) * 3 / 2)) > ondemand/sampling_rate ``up_threshold`` If the estimated CPU load is above this value (in percent), the governor From db6a017c91b774c15b1b890db45981eacfff540e Mon Sep 17 00:00:00 2001 From: Zhongqiu Han Date: Fri, 3 Jul 2026 17:24:33 +0800 Subject: [PATCH 18/76] cpufreq: schedutil: Fix self-contradictory comment in sugov_iowait_apply() The kerneldoc of sugov_iowait_apply() says the IO boost value is increased in sugov_iowait_apply() and, in the same sentence, that it is decreased by the same function. That is self-contradictory, and the first part is wrong: sugov_iowait_apply() only decreases the boost. The boost is actually increased in sugov_iowait_boost(). Fix the comment to name sugov_iowait_boost() as the place where the boost is increased, so it matches the code. No functional change. Fixes: fd7d5287fd65 ("cpufreq: schedutil: Cleanup and document iowait boost") Signed-off-by: Zhongqiu Han Reviewed-by: Christian Loehle Link: https://patch.msgid.link/20260703092433.4080165-1-zhongqiu.han@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- kernel/sched/cpufreq_schedutil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c index 7271cf24879d..dc0835ceee98 100644 --- a/kernel/sched/cpufreq_schedutil.c +++ b/kernel/sched/cpufreq_schedutil.c @@ -316,7 +316,7 @@ static void sugov_iowait_boost(struct sugov_cpu *sg_cpu, u64 time, * A CPU running a task which woken up after an IO operation can have its * utilization boosted to speed up the completion of those IO operations. * The IO boost value is increased each time a task wakes up from IO, in - * sugov_iowait_apply(), and it's instead decreased by this function, + * sugov_iowait_boost(), and it's instead decreased by this function, * each time an increase has not been requested (!iowait_boost_pending). * * A CPU which also appears to have been idle for at least one tick has also From 68f34fad760b68e878aced43934cc02b9d1bed89 Mon Sep 17 00:00:00 2001 From: Jeremy Linton Date: Thu, 9 Jul 2026 17:13:42 -0500 Subject: [PATCH 19/76] cpupower: Add generic CPPC performance display Arm64 machines, and possibly others, use the standard ACPI defined CPPC infrastructure. cpupower has CPPC support, but it's largely written around the intricacies of AMD processors, using platform MSRs to avoid shortcomings in the specification. Add a generic CPPC display that depends only on standardized fields. The computed frequency values are best effort and rely on the FW providing optional values that can be used to derive a meaningful frequency at a given unique performance level. Link: https://lore.kernel.org/r/20260709221344.1919794-2-jeremy.linton@arm.com Signed-off-by: Jeremy Linton Signed-off-by: Shuah Khan --- tools/power/cpupower/utils/helpers/cppc.c | 56 +++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tools/power/cpupower/utils/helpers/cppc.c diff --git a/tools/power/cpupower/utils/helpers/cppc.c b/tools/power/cpupower/utils/helpers/cppc.c new file mode 100644 index 000000000000..3493ce8551ea --- /dev/null +++ b/tools/power/cpupower/utils/helpers/cppc.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include + +#include "helpers/helpers.h" +#include "cpufreq.h" +#include "acpi_cppc.h" + +#define cppc_to_frequency(perf) (roundf(slope * (perf) + intercept)) + +void cppc_show_perf_and_freq(unsigned int cpu, int no_rounding) +{ + int64_t nominal = acpi_cppc_get_data(cpu, NOMINAL_PERF); + int64_t nominal_freq = acpi_cppc_get_data(cpu, NOMINAL_FREQ) * 1000; + int64_t lowest = acpi_cppc_get_data(cpu, LOWEST_PERF); + int64_t lowest_freq = acpi_cppc_get_data(cpu, LOWEST_FREQ) * 1000; + unsigned long non_linear = acpi_cppc_get_data(cpu, LOWEST_NONLINEAR_PERF); + unsigned long highest = acpi_cppc_get_data(cpu, HIGHEST_PERF); + float slope, intercept; + + /* do the optional freq fields look invalid? */ + if (!nominal_freq || !lowest_freq || nominal == lowest) + return; + + slope = (float)(nominal_freq - lowest_freq) / (nominal - lowest); + intercept = lowest_freq - slope * lowest; + + printf(_(" CPPC limits:\n")); + printf(_(" Highest Performance: %lu. Maximum Frequency: "), + highest); + /* + * If boost isn't active, the cpuinfo_max doesn't indicate real max + * frequency. + */ + print_speed(cppc_to_frequency(highest), no_rounding); + printf(".\n"); + + printf(_(" Nominal Performance: %lu. Nominal Frequency: "), + acpi_cppc_get_data(cpu, NOMINAL_PERF)); + print_speed(nominal_freq, no_rounding); + printf(".\n"); + + printf(_(" Lowest Non-linear Performance: %lu. Lowest Non-linear Frequency: "), + non_linear); + print_speed(cppc_to_frequency(non_linear), no_rounding); + printf(".\n"); + + printf(_(" Lowest Performance: %lu. Lowest Frequency: "), + acpi_cppc_get_data(cpu, LOWEST_PERF)); + print_speed(lowest_freq, no_rounding); + printf(".\n"); +} From 6b8ff068542a62c0fd58a7134282d48dd8a729c9 Mon Sep 17 00:00:00 2001 From: Jeremy Linton Date: Thu, 9 Jul 2026 17:13:43 -0500 Subject: [PATCH 20/76] cpupower: Build and call CPPC information on non-AMD processors Now that we have a generic CPPC printout, call it on !AMD processors. If it fails to detect CPPC, or the registers don't look reasonable then it will exit without printing anything. Link: https://lore.kernel.org/r/20260709221344.1919794-3-jeremy.linton@arm.com Signed-off-by: Jeremy Linton Signed-off-by: Shuah Khan --- tools/power/cpupower/Makefile | 2 +- tools/power/cpupower/utils/cpufreq-info.c | 3 ++- tools/power/cpupower/utils/helpers/helpers.h | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/power/cpupower/Makefile b/tools/power/cpupower/Makefile index 969716dfe8de..cd8b7315fe74 100644 --- a/tools/power/cpupower/Makefile +++ b/tools/power/cpupower/Makefile @@ -131,7 +131,7 @@ override CFLAGS += -DVERSION=\"$(VERSION)\" -DPACKAGE=\"$(PACKAGE)\" \ UTIL_OBJS = utils/helpers/amd.o utils/helpers/msr.o \ utils/helpers/sysfs.o utils/helpers/misc.o utils/helpers/cpuid.o \ - utils/helpers/pci.o utils/helpers/bitmask.o \ + utils/helpers/pci.o utils/helpers/bitmask.o utils/helpers/cppc.o \ utils/idle_monitor/nhm_idle.o utils/idle_monitor/snb_idle.o \ utils/idle_monitor/hsw_ext_idle.o \ utils/idle_monitor/amd_fam14h_idle.o utils/idle_monitor/cpuidle_sysfs.o \ diff --git a/tools/power/cpupower/utils/cpufreq-info.c b/tools/power/cpupower/utils/cpufreq-info.c index 5a242b491a9d..105f06e690cc 100644 --- a/tools/power/cpupower/utils/cpufreq-info.c +++ b/tools/power/cpupower/utils/cpufreq-info.c @@ -477,12 +477,13 @@ static int get_latency(unsigned int cpu, unsigned int human) } /* --performance / -c */ - static int get_perf_cap(unsigned int cpu) { if (cpupower_cpu_info.vendor == X86_VENDOR_AMD && cpupower_cpu_info.caps & CPUPOWER_CAP_AMD_PSTATE) amd_pstate_show_perf_and_freq(cpu, no_rounding); + else + cppc_show_perf_and_freq(cpu, no_rounding); return 0; } diff --git a/tools/power/cpupower/utils/helpers/helpers.h b/tools/power/cpupower/utils/helpers/helpers.h index a3ad80b9c2c2..9c5126b63966 100644 --- a/tools/power/cpupower/utils/helpers/helpers.h +++ b/tools/power/cpupower/utils/helpers/helpers.h @@ -221,4 +221,6 @@ void print_online_cpus(void); void print_offline_cpus(void); void print_speed(unsigned long speed, int no_rounding); +void cppc_show_perf_and_freq(unsigned int cpu, int no_rounding); + #endif /* __CPUPOWERUTILS_HELPERS__ */ From 5100bd356cd315112c4e27e66e4f0129800eabd2 Mon Sep 17 00:00:00 2001 From: Jeremy Linton Date: Thu, 9 Jul 2026 17:13:44 -0500 Subject: [PATCH 21/76] cpupower: Print kernel and hardware frequency information The kernel asserted frequency from scaling_cur_freq may not always match the hardware reported frequency from cpuinfo_cur_freq. Print both values when they are available, and only print the unavailable message on x86 when the hardware frequency can't be read. Link: https://lore.kernel.org/r/20260709221344.1919794-4-jeremy.linton@arm.com Signed-off-by: Jeremy Linton Signed-off-by: Shuah Khan --- tools/power/cpupower/utils/cpufreq-info.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/power/cpupower/utils/cpufreq-info.c b/tools/power/cpupower/utils/cpufreq-info.c index 105f06e690cc..11629ae49f98 100644 --- a/tools/power/cpupower/utils/cpufreq-info.c +++ b/tools/power/cpupower/utils/cpufreq-info.c @@ -270,10 +270,10 @@ static int get_freq_hardware(unsigned int cpu, unsigned int human) { unsigned long freq; - if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF)) + freq = cpufreq_get_freq_hardware(cpu); + if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF) && !freq) return -EINVAL; - freq = cpufreq_get_freq_hardware(cpu); printf(_(" current CPU frequency: ")); if (!freq) { printf("Unable to call hardware\n"); @@ -514,8 +514,8 @@ static void debug_output_one(unsigned int cpu) get_available_governors(cpu); get_policy(cpu); - if (get_freq_hardware(cpu, 1) < 0) - get_freq_kernel(cpu, 1); + get_freq_hardware(cpu, 1); + get_freq_kernel(cpu, 1); get_boost_mode(cpu); get_perf_cap(cpu); } From d5c13047a132162d2649be876906ead691d12948 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:30:35 +0200 Subject: [PATCH 22/76] ACPI: processor: idle: Expand _LPI package sanity checks The _LPI package sanity checks in acpi_processor_evaluate_lpi() miss a couple of things, so expand them by adding a buffer size check before retrieving a struct acpi_power_register from it (and skip the given state if the buffer is not large enough to hold a register structure) and making the function avoid copying the state description from the ACPI table if there are too few elements in the package supposed to hold it. While at it, relocate and rephrase a comment about skipping _LPI state package elements [7-8]. Fixes: a36a7fecfe60 ("ACPI / processor_idle: Add support for Low Power Idle(LPI) states") Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/5084143.GXAFRqVoOG@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 4482cf28f56a..d573f201295a 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -927,6 +927,13 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, if (obj->type == ACPI_TYPE_BUFFER) { struct acpi_power_register *reg; + if (obj->buffer.length < sizeof(*reg)) { + acpi_handle_debug(handle, + "Invalid register data for _LPI state %d\n", + state_idx); + continue; + } + reg = (struct acpi_power_register *)obj->buffer.pointer; if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_IO && reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) @@ -945,13 +952,6 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, continue; } - /* elements[7,8] skipped for now i.e. Residency/Usage counter*/ - - obj = pkg_elem + 9; - if (obj->type == ACPI_TYPE_STRING) - strscpy(lpi_state->desc, obj->string.pointer, - ACPI_CX_DESC_LEN); - lpi_state->index = state_idx; if (obj_get_integer(pkg_elem + 0, &lpi_state->min_residency)) { pr_debug("No min. residency found, assuming 10 us\n"); @@ -974,6 +974,20 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, if (obj_get_integer(pkg_elem + 5, &lpi_state->enable_parent_state)) lpi_state->enable_parent_state = 0; + + /* Skip elements [7-8] i.e. Residency/Usage counters. */ + + /* + * Avoid out-of-bounds access if the size of the package is less + * than expected. + */ + if (element->package.count < 10) + continue; + + obj = pkg_elem + 9; + if (obj->type == ACPI_TYPE_STRING) + strscpy(lpi_state->desc, obj->string.pointer, + ACPI_CX_DESC_LEN); } acpi_handle_debug(handle, "Found %d power states\n", state_idx); From 291c2047e73cc60e5da9b0b3190e48d55d2b104b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:31:28 +0200 Subject: [PATCH 23/76] ACPI: processor: idle: Ignore _LPI states with SYSTEMIO entry method The only entry method for _LPI states supported by acpi_idle_lpi_enter() is FFH and it is better to ignore _LPI states with the SYSTEMIO entry method upfront than return an error from acpi_idle_lpi_enter() on attempts to use them. Update acpi_processor_evaluate_lpi() accordingly. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/2268989.irdbgypaU6@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index d573f201295a..aa6d5d8edc79 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -935,14 +935,15 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, } reg = (struct acpi_power_register *)obj->buffer.pointer; - if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_IO && - reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) + if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { + acpi_handle_debug(handle, + "Unsupported entry method for _LPI state %d\n", + state_idx); continue; + } + lpi_state->entry_method = ACPI_CSTATE_FFH; lpi_state->address = reg->address; - lpi_state->entry_method = - reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE ? - ACPI_CSTATE_FFH : ACPI_CSTATE_SYSTEMIO; } else if (obj->type == ACPI_TYPE_INTEGER) { lpi_state->entry_method = ACPI_CSTATE_INTEGER; lpi_state->address = obj->integer.value; From df0702bfb56b4b7e31b82ac16a4332ad9b311e13 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:32:15 +0200 Subject: [PATCH 24/76] ACPI: processor: idle: Unify debug in acpi_processor_evaluate_lpi() Use acpi_handle_debug() consistently for printing debug messages in acpi_processor_evaluate_lpi() because that makes it somewhat easier to identify the source of the problem in the ACPI tables. No intentional functional impact beyond debug output. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/3051550.e9J7NaK4W3@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index aa6d5d8edc79..e8682f779249 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -890,7 +890,7 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, /* There must be at least 4 elements = 3 elements + 1 package */ if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || lpi_data->package.count < 4) { - pr_debug("not enough elements in _LPI\n"); + acpi_handle_debug(handle, "Not enough elements in _LPI\n"); ret = -ENODATA; goto end; } @@ -899,7 +899,7 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, /* Validate number of power states. */ if (pkg_count < 1 || pkg_count != lpi_data->package.count - 3) { - pr_debug("count given by _LPI is not valid\n"); + acpi_handle_debug(handle, "Invalid _LPI state count\n"); ret = -ENODATA; goto end; } @@ -948,19 +948,24 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, lpi_state->entry_method = ACPI_CSTATE_INTEGER; lpi_state->address = obj->integer.value; } else { - pr_debug("Entry method of state-%d is invalid, disable it.\n", - state_idx); + acpi_handle_debug(handle, + "Invalid entry method for _LPI state %d\n", + state_idx); continue; } lpi_state->index = state_idx; if (obj_get_integer(pkg_elem + 0, &lpi_state->min_residency)) { - pr_debug("No min. residency found, assuming 10 us\n"); + acpi_handle_debug(handle, + "Assuming 10 us min. residency for _LPI state %d\n", + state_idx); lpi_state->min_residency = 10; } if (obj_get_integer(pkg_elem + 1, &lpi_state->wake_latency)) { - pr_debug("No wakeup residency found, assuming 10 us\n"); + acpi_handle_debug(handle, + "Assuming 10 us wake latency for _LPI state %d\n", + state_idx); lpi_state->wake_latency = 10; } From d06c12bebf9825c747e5f9dc7969c06c209bf8e2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:33:41 +0200 Subject: [PATCH 25/76] ACPI: processor: idle: Rearrange acpi_processor_evaluate_lpi() Rearrange acpi_processor_evaluate_lpi() to make it somewhat easier to follow and diagnose (if need be). In particular: * Rename some local variables and reorder their definitions. * Change the type of local variables used for storing firmware-provided values to unsigned int (they cannot be negative). * Eliminate local variable "loop" that is redundant. * Avoid explicit pointer arithmetic. * Print the correct number of _LPI state packages in the final debug message. No intentional functional impact beyond debug output. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/3426078.44csPzL39Z@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 64 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index e8682f779249..cd506e9e5a84 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -872,12 +872,12 @@ static int obj_get_integer(union acpi_object *obj, u32 *value) static int acpi_processor_evaluate_lpi(acpi_handle handle, struct acpi_lpi_states_array *info) { + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *lpi_data, *lpi_pkg; + unsigned int lpi_pkg_count, state_idx; + struct acpi_lpi_state *lpi_state; acpi_status status; int ret = 0; - int pkg_count, state_idx = 1, loop; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *lpi_data; - struct acpi_lpi_state *lpi_state; status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); if (ACPI_FAILURE(status)) { @@ -895,41 +895,46 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, goto end; } - pkg_count = lpi_data->package.elements[2].integer.value; + lpi_pkg_count = lpi_data->package.elements[2].integer.value; /* Validate number of power states. */ - if (pkg_count < 1 || pkg_count != lpi_data->package.count - 3) { + if (!lpi_pkg_count || lpi_pkg_count != lpi_data->package.count - 3) { acpi_handle_debug(handle, "Invalid _LPI state count\n"); ret = -ENODATA; goto end; } - lpi_state = kzalloc_objs(*lpi_state, pkg_count); + lpi_state = kzalloc_objs(*lpi_state, lpi_pkg_count); if (!lpi_state) { ret = -ENOMEM; goto end; } - info->size = pkg_count; + info->size = lpi_pkg_count; info->entries = lpi_state; - /* LPI States start at index 3 */ - for (loop = 3; state_idx <= pkg_count; loop++, state_idx++, lpi_state++) { - union acpi_object *element, *pkg_elem, *obj; + /* _LPI State packages start at index 3. */ + lpi_pkg = &lpi_data->package.elements[3]; - element = &lpi_data->package.elements[loop]; - if (element->type != ACPI_TYPE_PACKAGE || element->package.count < 7) + for (state_idx = 1; state_idx <= lpi_pkg_count; + state_idx++, lpi_state++, lpi_pkg++) { + union acpi_object *lpi_pkg_elem, *obj; + + lpi_state->index = state_idx; + + if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) continue; - pkg_elem = element->package.elements; + lpi_pkg_elem = lpi_pkg->package.elements; - obj = pkg_elem + 6; + /* Get the entry method first and skip the state if that fails. */ + obj = &lpi_pkg_elem[6]; if (obj->type == ACPI_TYPE_BUFFER) { struct acpi_power_register *reg; if (obj->buffer.length < sizeof(*reg)) { acpi_handle_debug(handle, - "Invalid register data for _LPI state %d\n", + "Invalid register data for _LPI state %u\n", state_idx); continue; } @@ -937,7 +942,7 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, reg = (struct acpi_power_register *)obj->buffer.pointer; if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { acpi_handle_debug(handle, - "Unsupported entry method for _LPI state %d\n", + "Unsupported entry method for _LPI state %u\n", state_idx); continue; } @@ -949,36 +954,35 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, lpi_state->address = obj->integer.value; } else { acpi_handle_debug(handle, - "Invalid entry method for _LPI state %d\n", + "Invalid entry method for _LPI state %u\n", state_idx); continue; } - lpi_state->index = state_idx; - if (obj_get_integer(pkg_elem + 0, &lpi_state->min_residency)) { + if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { acpi_handle_debug(handle, - "Assuming 10 us min. residency for _LPI state %d\n", + "Assuming 10 us min. residency for _LPI state %u\n", state_idx); lpi_state->min_residency = 10; } - if (obj_get_integer(pkg_elem + 1, &lpi_state->wake_latency)) { + if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { acpi_handle_debug(handle, - "Assuming 10 us wake latency for _LPI state %d\n", + "Assuming 10 us wake latency for _LPI state %u\n", state_idx); lpi_state->wake_latency = 10; } - if (obj_get_integer(pkg_elem + 2, &lpi_state->flags)) + if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) lpi_state->flags = 0; - if (obj_get_integer(pkg_elem + 3, &lpi_state->arch_flags)) + if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) lpi_state->arch_flags = 0; - if (obj_get_integer(pkg_elem + 4, &lpi_state->res_cnt_freq)) + if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) lpi_state->res_cnt_freq = 1; - if (obj_get_integer(pkg_elem + 5, &lpi_state->enable_parent_state)) + if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) lpi_state->enable_parent_state = 0; /* Skip elements [7-8] i.e. Residency/Usage counters. */ @@ -987,16 +991,16 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, * Avoid out-of-bounds access if the size of the package is less * than expected. */ - if (element->package.count < 10) + if (lpi_pkg->package.count < 10) continue; - obj = pkg_elem + 9; + obj = &lpi_pkg_elem[9]; if (obj->type == ACPI_TYPE_STRING) strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); } - acpi_handle_debug(handle, "Found %d power states\n", state_idx); + acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); end: kfree(buffer.pointer); return ret; From d48c5722248cbbe2e742c2d7e7b8a0417c93bd6b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:34:36 +0200 Subject: [PATCH 26/76] ACPI: processor: idle: Split acpi_processor_evaluate_lpi() Move individual _LPI state package processing from acpi_processor_evaluate_lpi() to a separate new function called process_lpi_state_package(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/10878273.nUPlyArG6x@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 167 +++++++++++++++++----------------- 1 file changed, 86 insertions(+), 81 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index cd506e9e5a84..771a7bd6fb73 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -869,6 +869,90 @@ static int obj_get_integer(union acpi_object *obj, u32 *value) return 0; } +static void process_lpi_state_package(union acpi_object *lpi_pkg, + struct acpi_lpi_state *lpi_state, + acpi_handle handle, + unsigned int state_idx) +{ + union acpi_object *lpi_pkg_elem, *obj; + + if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) + return; + + lpi_pkg_elem = lpi_pkg->package.elements; + + /* Get the entry method first and skip the state if that fails. */ + obj = &lpi_pkg_elem[6]; + if (obj->type == ACPI_TYPE_BUFFER) { + struct acpi_power_register *reg; + + if (obj->buffer.length < sizeof(*reg)) { + acpi_handle_debug(handle, + "Invalid register data for _LPI state %u\n", + state_idx); + return; + } + + reg = (struct acpi_power_register *)obj->buffer.pointer; + if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { + acpi_handle_debug(handle, + "Unsupported entry method for _LPI state %u\n", + state_idx); + return; + } + + lpi_state->entry_method = ACPI_CSTATE_FFH; + lpi_state->address = reg->address; + } else if (obj->type == ACPI_TYPE_INTEGER) { + lpi_state->entry_method = ACPI_CSTATE_INTEGER; + lpi_state->address = obj->integer.value; + } else { + acpi_handle_debug(handle, + "Invalid entry method for _LPI state %u\n", + state_idx); + return; + } + + if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { + acpi_handle_debug(handle, + "Assuming 10 us min. residency for _LPI state %u\n", + state_idx); + lpi_state->min_residency = 10; + } + + if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { + acpi_handle_debug(handle, + "Assuming 10 us wake latency for _LPI state %u\n", + state_idx); + lpi_state->wake_latency = 10; + } + + if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) + lpi_state->flags = 0; + + if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) + lpi_state->arch_flags = 0; + + if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) + lpi_state->res_cnt_freq = 1; + + if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) + lpi_state->enable_parent_state = 0; + + /* Skip elements [7-8] i.e. Residency/Usage counters. */ + + /* + * Avoid out-of-bounds access if the size of the package is less than + * expected. + */ + if (lpi_pkg->package.count < 10) + return; + + obj = &lpi_pkg_elem[9]; + if (obj->type == ACPI_TYPE_STRING) + strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); +} + static int acpi_processor_evaluate_lpi(acpi_handle handle, struct acpi_lpi_states_array *info) { @@ -916,88 +1000,9 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, /* _LPI State packages start at index 3. */ lpi_pkg = &lpi_data->package.elements[3]; - for (state_idx = 1; state_idx <= lpi_pkg_count; - state_idx++, lpi_state++, lpi_pkg++) { - union acpi_object *lpi_pkg_elem, *obj; - + for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { lpi_state->index = state_idx; - - if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) - continue; - - lpi_pkg_elem = lpi_pkg->package.elements; - - /* Get the entry method first and skip the state if that fails. */ - obj = &lpi_pkg_elem[6]; - if (obj->type == ACPI_TYPE_BUFFER) { - struct acpi_power_register *reg; - - if (obj->buffer.length < sizeof(*reg)) { - acpi_handle_debug(handle, - "Invalid register data for _LPI state %u\n", - state_idx); - continue; - } - - reg = (struct acpi_power_register *)obj->buffer.pointer; - if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { - acpi_handle_debug(handle, - "Unsupported entry method for _LPI state %u\n", - state_idx); - continue; - } - - lpi_state->entry_method = ACPI_CSTATE_FFH; - lpi_state->address = reg->address; - } else if (obj->type == ACPI_TYPE_INTEGER) { - lpi_state->entry_method = ACPI_CSTATE_INTEGER; - lpi_state->address = obj->integer.value; - } else { - acpi_handle_debug(handle, - "Invalid entry method for _LPI state %u\n", - state_idx); - continue; - } - - if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { - acpi_handle_debug(handle, - "Assuming 10 us min. residency for _LPI state %u\n", - state_idx); - lpi_state->min_residency = 10; - } - - if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { - acpi_handle_debug(handle, - "Assuming 10 us wake latency for _LPI state %u\n", - state_idx); - lpi_state->wake_latency = 10; - } - - if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) - lpi_state->flags = 0; - - if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) - lpi_state->arch_flags = 0; - - if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) - lpi_state->res_cnt_freq = 1; - - if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) - lpi_state->enable_parent_state = 0; - - /* Skip elements [7-8] i.e. Residency/Usage counters. */ - - /* - * Avoid out-of-bounds access if the size of the package is less - * than expected. - */ - if (lpi_pkg->package.count < 10) - continue; - - obj = &lpi_pkg_elem[9]; - if (obj->type == ACPI_TYPE_STRING) - strscpy(lpi_state->desc, obj->string.pointer, - ACPI_CX_DESC_LEN); + process_lpi_state_package(lpi_pkg++, lpi_state++, handle, state_idx); } acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); From 7358da875332d20e59a49cea3b35bab8c5519d53 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:35:14 +0200 Subject: [PATCH 27/76] ACPI: processor: idle: Introduce lpi_state_debug() Add a helper macro called lpi_state_debug() for printing debug messages regarding _LPI states and use it in acpi_processor_evaluate_lpi(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2043393.PYKUYFuaPT@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 771a7bd6fb73..f2ebd99a2895 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -869,6 +869,9 @@ static int obj_get_integer(union acpi_object *obj, u32 *value) return 0; } +#define lpi_state_debug(handle, message, state_idx) \ + acpi_handle_debug(handle, message " for _LPI state %u\n", state_idx) + static void process_lpi_state_package(union acpi_object *lpi_pkg, struct acpi_lpi_state *lpi_state, acpi_handle handle, @@ -887,17 +890,13 @@ static void process_lpi_state_package(union acpi_object *lpi_pkg, struct acpi_power_register *reg; if (obj->buffer.length < sizeof(*reg)) { - acpi_handle_debug(handle, - "Invalid register data for _LPI state %u\n", - state_idx); + lpi_state_debug(handle, "Invalid register data", state_idx); return; } reg = (struct acpi_power_register *)obj->buffer.pointer; if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { - acpi_handle_debug(handle, - "Unsupported entry method for _LPI state %u\n", - state_idx); + lpi_state_debug(handle, "Unsupported entry method", state_idx); return; } @@ -907,23 +906,17 @@ static void process_lpi_state_package(union acpi_object *lpi_pkg, lpi_state->entry_method = ACPI_CSTATE_INTEGER; lpi_state->address = obj->integer.value; } else { - acpi_handle_debug(handle, - "Invalid entry method for _LPI state %u\n", - state_idx); + lpi_state_debug(handle, "Invalid entry method", state_idx); return; } if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { - acpi_handle_debug(handle, - "Assuming 10 us min. residency for _LPI state %u\n", - state_idx); + lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); lpi_state->min_residency = 10; } if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { - acpi_handle_debug(handle, - "Assuming 10 us wake latency for _LPI state %u\n", - state_idx); + lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); lpi_state->wake_latency = 10; } From fcdc7587db7701f5c451b558b989822131b1b8c8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:36:28 +0200 Subject: [PATCH 28/76] ACPI: processor: idle: Rearrange acpi_processor_get_lpi_info() Reorder the definitions of local variables in acpi_processor_get_lpi_info() and drop local variable pr_ahandle that is not really necessary from it. Additionally, move two definitions of local variables to the loop in which they are used and rearrange the code slightly to prepare it for subsequent changes. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/7965163.EvYhyI6sBW@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index f2ebd99a2895..e990a43514e6 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1099,12 +1099,11 @@ int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) static int acpi_processor_get_lpi_info(struct acpi_processor *pr) { - int ret, i; - acpi_status status; - acpi_handle handle = pr->handle, pr_ahandle; - struct acpi_device *d = NULL; - struct acpi_lpi_states_array info[2], *tmp, *prev, *curr; + struct acpi_lpi_states_array info[2], *prev, *curr; + acpi_handle handle = pr->handle; unsigned int state_count; + acpi_status status; + int ret, i; /* make sure our architecture has support */ ret = acpi_processor_ffh_lpi_probe(pr->id); @@ -1117,22 +1116,26 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) if (!acpi_has_method(handle, "_LPI")) return -EINVAL; - prev = &info[0]; - curr = &info[1]; - handle = pr->handle; - ret = acpi_processor_evaluate_lpi(handle, prev); + curr = &info[0]; + + ret = acpi_processor_evaluate_lpi(handle, curr); if (ret) return ret; - state_count = flatten_lpi_states(pr, 0, prev, NULL); - status = acpi_get_parent(handle, &pr_ahandle); + state_count = flatten_lpi_states(pr, 0, curr, NULL); + + prev = curr; + curr = &info[1]; + + status = acpi_get_parent(handle, &handle); while (ACPI_SUCCESS(status)) { - d = acpi_fetch_acpi_dev(pr_ahandle); + struct acpi_lpi_states_array *tmp; + struct acpi_device *d; + + d = acpi_fetch_acpi_dev(handle); if (!d) break; - handle = pr_ahandle; - if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) break; @@ -1149,7 +1152,7 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) tmp = prev, prev = curr, curr = tmp; - status = acpi_get_parent(handle, &pr_ahandle); + status = acpi_get_parent(handle, &handle); } /* reset the index after flattening */ From ab96ea910d06d60758adbc330456528cb41cf2eb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:37:09 +0200 Subject: [PATCH 29/76] ACPI: processor: idle: Rework first-level _LPI states processing The first-level _LPI states need not be combined with the previous level and the entry method for them cannot be ACPI_CSTATE_INTEGER, so process them directly in acpi_processor_get_lpi_info() instead of doing a special case for them in flatten_lpi_states(). Also bail out if there are no _LPI states at the first level because that means that there are no _LPI states at all. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/3703077.iIbC2pHGDl@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 47 +++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index e990a43514e6..efd3e76377fa 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1068,13 +1068,6 @@ static unsigned int flatten_lpi_states(struct acpi_processor *pr, flpi = &pr->power.lpi_states[flat_state_cnt]; - if (!prev_level) { /* leaf/processor node */ - memcpy(flpi, t, sizeof(*t)); - stash_composite_state(curr_level, flpi); - flat_state_cnt++; - continue; - } - for (i = 0; i < prev_level->composite_states_size; i++) { p = prev_level->composite_states[i]; if (t->index <= p->enable_parent_state && @@ -1101,9 +1094,10 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) { struct acpi_lpi_states_array info[2], *prev, *curr; acpi_handle handle = pr->handle; - unsigned int state_count; + unsigned int state_count = 0; acpi_status status; - int ret, i; + unsigned int i; + int ret; /* make sure our architecture has support */ ret = acpi_processor_ffh_lpi_probe(pr->id); @@ -1117,12 +1111,45 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) return -EINVAL; curr = &info[0]; + curr->composite_states_size = 0; ret = acpi_processor_evaluate_lpi(handle, curr); if (ret) return ret; - state_count = flatten_lpi_states(pr, 0, curr, NULL); + /* Copy all of the usable first-level states to power.lpi_states[]. */ + for (i = 0; i < curr->size; i++) { + struct acpi_lpi_state *lpi = &curr->entries[i]; + struct acpi_lpi_state *flpi; + + /* + * Skip states that are not enabled or have an inadequate entry + * method for this level. + */ + if (!(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) || + lpi->entry_method == ACPI_CSTATE_INTEGER) + continue; + + if (state_count >= ACPI_PROCESSOR_MAX_POWER) { + acpi_handle_info(handle, + "No space for more _LPI states than %d\n", + ACPI_PROCESSOR_MAX_POWER); + break; + } + + flpi = &pr->power.lpi_states[state_count++]; + memcpy(flpi, lpi, sizeof(*lpi)); + stash_composite_state(curr, flpi); + } + + kfree(curr->entries); + + /* + * If there are no _LPI states at the first level, there are no _LPI + * states at all. + */ + if (!state_count) + return -ENODATA; prev = curr; curr = &info[1]; From 863572d8711c7f02344f5e37163063c43c4b105d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:37:49 +0200 Subject: [PATCH 30/76] ACPI: processor: idle: Drop redundant _LPI presence checks The acpi_has_method() checks for _LPI in acpi_processor_get_lpi_info() are redundant because acpi_processor_evaluate_lpi() returns an error when _LPI is not present, so drop them. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/3349003.5fSG56mABF@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index efd3e76377fa..274abe3da7a4 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1107,9 +1107,6 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) if (!osc_pc_lpi_support_confirmed) return -EOPNOTSUPP; - if (!acpi_has_method(handle, "_LPI")) - return -EINVAL; - curr = &info[0]; curr->composite_states_size = 0; @@ -1166,10 +1163,6 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) break; - /* can be optional ? */ - if (!acpi_has_method(handle, "_LPI")) - break; - ret = acpi_processor_evaluate_lpi(handle, curr); if (ret) break; From 75739f2c708f7be888fd5353811f169bbbc44313 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:38:29 +0200 Subject: [PATCH 31/76] ACPI: processor: idle: Rearrange loop in acpi_processor_get_lpi_info() Eliminate local variable status (that is redundant) from acpi_processor_get_lpi_info() and make that function call acpi_get_parent() in one place. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/1862515.VLH7GnMWUR@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 274abe3da7a4..57a0a8aaa42f 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1095,7 +1095,6 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) struct acpi_lpi_states_array info[2], *prev, *curr; acpi_handle handle = pr->handle; unsigned int state_count = 0; - acpi_status status; unsigned int i; int ret; @@ -1151,11 +1150,13 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) prev = curr; curr = &info[1]; - status = acpi_get_parent(handle, &handle); - while (ACPI_SUCCESS(status)) { + for (;;) { struct acpi_lpi_states_array *tmp; struct acpi_device *d; + if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) + break; + d = acpi_fetch_acpi_dev(handle); if (!d) break; @@ -1171,8 +1172,6 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) state_count = flatten_lpi_states(pr, state_count, curr, prev); tmp = prev, prev = curr, curr = tmp; - - status = acpi_get_parent(handle, &handle); } /* reset the index after flattening */ From 592343c4d2a215f07b5d5c657370a4fa2ac731c6 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:39:06 +0200 Subject: [PATCH 32/76] ACPI: processor: idle: Rework flatten_lpi_states() Rewrite flatten_lpi_states() to make it easier to follow: * Rename its flat_state_cnt, curr_level and prev_level parameters to state_count, curr, and prev, respectively, so their names match the names of analogous variables in acpi_processor_get_lpi_info(). * Eliminate a redundant local variable state_count. * Move definitions of local variables to the code blocks in which they are used. * Reduce the indentation level in the inner loop. * Use more meaningful names for local variables. * Move two statements that belong in acpi_processor_get_lpi_info() from flatten_lpi_states() to that function. * Use acpi_handle_info() for printing a message when the count of flattened states gets too large and drop the message requesting ACPI_PROCESSOR_MAX_POWER to be adjusted which is pointless. * Add a comment explaining what happens in that function. No intentional functional impact beyond the message printed when the count of flattened states is too large. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/2064627.usQuhbGJ8B@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 63 +++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 57a0a8aaa42f..edc9b5d88b79 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1045,44 +1045,53 @@ static void stash_composite_state(struct acpi_lpi_states_array *curr_level, } static unsigned int flatten_lpi_states(struct acpi_processor *pr, - unsigned int flat_state_cnt, - struct acpi_lpi_states_array *curr_level, - struct acpi_lpi_states_array *prev_level) + unsigned int state_count, + struct acpi_lpi_states_array *curr, + struct acpi_lpi_states_array *prev) { - int i, j, state_count = curr_level->size; - struct acpi_lpi_state *p, *t = curr_level->entries; + struct acpi_lpi_state *parent_lpi = curr->entries; + unsigned int j; - curr_level->composite_states_size = 0; - for (j = 0; j < state_count; j++, t++) { + /* + * Combine each of the "raw" _LPI states from the current (processor + * container) level with all of the composite _LPI states from the + * previous (processor or processor container) level. + */ + for (j = 0; j < curr->size; j++, parent_lpi++) { struct acpi_lpi_state *flpi; + int i; - if (!(t->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) + if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) continue; - if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) { - pr_warn("Limiting number of LPI states to max (%d)\n", - ACPI_PROCESSOR_MAX_POWER); - pr_warn("Please increase ACPI_PROCESSOR_MAX_POWER if needed.\n"); + if (state_count >= ACPI_PROCESSOR_MAX_POWER) { + acpi_handle_info(pr->handle, + "No space for more _LPI states than %d\n", + ACPI_PROCESSOR_MAX_POWER); break; } - flpi = &pr->power.lpi_states[flat_state_cnt]; + flpi = &pr->power.lpi_states[state_count]; - for (i = 0; i < prev_level->composite_states_size; i++) { - p = prev_level->composite_states[i]; - if (t->index <= p->enable_parent_state && - combine_lpi_states(p, t, flpi)) { - stash_composite_state(curr_level, flpi); - flat_state_cnt++; - flpi++; - if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) - break; - } + for (i = 0; i < prev->composite_states_size; i++) { + struct acpi_lpi_state *local_lpi = prev->composite_states[i]; + + if (parent_lpi->index > local_lpi->enable_parent_state) + continue; + + if (!combine_lpi_states(local_lpi, parent_lpi, flpi)) + continue; + + stash_composite_state(curr, flpi); + state_count++; + flpi++; + + if (state_count >= ACPI_PROCESSOR_MAX_POWER) + break; } } - kfree(curr_level->entries); - return flat_state_cnt; + return state_count; } int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) @@ -1164,6 +1173,8 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) break; + curr->composite_states_size = 0; + ret = acpi_processor_evaluate_lpi(handle, curr); if (ret) break; @@ -1171,6 +1182,8 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) /* flatten all the LPI states in this level of hierarchy */ state_count = flatten_lpi_states(pr, state_count, curr, prev); + kfree(curr->entries); + tmp = prev, prev = curr, curr = tmp; } From db4c69b33bc5f3e41b80b10b2e00bed4a690c648 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:39:49 +0200 Subject: [PATCH 33/76] ACPI: processor: idle: Introduce too_many_states() for _LPI To reduce code duplication, introduce a function called too_many_states() that will check if the total number of _LPI states for a given CPU is too large and print a message in that case. Use that function in flatten_lpi_states() and acpi_processor_get_lpi_info(). No functional impact beyond reducing dynamic debug flexibility. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/1964818.CQOukoFCf9@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index edc9b5d88b79..c7f206c094fb 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1044,6 +1044,16 @@ static void stash_composite_state(struct acpi_lpi_states_array *curr_level, curr_level->composite_states[curr_level->composite_states_size++] = t; } +static bool too_many_states(acpi_handle handle, unsigned int state_count) +{ + if (state_count < ACPI_PROCESSOR_MAX_POWER) + return false; + + acpi_handle_info(handle, "No space for more _LPI states than %d\n", + ACPI_PROCESSOR_MAX_POWER); + return true; +} + static unsigned int flatten_lpi_states(struct acpi_processor *pr, unsigned int state_count, struct acpi_lpi_states_array *curr, @@ -1064,12 +1074,8 @@ static unsigned int flatten_lpi_states(struct acpi_processor *pr, if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) continue; - if (state_count >= ACPI_PROCESSOR_MAX_POWER) { - acpi_handle_info(pr->handle, - "No space for more _LPI states than %d\n", - ACPI_PROCESSOR_MAX_POWER); + if (too_many_states(pr->handle, state_count)) break; - } flpi = &pr->power.lpi_states[state_count]; @@ -1135,12 +1141,8 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) lpi->entry_method == ACPI_CSTATE_INTEGER) continue; - if (state_count >= ACPI_PROCESSOR_MAX_POWER) { - acpi_handle_info(handle, - "No space for more _LPI states than %d\n", - ACPI_PROCESSOR_MAX_POWER); + if (too_many_states(pr->handle, state_count)) break; - } flpi = &pr->power.lpi_states[state_count++]; memcpy(flpi, lpi, sizeof(*lpi)); From 99ba75bbc17bb9f6e1e46e809651f5f5e39f8f69 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:40:35 +0200 Subject: [PATCH 34/76] ACPI: processor: idle: Introduce acpi_processor_extract_lpi_info() In preparation for adding ACPI _LPI support to the intel_idle driver, move the majority of the acpi_processor_get_lpi_info() function body to a new function called acpi_processor_extract_lpi_info() that will be exported to external code subsequently. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/2709187.Lt9SDvczpP@rafael.j.wysocki --- drivers/acpi/processor_idle.c | 44 +++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index c7f206c094fb..2c6c9a7365fb 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1054,7 +1054,8 @@ static bool too_many_states(acpi_handle handle, unsigned int state_count) return true; } -static unsigned int flatten_lpi_states(struct acpi_processor *pr, +static unsigned int flatten_lpi_states(acpi_handle handle, + struct acpi_lpi_state *lpi_states, unsigned int state_count, struct acpi_lpi_states_array *curr, struct acpi_lpi_states_array *prev) @@ -1074,10 +1075,10 @@ static unsigned int flatten_lpi_states(struct acpi_processor *pr, if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) continue; - if (too_many_states(pr->handle, state_count)) + if (too_many_states(handle, state_count)) break; - flpi = &pr->power.lpi_states[state_count]; + flpi = &lpi_states[state_count]; for (i = 0; i < prev->composite_states_size; i++) { struct acpi_lpi_state *local_lpi = prev->composite_states[i]; @@ -1105,19 +1106,15 @@ int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) return -EOPNOTSUPP; } -static int acpi_processor_get_lpi_info(struct acpi_processor *pr) +static int acpi_processor_extract_lpi_info(acpi_handle pr_handle, + struct acpi_processor_power *pr_power) { struct acpi_lpi_states_array info[2], *prev, *curr; - acpi_handle handle = pr->handle; + acpi_handle handle = pr_handle; unsigned int state_count = 0; unsigned int i; int ret; - /* make sure our architecture has support */ - ret = acpi_processor_ffh_lpi_probe(pr->id); - if (ret == -EOPNOTSUPP) - return ret; - if (!osc_pc_lpi_support_confirmed) return -EOPNOTSUPP; @@ -1141,10 +1138,10 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) lpi->entry_method == ACPI_CSTATE_INTEGER) continue; - if (too_many_states(pr->handle, state_count)) + if (too_many_states(pr_handle, state_count)) break; - flpi = &pr->power.lpi_states[state_count++]; + flpi = &pr_power->lpi_states[state_count++]; memcpy(flpi, lpi, sizeof(*lpi)); stash_composite_state(curr, flpi); } @@ -1182,7 +1179,8 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) break; /* flatten all the LPI states in this level of hierarchy */ - state_count = flatten_lpi_states(pr, state_count, curr, prev); + state_count = flatten_lpi_states(pr_handle, pr_power->lpi_states, + state_count, curr, prev); kfree(curr->entries); @@ -1191,9 +1189,25 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) /* reset the index after flattening */ for (i = 0; i < state_count; i++) - pr->power.lpi_states[i].index = i; + pr_power->lpi_states[i].index = i; - pr->power.count = state_count; + pr_power->count = state_count; + + return 0; +} + +static int acpi_processor_get_lpi_info(struct acpi_processor *pr) +{ + int ret; + + /* make sure our architecture has support */ + ret = acpi_processor_ffh_lpi_probe(pr->id); + if (ret == -EOPNOTSUPP) + return ret; + + ret = acpi_processor_extract_lpi_info(pr->handle, &pr->power); + if (ret) + return ret; /* Tell driver that _LPI is supported. */ pr->flags.has_lpi = 1; From c60e851f9c179c265dd71a8ecc49abee977bff14 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:41:36 +0200 Subject: [PATCH 35/76] ACPI: processor: idle: Relocate acpi_processor_extract_lpi_info() In preparation for adding ACPI _LPI support to the intel_idle driver, move acpi_processor_extract_lpi_info() along with some static functions used by it to the acpi_processor.c file containing the non-modular part of the ACPI processor driver, so it can be called by external non-modular code like intel_idle. However, export it to modules in the ACPI_PROCESSOR_IDLE import namespace so that the modular part of the ACPI processor driver can still invoke it. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/2277662.Mh6RI2rZIc@rafael.j.wysocki --- drivers/acpi/acpi_processor.c | 341 ++++++++++++++++++++++++++++++++++ drivers/acpi/processor_idle.c | 338 --------------------------------- include/linux/acpi.h | 11 ++ 3 files changed, 352 insertions(+), 338 deletions(-) diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c index 00775b91bd41..cdba1aee84e9 100644 --- a/drivers/acpi/acpi_processor.c +++ b/drivers/acpi/acpi_processor.c @@ -994,3 +994,344 @@ int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, } EXPORT_SYMBOL_NS_GPL(acpi_processor_evaluate_cst, "ACPI_PROCESSOR_IDLE"); #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ + +#ifdef CONFIG_ACPI_PROCESSOR_IDLE +struct acpi_lpi_states_array { + unsigned int size; + unsigned int composite_states_size; + struct acpi_lpi_state *entries; + struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; +}; + +static int obj_get_integer(union acpi_object *obj, u32 *value) +{ + if (obj->type != ACPI_TYPE_INTEGER) + return -EINVAL; + + *value = obj->integer.value; + return 0; +} + +#define lpi_state_debug(handle, message, state_idx) \ + acpi_handle_debug(handle, message " for _LPI state %u\n", state_idx) + +static void process_lpi_state_package(union acpi_object *lpi_pkg, + struct acpi_lpi_state *lpi_state, + acpi_handle handle, + unsigned int state_idx) +{ + union acpi_object *lpi_pkg_elem, *obj; + + if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) + return; + + lpi_pkg_elem = lpi_pkg->package.elements; + + /* Get the entry method first and skip the state if that fails. */ + obj = &lpi_pkg_elem[6]; + if (obj->type == ACPI_TYPE_BUFFER) { + struct acpi_power_register *reg; + + if (obj->buffer.length < sizeof(*reg)) { + lpi_state_debug(handle, "Invalid register data", state_idx); + return; + } + + reg = (struct acpi_power_register *)obj->buffer.pointer; + if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { + lpi_state_debug(handle, "Unsupported entry method", state_idx); + return; + } + + lpi_state->entry_method = ACPI_CSTATE_FFH; + lpi_state->address = reg->address; + } else if (obj->type == ACPI_TYPE_INTEGER) { + lpi_state->entry_method = ACPI_CSTATE_INTEGER; + lpi_state->address = obj->integer.value; + } else { + lpi_state_debug(handle, "Invalid entry method", state_idx); + return; + } + + if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { + lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); + lpi_state->min_residency = 10; + } + + if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { + lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); + lpi_state->wake_latency = 10; + } + + if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) + lpi_state->flags = 0; + + if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) + lpi_state->arch_flags = 0; + + if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) + lpi_state->res_cnt_freq = 1; + + if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) + lpi_state->enable_parent_state = 0; + + /* Skip elements [7-8] i.e. Residency/Usage counters. */ + + /* + * Avoid out-of-bounds access if the size of the package is less than + * expected. + */ + if (lpi_pkg->package.count < 10) + return; + + obj = &lpi_pkg_elem[9]; + if (obj->type == ACPI_TYPE_STRING) + strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); +} + +static int acpi_processor_evaluate_lpi(acpi_handle handle, + struct acpi_lpi_states_array *info) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *lpi_data, *lpi_pkg; + unsigned int lpi_pkg_count, state_idx; + struct acpi_lpi_state *lpi_state; + acpi_status status; + int ret = 0; + + status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); + if (ACPI_FAILURE(status)) { + acpi_handle_debug(handle, "No _LPI, giving up\n"); + return -ENODEV; + } + + lpi_data = buffer.pointer; + + /* There must be at least 4 elements = 3 elements + 1 package */ + if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || + lpi_data->package.count < 4) { + acpi_handle_debug(handle, "Not enough elements in _LPI\n"); + ret = -ENODATA; + goto end; + } + + lpi_pkg_count = lpi_data->package.elements[2].integer.value; + + /* Validate number of power states. */ + if (!lpi_pkg_count || lpi_pkg_count != lpi_data->package.count - 3) { + acpi_handle_debug(handle, "Invalid _LPI state count\n"); + ret = -ENODATA; + goto end; + } + + lpi_state = kzalloc_objs(*lpi_state, lpi_pkg_count); + if (!lpi_state) { + ret = -ENOMEM; + goto end; + } + + info->size = lpi_pkg_count; + info->entries = lpi_state; + + /* _LPI State packages start at index 3. */ + lpi_pkg = &lpi_data->package.elements[3]; + + for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { + lpi_state->index = state_idx; + process_lpi_state_package(lpi_pkg++, lpi_state++, handle, state_idx); + } + + acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); +end: + kfree(buffer.pointer); + return ret; +} + +/** + * combine_lpi_states - combine local and parent LPI states to form a composite LPI state + * + * @local: local LPI state + * @parent: parent LPI state + * @result: composite LPI state + */ +static bool combine_lpi_states(struct acpi_lpi_state *local, + struct acpi_lpi_state *parent, + struct acpi_lpi_state *result) +{ + if (parent->entry_method == ACPI_CSTATE_INTEGER) { + if (!parent->address) /* 0 means autopromotable */ + return false; + result->address = local->address + parent->address; + } else { + result->address = parent->address; + } + + result->min_residency = max(local->min_residency, parent->min_residency); + result->wake_latency = local->wake_latency + parent->wake_latency; + result->enable_parent_state = parent->enable_parent_state; + result->entry_method = local->entry_method; + + result->flags = parent->flags; + result->arch_flags = parent->arch_flags; + result->index = parent->index; + + scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); + return true; +} + +#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) + +static void stash_composite_state(struct acpi_lpi_states_array *curr_level, + struct acpi_lpi_state *t) +{ + curr_level->composite_states[curr_level->composite_states_size++] = t; +} + +static bool too_many_states(acpi_handle handle, unsigned int state_count) +{ + if (state_count < ACPI_PROCESSOR_MAX_POWER) + return false; + + acpi_handle_info(handle, "No space for more _LPI states than %d\n", + ACPI_PROCESSOR_MAX_POWER); + return true; +} + +static unsigned int flatten_lpi_states(acpi_handle handle, + struct acpi_lpi_state *lpi_states, + unsigned int state_count, + struct acpi_lpi_states_array *curr, + struct acpi_lpi_states_array *prev) +{ + struct acpi_lpi_state *parent_lpi = curr->entries; + unsigned int j; + + /* + * Combine each of the "raw" _LPI states from the current (processor + * container) level with all of the composite _LPI states from the + * previous (processor or processor container) level. + */ + for (j = 0; j < curr->size; j++, parent_lpi++) { + struct acpi_lpi_state *flpi; + int i; + + if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) + continue; + + if (too_many_states(handle, state_count)) + break; + + flpi = &lpi_states[state_count]; + + for (i = 0; i < prev->composite_states_size; i++) { + struct acpi_lpi_state *local_lpi = prev->composite_states[i]; + + if (parent_lpi->index > local_lpi->enable_parent_state) + continue; + + if (!combine_lpi_states(local_lpi, parent_lpi, flpi)) + continue; + + stash_composite_state(curr, flpi); + state_count++; + flpi++; + + if (state_count >= ACPI_PROCESSOR_MAX_POWER) + break; + } + } + + return state_count; +} + +int acpi_processor_extract_lpi_info(acpi_handle pr_handle, + struct acpi_processor_power *pr_power) +{ + struct acpi_lpi_states_array info[2], *prev, *curr; + acpi_handle handle = pr_handle; + unsigned int state_count = 0; + unsigned int i; + int ret; + + if (!osc_pc_lpi_support_confirmed) + return -EOPNOTSUPP; + + curr = &info[0]; + curr->composite_states_size = 0; + + ret = acpi_processor_evaluate_lpi(handle, curr); + if (ret) + return ret; + + /* Copy all of the usable first-level states to power.lpi_states[]. */ + for (i = 0; i < curr->size; i++) { + struct acpi_lpi_state *lpi = &curr->entries[i]; + struct acpi_lpi_state *flpi; + + /* + * Skip states that are not enabled or have an inadequate entry + * method for this level. + */ + if (!(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) || + lpi->entry_method == ACPI_CSTATE_INTEGER) + continue; + + if (too_many_states(pr_handle, state_count)) + break; + + flpi = &pr_power->lpi_states[state_count++]; + memcpy(flpi, lpi, sizeof(*lpi)); + stash_composite_state(curr, flpi); + } + + kfree(curr->entries); + + /* + * If there are no _LPI states at the first level, there are no _LPI + * states at all. + */ + if (!state_count) + return -ENODATA; + + prev = curr; + curr = &info[1]; + + for (;;) { + struct acpi_lpi_states_array *tmp; + struct acpi_device *d; + + if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) + break; + + d = acpi_fetch_acpi_dev(handle); + if (!d) + break; + + if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) + break; + + curr->composite_states_size = 0; + + ret = acpi_processor_evaluate_lpi(handle, curr); + if (ret) + break; + + /* flatten all the LPI states in this level of hierarchy */ + state_count = flatten_lpi_states(pr_handle, pr_power->lpi_states, + state_count, curr, prev); + + kfree(curr->entries); + + tmp = prev, prev = curr, curr = tmp; + } + + /* reset the index after flattening */ + for (i = 0; i < state_count; i++) + pr_power->lpi_states[i].index = i; + + pr_power->count = state_count; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acpi_processor_extract_lpi_info, "ACPI_PROCESSOR_IDLE"); +#endif /* CONFIG_ACPI_PROCESSOR_IDLE */ diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 2c6c9a7365fb..44a52148bc25 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -853,349 +853,11 @@ static int acpi_processor_setup_cstates(struct acpi_processor *pr) #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ -struct acpi_lpi_states_array { - unsigned int size; - unsigned int composite_states_size; - struct acpi_lpi_state *entries; - struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; -}; - -static int obj_get_integer(union acpi_object *obj, u32 *value) -{ - if (obj->type != ACPI_TYPE_INTEGER) - return -EINVAL; - - *value = obj->integer.value; - return 0; -} - -#define lpi_state_debug(handle, message, state_idx) \ - acpi_handle_debug(handle, message " for _LPI state %u\n", state_idx) - -static void process_lpi_state_package(union acpi_object *lpi_pkg, - struct acpi_lpi_state *lpi_state, - acpi_handle handle, - unsigned int state_idx) -{ - union acpi_object *lpi_pkg_elem, *obj; - - if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) - return; - - lpi_pkg_elem = lpi_pkg->package.elements; - - /* Get the entry method first and skip the state if that fails. */ - obj = &lpi_pkg_elem[6]; - if (obj->type == ACPI_TYPE_BUFFER) { - struct acpi_power_register *reg; - - if (obj->buffer.length < sizeof(*reg)) { - lpi_state_debug(handle, "Invalid register data", state_idx); - return; - } - - reg = (struct acpi_power_register *)obj->buffer.pointer; - if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { - lpi_state_debug(handle, "Unsupported entry method", state_idx); - return; - } - - lpi_state->entry_method = ACPI_CSTATE_FFH; - lpi_state->address = reg->address; - } else if (obj->type == ACPI_TYPE_INTEGER) { - lpi_state->entry_method = ACPI_CSTATE_INTEGER; - lpi_state->address = obj->integer.value; - } else { - lpi_state_debug(handle, "Invalid entry method", state_idx); - return; - } - - if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { - lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); - lpi_state->min_residency = 10; - } - - if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { - lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); - lpi_state->wake_latency = 10; - } - - if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) - lpi_state->flags = 0; - - if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) - lpi_state->arch_flags = 0; - - if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) - lpi_state->res_cnt_freq = 1; - - if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) - lpi_state->enable_parent_state = 0; - - /* Skip elements [7-8] i.e. Residency/Usage counters. */ - - /* - * Avoid out-of-bounds access if the size of the package is less than - * expected. - */ - if (lpi_pkg->package.count < 10) - return; - - obj = &lpi_pkg_elem[9]; - if (obj->type == ACPI_TYPE_STRING) - strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); -} - -static int acpi_processor_evaluate_lpi(acpi_handle handle, - struct acpi_lpi_states_array *info) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *lpi_data, *lpi_pkg; - unsigned int lpi_pkg_count, state_idx; - struct acpi_lpi_state *lpi_state; - acpi_status status; - int ret = 0; - - status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); - if (ACPI_FAILURE(status)) { - acpi_handle_debug(handle, "No _LPI, giving up\n"); - return -ENODEV; - } - - lpi_data = buffer.pointer; - - /* There must be at least 4 elements = 3 elements + 1 package */ - if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || - lpi_data->package.count < 4) { - acpi_handle_debug(handle, "Not enough elements in _LPI\n"); - ret = -ENODATA; - goto end; - } - - lpi_pkg_count = lpi_data->package.elements[2].integer.value; - - /* Validate number of power states. */ - if (!lpi_pkg_count || lpi_pkg_count != lpi_data->package.count - 3) { - acpi_handle_debug(handle, "Invalid _LPI state count\n"); - ret = -ENODATA; - goto end; - } - - lpi_state = kzalloc_objs(*lpi_state, lpi_pkg_count); - if (!lpi_state) { - ret = -ENOMEM; - goto end; - } - - info->size = lpi_pkg_count; - info->entries = lpi_state; - - /* _LPI State packages start at index 3. */ - lpi_pkg = &lpi_data->package.elements[3]; - - for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { - lpi_state->index = state_idx; - process_lpi_state_package(lpi_pkg++, lpi_state++, handle, state_idx); - } - - acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); -end: - kfree(buffer.pointer); - return ret; -} - -/** - * combine_lpi_states - combine local and parent LPI states to form a composite LPI state - * - * @local: local LPI state - * @parent: parent LPI state - * @result: composite LPI state - */ -static bool combine_lpi_states(struct acpi_lpi_state *local, - struct acpi_lpi_state *parent, - struct acpi_lpi_state *result) -{ - if (parent->entry_method == ACPI_CSTATE_INTEGER) { - if (!parent->address) /* 0 means autopromotable */ - return false; - result->address = local->address + parent->address; - } else { - result->address = parent->address; - } - - result->min_residency = max(local->min_residency, parent->min_residency); - result->wake_latency = local->wake_latency + parent->wake_latency; - result->enable_parent_state = parent->enable_parent_state; - result->entry_method = local->entry_method; - - result->flags = parent->flags; - result->arch_flags = parent->arch_flags; - result->index = parent->index; - - scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); - return true; -} - -#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) - -static void stash_composite_state(struct acpi_lpi_states_array *curr_level, - struct acpi_lpi_state *t) -{ - curr_level->composite_states[curr_level->composite_states_size++] = t; -} - -static bool too_many_states(acpi_handle handle, unsigned int state_count) -{ - if (state_count < ACPI_PROCESSOR_MAX_POWER) - return false; - - acpi_handle_info(handle, "No space for more _LPI states than %d\n", - ACPI_PROCESSOR_MAX_POWER); - return true; -} - -static unsigned int flatten_lpi_states(acpi_handle handle, - struct acpi_lpi_state *lpi_states, - unsigned int state_count, - struct acpi_lpi_states_array *curr, - struct acpi_lpi_states_array *prev) -{ - struct acpi_lpi_state *parent_lpi = curr->entries; - unsigned int j; - - /* - * Combine each of the "raw" _LPI states from the current (processor - * container) level with all of the composite _LPI states from the - * previous (processor or processor container) level. - */ - for (j = 0; j < curr->size; j++, parent_lpi++) { - struct acpi_lpi_state *flpi; - int i; - - if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) - continue; - - if (too_many_states(handle, state_count)) - break; - - flpi = &lpi_states[state_count]; - - for (i = 0; i < prev->composite_states_size; i++) { - struct acpi_lpi_state *local_lpi = prev->composite_states[i]; - - if (parent_lpi->index > local_lpi->enable_parent_state) - continue; - - if (!combine_lpi_states(local_lpi, parent_lpi, flpi)) - continue; - - stash_composite_state(curr, flpi); - state_count++; - flpi++; - - if (state_count >= ACPI_PROCESSOR_MAX_POWER) - break; - } - } - - return state_count; -} - int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) { return -EOPNOTSUPP; } -static int acpi_processor_extract_lpi_info(acpi_handle pr_handle, - struct acpi_processor_power *pr_power) -{ - struct acpi_lpi_states_array info[2], *prev, *curr; - acpi_handle handle = pr_handle; - unsigned int state_count = 0; - unsigned int i; - int ret; - - if (!osc_pc_lpi_support_confirmed) - return -EOPNOTSUPP; - - curr = &info[0]; - curr->composite_states_size = 0; - - ret = acpi_processor_evaluate_lpi(handle, curr); - if (ret) - return ret; - - /* Copy all of the usable first-level states to power.lpi_states[]. */ - for (i = 0; i < curr->size; i++) { - struct acpi_lpi_state *lpi = &curr->entries[i]; - struct acpi_lpi_state *flpi; - - /* - * Skip states that are not enabled or have an inadequate entry - * method for this level. - */ - if (!(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) || - lpi->entry_method == ACPI_CSTATE_INTEGER) - continue; - - if (too_many_states(pr_handle, state_count)) - break; - - flpi = &pr_power->lpi_states[state_count++]; - memcpy(flpi, lpi, sizeof(*lpi)); - stash_composite_state(curr, flpi); - } - - kfree(curr->entries); - - /* - * If there are no _LPI states at the first level, there are no _LPI - * states at all. - */ - if (!state_count) - return -ENODATA; - - prev = curr; - curr = &info[1]; - - for (;;) { - struct acpi_lpi_states_array *tmp; - struct acpi_device *d; - - if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) - break; - - d = acpi_fetch_acpi_dev(handle); - if (!d) - break; - - if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) - break; - - curr->composite_states_size = 0; - - ret = acpi_processor_evaluate_lpi(handle, curr); - if (ret) - break; - - /* flatten all the LPI states in this level of hierarchy */ - state_count = flatten_lpi_states(pr_handle, pr_power->lpi_states, - state_count, curr, prev); - - kfree(curr->entries); - - tmp = prev, prev = curr, curr = tmp; - } - - /* reset the index after flattening */ - for (i = 0; i < state_count; i++) - pr_power->lpi_states[i].index = i; - - pr_power->count = state_count; - - return 0; -} - static int acpi_processor_get_lpi_info(struct acpi_processor *pr) { int ret; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 60ab50cb8930..1d3ea92a2344 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -315,6 +315,17 @@ static inline int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, } #endif +#ifdef CONFIG_ACPI_PROCESSOR_IDLE +int acpi_processor_extract_lpi_info(acpi_handle pr_handle, + struct acpi_processor_power *pr_power); +#else +static inline int acpi_processor_extract_lpi_info(acpi_handle pr_handle, + struct acpi_processor_power *pr_power) +{ + return -ENODEV; +} +#endif + #ifdef CONFIG_ACPI_HOTPLUG_CPU /* Arch dependent functions for cpu hotplug support */ int acpi_map_cpu(acpi_handle handle, phys_cpuid_t physid, u32 acpi_id, From 67fcf679c80835a6110190dfd3287a93a7df1dfb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:42:33 +0200 Subject: [PATCH 36/76] ACPI: processor: idle: Add switch for strict _LPI processing Add a "strict" argument to acpi_processor_extract_lpi_info() that, when set, will cause it to ignore _LPI states without minimum residency or wake latency instead of assuming 10 us values for these parameters. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Acked-by: Huisong Li Link: https://patch.msgid.link/3896986.MHq7AAxBmi@rafael.j.wysocki --- drivers/acpi/acpi_processor.c | 25 +++++++++++++++++++------ drivers/acpi/processor_idle.c | 2 +- include/linux/acpi.h | 6 ++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c index cdba1aee84e9..5fbab54171b7 100644 --- a/drivers/acpi/acpi_processor.c +++ b/drivers/acpi/acpi_processor.c @@ -1018,7 +1018,7 @@ static int obj_get_integer(union acpi_object *obj, u32 *value) static void process_lpi_state_package(union acpi_object *lpi_pkg, struct acpi_lpi_state *lpi_state, acpi_handle handle, - unsigned int state_idx) + unsigned int state_idx, bool strict) { union acpi_object *lpi_pkg_elem, *obj; @@ -1054,11 +1054,21 @@ static void process_lpi_state_package(union acpi_object *lpi_pkg, } if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { + if (strict) { + lpi_state_debug(handle, "No min. residency", state_idx); + return; + } + lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); lpi_state->min_residency = 10; } if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { + if (strict) { + lpi_state_debug(handle, "No wake latency", state_idx); + return; + } + lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); lpi_state->wake_latency = 10; } @@ -1090,7 +1100,8 @@ static void process_lpi_state_package(union acpi_object *lpi_pkg, } static int acpi_processor_evaluate_lpi(acpi_handle handle, - struct acpi_lpi_states_array *info) + struct acpi_lpi_states_array *info, + bool strict) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *lpi_data, *lpi_pkg; @@ -1138,7 +1149,8 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle, for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { lpi_state->index = state_idx; - process_lpi_state_package(lpi_pkg++, lpi_state++, handle, state_idx); + process_lpi_state_package(lpi_pkg++, lpi_state++, handle, + state_idx, strict); } acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); @@ -1245,7 +1257,8 @@ static unsigned int flatten_lpi_states(acpi_handle handle, } int acpi_processor_extract_lpi_info(acpi_handle pr_handle, - struct acpi_processor_power *pr_power) + struct acpi_processor_power *pr_power, + bool strict) { struct acpi_lpi_states_array info[2], *prev, *curr; acpi_handle handle = pr_handle; @@ -1259,7 +1272,7 @@ int acpi_processor_extract_lpi_info(acpi_handle pr_handle, curr = &info[0]; curr->composite_states_size = 0; - ret = acpi_processor_evaluate_lpi(handle, curr); + ret = acpi_processor_evaluate_lpi(handle, curr, strict); if (ret) return ret; @@ -1312,7 +1325,7 @@ int acpi_processor_extract_lpi_info(acpi_handle pr_handle, curr->composite_states_size = 0; - ret = acpi_processor_evaluate_lpi(handle, curr); + ret = acpi_processor_evaluate_lpi(handle, curr, strict); if (ret) break; diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 44a52148bc25..e113bcbbb882 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -867,7 +867,7 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr) if (ret == -EOPNOTSUPP) return ret; - ret = acpi_processor_extract_lpi_info(pr->handle, &pr->power); + ret = acpi_processor_extract_lpi_info(pr->handle, &pr->power, false); if (ret) return ret; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 1d3ea92a2344..9e418b23373c 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -317,10 +317,12 @@ static inline int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, #ifdef CONFIG_ACPI_PROCESSOR_IDLE int acpi_processor_extract_lpi_info(acpi_handle pr_handle, - struct acpi_processor_power *pr_power); + struct acpi_processor_power *pr_power, + bool strict); #else static inline int acpi_processor_extract_lpi_info(acpi_handle pr_handle, - struct acpi_processor_power *pr_power) + struct acpi_processor_power *pr_power, + bool strict) { return -ENODEV; } From 4d7821961f788615ef6fe0ad6d8a034579800e61 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:43:43 +0200 Subject: [PATCH 37/76] intel_idle: Prepare for adding ACPI _LPI support In preparation for adding ACPI _LPI support to intel_idle, move some code used for processing ACPI idle states information coming from _CST objects to separate functions because that code will be also used for processing idle states information coming from _LPI. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/1932965.atdPhlSkOF@rafael.j.wysocki --- drivers/idle/intel_idle.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index d74b478db280..fedaa8142121 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -1837,6 +1837,16 @@ static bool __init intel_idle_acpi_cst_extract(void) return false; } +static void __init intel_idle_complete_state_init(struct cpuidle_state *state) +{ + if (intel_idle_state_needs_timer_stop(state)) + state->flags |= CPUIDLE_FLAG_TIMER_STOP; + + state->enter = intel_idle; + state->enter_dead = intel_idle_enter_dead; + state->enter_s2idle = intel_idle_s2idle; +} + static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) { int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); @@ -1879,18 +1889,23 @@ static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) if (disabled_states_mask & BIT(cstate)) state->flags |= CPUIDLE_FLAG_OFF; - if (intel_idle_state_needs_timer_stop(state)) - state->flags |= CPUIDLE_FLAG_TIMER_STOP; - if (cx->type > ACPI_STATE_C1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) mark_tsc_unstable("TSC halts in idle"); - state->enter = intel_idle; - state->enter_dead = intel_idle_enter_dead; - state->enter_s2idle = intel_idle_s2idle; + intel_idle_complete_state_init(state); } } +static bool __init intel_idle_acpi_hint_match(unsigned int flags, u32 acpi_hint, + u32 table_hint) +{ + if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { + acpi_hint &= ~MWAIT_SUBSTATE_MASK; + table_hint &= ~MWAIT_SUBSTATE_MASK; + } + return acpi_hint == table_hint; +} + static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) { int cstate, limit; @@ -1909,14 +1924,8 @@ static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) */ for (cstate = 1; cstate < limit; cstate++) { u32 acpi_hint = acpi_state_table.states[cstate].address; - u32 table_hint = mwait_hint; - if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { - acpi_hint &= ~MWAIT_SUBSTATE_MASK; - table_hint &= ~MWAIT_SUBSTATE_MASK; - } - - if (acpi_hint == table_hint) + if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) return false; } return true; From abc5c103c66d9b8102605e63ad0ebb4cadc58995 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Jul 2026 14:44:23 +0200 Subject: [PATCH 38/76] intel_idle: Add ACPI _LPI support Allow intel_idle to use idle states information coming from ACPI _LPI objects by making it call acpi_processor_extract_lpi_info() and, if that is successful, using the list of idle states produced by that function instead of the one coming from acpi_processor_evaluate_cst(). Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2280567.Icojqenx9y@rafael.j.wysocki --- drivers/idle/intel_idle.c | 164 ++++++++++++++++++++++++++++++++------ 1 file changed, 140 insertions(+), 24 deletions(-) diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index fedaa8142121..bcc2725769c5 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -1779,6 +1779,7 @@ module_param_named(no_native, no_native, bool, 0444); MODULE_PARM_DESC(no_native, "Ignore cpu specific (native) idle states in lieu of ACPI idle states"); static struct acpi_processor_power acpi_state_table __initdata; +static bool acpi_lpi_available __initdata; /** * intel_idle_cst_usable - Check if the _CST information can be used. @@ -1803,18 +1804,37 @@ static bool __init intel_idle_cst_usable(void) return true; } -static bool __init intel_idle_acpi_cst_extract(void) +static bool __init intel_idle_acpi_extract_lpi_cstates(void) { unsigned int cpu; - if (no_acpi) { - pr_debug("Not allowed to use ACPI _CST\n"); - return false; + for_each_possible_cpu(cpu) { + struct acpi_processor *pr; + + pr = per_cpu(processors, cpu); + if (!pr) + continue; + + if (acpi_processor_extract_lpi_info(pr->handle, + &acpi_state_table, true)) + continue; + + acpi_lpi_available = true; + return true; } - for_each_possible_cpu(cpu) { - struct acpi_processor *pr = per_cpu(processors, cpu); + pr_debug("No ACPI _LPI idle states\n"); + return false; +} +static bool __init intel_idle_acpi_extract_cst_cstates(void) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) { + struct acpi_processor *pr; + + pr = per_cpu(processors, cpu); if (!pr) continue; @@ -1826,17 +1846,39 @@ static bool __init intel_idle_acpi_cst_extract(void) if (!intel_idle_cst_usable()) continue; - if (!acpi_processor_claim_cst_control()) - break; - return true; } - acpi_state_table.count = 0; pr_debug("ACPI _CST not found or not usable\n"); return false; } +static bool __init intel_idle_acpi_extract_cstates(void) +{ + if (intel_idle_acpi_extract_lpi_cstates()) + return true; + + if (intel_idle_acpi_extract_cst_cstates()) + return true; + + return false; +} + +static bool __init intel_idle_acpi_probe(void) +{ + if (no_acpi) { + pr_debug("Not allowed to use ACPI for C-states extraction\n"); + return false; + } + + if (intel_idle_acpi_extract_cstates() && + acpi_processor_claim_cst_control()) + return true; + + acpi_state_table.count = 0; + return false; +} + static void __init intel_idle_complete_state_init(struct cpuidle_state *state) { if (intel_idle_state_needs_timer_stop(state)) @@ -1847,7 +1889,53 @@ static void __init intel_idle_complete_state_init(struct cpuidle_state *state) state->enter_s2idle = intel_idle_s2idle; } -static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) +static void __init intel_idle_init_cstates_acpi_lpi(struct cpuidle_driver *drv) +{ + int index; + + for (index = 0; index < acpi_state_table.count; index++) { + struct acpi_lpi_state *lpi_state; + struct cpuidle_state *state; + + if (intel_idle_max_cstate_reached(index)) + break; + + lpi_state = &acpi_state_table.lpi_states[index]; + + state = &drv->states[drv->state_count++]; + + scnprintf(state->name, CPUIDLE_NAME_LEN, "C%d_LPI", index + 1); + strscpy(state->desc, lpi_state->desc, CPUIDLE_DESC_LEN); + state->exit_latency = lpi_state->wake_latency; + state->target_residency = lpi_state->min_residency; + state->flags = MWAIT2flg(lpi_state->address); + /* + * Assume that entering any of the idle states extracted from + * _LPI except for the first two will cause the TLB to be + * flushed and let the core call leave_mm() for them upfront + * to avoid unnecessary wakeups due to TLB shootdowns. + */ + if (index > 1) + state->flags |= CPUIDLE_FLAG_TLB_FLUSHED; + + if (disabled_states_mask & BIT(index + 1)) + state->flags |= CPUIDLE_FLAG_OFF; + + intel_idle_complete_state_init(state); + + pr_info("%s: MWAIT hint 0x%x\n", state->name, flg2MWAIT(state->flags)); + } + + /* + * Assume the first idle state in the table to be C1 and if any deeper + * idle states are exposed while X86_FEATURE_NONSTOP_TSC is unset, mark + * the TSC as unstable. + */ + if (index > 1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) + mark_tsc_unstable("TSC halts in idle"); +} + +static void __init intel_idle_init_cstates_acpi_cst(struct cpuidle_driver *drv) { int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); @@ -1896,6 +1984,14 @@ static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) } } +static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) +{ + if (acpi_lpi_available) + intel_idle_init_cstates_acpi_lpi(drv); + else + intel_idle_init_cstates_acpi_cst(drv); +} + static bool __init intel_idle_acpi_hint_match(unsigned int flags, u32 acpi_hint, u32 table_hint) { @@ -1906,18 +2002,23 @@ static bool __init intel_idle_acpi_hint_match(unsigned int flags, u32 acpi_hint, return acpi_hint == table_hint; } -static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) +static bool __init intel_idle_off_by_default_lpi(unsigned int flags, u32 mwait_hint) { - int cstate, limit; + int index; - /* - * If there are no _CST C-states, do not disable any C-states by - * default. - */ - if (!acpi_state_table.count) - return false; + for (index = 0; index < acpi_state_table.count; index++) { + u32 acpi_hint = acpi_state_table.lpi_states[index].address; + + if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) + return false; + } + return true; +} + +static bool __init intel_idle_off_by_default_cst(unsigned int flags, u32 mwait_hint) +{ + int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); - limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); /* * If limit > 0, intel_idle_cst_usable() has returned 'true', so all of * the interesting states are ACPI_CSTATE_FFH. @@ -1931,6 +2032,21 @@ static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) return true; } +static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) +{ + /* + * If there is no C-states information in the ACPI tables, do not + * disable any C-states by default. + */ + if (!acpi_state_table.count) + return false; + + if (acpi_lpi_available) + return intel_idle_off_by_default_lpi(flags, mwait_hint); + + return intel_idle_off_by_default_cst(flags, mwait_hint); +} + static inline bool ignore_native(void) { return no_native && !no_acpi; @@ -1938,7 +2054,7 @@ static inline bool ignore_native(void) #else /* !CONFIG_ACPI_PROCESSOR_CSTATE */ #define force_use_acpi (false) -static inline bool intel_idle_acpi_cst_extract(void) { return false; } +static inline bool intel_idle_acpi_probe(void) { return false; } static inline void intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) { } static inline bool intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) { @@ -2750,7 +2866,7 @@ static int __init intel_idle_init(void) if (icpu) { if (icpu->state_table) cpuidle_state_table = icpu->state_table; - else if (!intel_idle_acpi_cst_extract()) + else if (!intel_idle_acpi_probe()) return -ENODEV; auto_demotion_disable_flags = icpu->auto_demotion_disable_flags; @@ -2759,8 +2875,8 @@ static int __init intel_idle_init(void) if (icpu->c1_demotion_supported) c1_demotion_supported = true; if (icpu->use_acpi || force_use_acpi) - intel_idle_acpi_cst_extract(); - } else if (!intel_idle_acpi_cst_extract()) { + intel_idle_acpi_probe(); + } else if (!intel_idle_acpi_probe()) { return -ENODEV; } From eeed6071ceda7104e3397dca24cfd3dc73948150 Mon Sep 17 00:00:00 2001 From: Jeremy Linton Date: Mon, 20 Jul 2026 13:14:54 -0500 Subject: [PATCH 39/76] cpupower: Add libm to cpupower for generic CPPC view The patch ("cpupower: Add generic CPPC performance display") uses roundf() but didn't include libm explicitly. This results in build breaks in environments where its not automatically inlined. Add libm to the cpupower makefile to correct this. Fixes: 68f34fad760b ("cpupower: Add generic CPPC performance display") Signed-off-by: Jeremy Linton Signed-off-by: Shuah Khan --- tools/power/cpupower/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/power/cpupower/Makefile b/tools/power/cpupower/Makefile index cd8b7315fe74..ab428e336d87 100644 --- a/tools/power/cpupower/Makefile +++ b/tools/power/cpupower/Makefile @@ -236,9 +236,9 @@ $(OUTPUT)%.o: %.c $(OUTPUT)cpupower: $(UTIL_OBJS) $(OUTPUT)$(LIBCPUPOWER) $(ECHO) " CC " $@ ifeq ($(strip $(STATIC)),true) - $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lrt -lpci -L$(OUTPUT) -o $@ + $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lm -lrt -lpci -L$(OUTPUT) -o $@ else - $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lcpupower -lrt -lpci -L$(OUTPUT) -o $@ + $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lm -lcpupower -lrt -lpci -L$(OUTPUT) -o $@ endif $(QUIET) $(STRIPCMD) $@ From 20919d30f4ec5c3d7356bb4df5a8d2d03709d04d Mon Sep 17 00:00:00 2001 From: Adi Nata Date: Wed, 10 Jun 2026 07:16:26 +0800 Subject: [PATCH 40/76] PM: hibernate: Remove kernel-doc markings from helper descriptions Several helpers in snapshot.c are introduced with kernel-doc (/**) comment blocks but do not describe their parameters with @param tags.This emits warnings when building with extra warnings enabled (make W=1), for example: kernel/power/snapshot.c:469: warning: Function parameter or member 'zone' not described in 'add_rtree_block' kernel/power/snapshot.c:469: warning: Function parameter or member 'gfp_mask' not described in 'add_rtree_block' kernel/power/snapshot.c:469: warning: Function parameter or member 'safe_needed' not described in 'add_rtree_block' kernel/power/snapshot.c:469: warning: Function parameter or member 'ca' not described in 'add_rtree_block' These are file-local implementation details, not part of the exported kernel API documented under Documentation/. Replace the kernel-doc markers with plain block comments for the affected functions. Properly documented symbols such as alloc_rtree_node(), snapshot_read_next() and snapshot_write_next() remain unchanged. Signed-off-by: Adi Nata [ rjw: Subject rewrite ] Link: https://patch.msgid.link/20260609231626.38839-1-adinata.softwareengineer@gmail.com Signed-off-by: Rafael J. Wysocki --- kernel/power/snapshot.c | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index d933b5b2c05d..38c0a9d31d1a 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -458,7 +458,7 @@ static struct rtree_node *alloc_rtree_node(gfp_t gfp_mask, int safe_needed, return node; } -/** +/* * add_rtree_block - Add a new leave node to the radix tree. * * The leave nodes need to be allocated in order to keep the leaves @@ -528,7 +528,7 @@ static int add_rtree_block(struct mem_zone_bm_rtree *zone, gfp_t gfp_mask, static void free_zone_bm_rtree(struct mem_zone_bm_rtree *zone, int clear_nosave_free); -/** +/* * create_zone_bm_rtree - Create a radix tree for one zone. * * Allocated the mem_zone_bm_rtree structure and initializes it. @@ -566,7 +566,7 @@ static struct mem_zone_bm_rtree *create_zone_bm_rtree(gfp_t gfp_mask, return zone; } -/** +/* * free_zone_bm_rtree - Free the memory of the radix tree. * * Free all node pages of the radix tree. The mem_zone_bm_rtree @@ -678,7 +678,7 @@ static int create_mem_extents(struct list_head *list, gfp_t gfp_mask) return 0; } -/** +/* * memory_bm_create - Allocate memory for a memory bitmap. */ static int memory_bm_create(struct memory_bitmap *bm, gfp_t gfp_mask, @@ -720,7 +720,7 @@ static int memory_bm_create(struct memory_bitmap *bm, gfp_t gfp_mask, goto Exit; } -/** +/* * memory_bm_free - Free memory occupied by the memory bitmap. * @bm: Memory bitmap. */ @@ -736,7 +736,7 @@ static void memory_bm_free(struct memory_bitmap *bm, int clear_nosave_free) INIT_LIST_HEAD(&bm->zones); } -/** +/* * memory_bm_find_bit - Find the bit for a given PFN in a memory bitmap. * * Find the bit in memory bitmap @bm that corresponds to the given PFN. @@ -988,7 +988,7 @@ static void memory_bm_recycle(struct memory_bitmap *bm) } } -/** +/* * register_nosave_region - Register a region of unsaveable memory. * * Register a range of page frames the contents of which should not be saved @@ -1305,7 +1305,7 @@ static unsigned int count_free_highmem_pages(void) return cnt; } -/** +/* * saveable_highmem_page - Check if a highmem page is saveable. * * Determine whether a highmem page should be included in a hibernation image. @@ -1362,7 +1362,7 @@ static unsigned int count_highmem_pages(void) } #endif /* CONFIG_HIGHMEM */ -/** +/* * saveable_page - Check if the given page is saveable. * * Determine whether a non-highmem page should be included in a hibernation @@ -1440,7 +1440,7 @@ static inline bool do_copy_page(long *dst, long *src) return !z; } -/** +/* * safe_copy_page - Copy a page in a safe way. * * Check if the page we are going to copy is marked as present in the kernel @@ -1687,7 +1687,7 @@ static unsigned long preallocate_image_highmem(unsigned long nr_pages) return preallocate_image_pages(nr_pages, GFP_IMAGE | __GFP_HIGHMEM); } -/** +/* * __fraction - Compute (an approximation of) x * (multiplier / base). */ static unsigned long __fraction(u64 x, u64 multiplier, u64 base) @@ -1982,7 +1982,7 @@ int hibernate_preallocate_memory(void) } #ifdef CONFIG_HIGHMEM -/** +/* * count_pages_for_highmem - Count non-highmem pages needed for copying highmem. * * Compute the number of non-highmem pages that will be necessary for creating @@ -2003,7 +2003,7 @@ static unsigned int count_pages_for_highmem(unsigned int nr_highmem) static unsigned int count_pages_for_highmem(unsigned int nr_highmem) { return 0; } #endif /* CONFIG_HIGHMEM */ -/** +/* * enough_free_mem - Check if there is enough free memory for the image. */ static int enough_free_mem(unsigned int nr_pages, unsigned int nr_highmem) @@ -2023,7 +2023,7 @@ static int enough_free_mem(unsigned int nr_pages, unsigned int nr_highmem) } #ifdef CONFIG_HIGHMEM -/** +/* * get_highmem_buffer - Allocate a buffer for highmem pages. * * If there are some highmem pages in the hibernation image, we may need a @@ -2035,7 +2035,7 @@ static inline int get_highmem_buffer(int safe_needed) return buffer ? 0 : -ENOMEM; } -/** +/* * alloc_highmem_pages - Allocate some highmem pages for the image. * * Try to allocate as many pages as needed, but if the number of free highmem @@ -2065,7 +2065,7 @@ static inline unsigned int alloc_highmem_pages(struct memory_bitmap *bm, unsigned int n) { return 0; } #endif /* CONFIG_HIGHMEM */ -/** +/* * swsusp_alloc - Allocate memory for hibernation image. * * We first try to allocate as many highmem pages as there are @@ -2292,7 +2292,7 @@ static void duplicate_memory_bitmap(struct memory_bitmap *dst, } } -/** +/* * mark_unsafe_pages - Mark pages that were used before hibernation. * * Mark the pages that cannot be used for storing the image during restoration, @@ -2330,7 +2330,7 @@ static int check_header(struct swsusp_info *info) return 0; } -/** +/* * load_header - Check the image header and copy the data from it. */ static int load_header(struct swsusp_info *info) @@ -2483,7 +2483,7 @@ static int prepare_highmem_image(struct memory_bitmap *bm, static struct page *last_highmem_page; -/** +/* * get_highmem_page_buffer - Prepare a buffer to store a highmem image page. * * For a given highmem image page get a buffer that suspend_write_next() should @@ -2706,7 +2706,7 @@ static int prepare_image(struct memory_bitmap *new_bm, struct memory_bitmap *bm, return error; } -/** +/* * get_buffer - Get the address to store the next image data page. * * Get the address that snapshot_write_next() should return to its caller to @@ -2843,7 +2843,7 @@ int snapshot_write_next(struct snapshot_handle *handle) return PAGE_SIZE; } -/** +/* * snapshot_write_finalize - Complete the loading of a hibernation image. * * Must be called after the last call to snapshot_write_next() in case the last From a343c6f15cc94a93aa2d51674d8ca10b36e750bc Mon Sep 17 00:00:00 2001 From: Zhongqiu Han Date: Thu, 16 Jul 2026 21:15:46 +0800 Subject: [PATCH 41/76] cpufreq: schedutil: Replace sprintf() with sysfs_emit() in sysfs show Use sysfs_emit() instead of sprintf() in rate_limit_us_show(). sysfs_emit() is the preferred API for sysfs output as it provides PAGE_SIZE bounds checking and ensures proper sysfs formatting. No functional change intended. Signed-off-by: Zhongqiu Han Link: https://patch.msgid.link/20260716131546.1159644-1-zhongqiu.han@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- kernel/sched/cpufreq_schedutil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c index dc0835ceee98..96651873345e 100644 --- a/kernel/sched/cpufreq_schedutil.c +++ b/kernel/sched/cpufreq_schedutil.c @@ -592,7 +592,7 @@ static ssize_t rate_limit_us_show(struct gov_attr_set *attr_set, char *buf) { struct sugov_tunables *tunables = to_sugov_tunables(attr_set); - return sprintf(buf, "%u\n", tunables->rate_limit_us); + return sysfs_emit(buf, "%u\n", tunables->rate_limit_us); } static ssize_t From 45a7c1077ba759ee59d0549a66ed86e35815e18e Mon Sep 17 00:00:00 2001 From: Ronan Marchal Date: Mon, 15 Jun 2026 21:18:32 +0200 Subject: [PATCH 42/76] PM: hibernate: Use %pe to print error pointer values Use %pe format specifier instead of %ld with PTR_ERR() to print error pointers as a symbolic error name (e.g. -ENOMEM) instead of a raw integer value. Signed-off-by: Ronan Marchal [ rjw: Subject rewrite ] Link: https://patch.msgid.link/20260615191832.75923-1-ronanmarchal29@gmail.com Signed-off-by: Rafael J. Wysocki --- kernel/power/swap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/power/swap.c b/kernel/power/swap.c index c626e9dc3c1c..67570f74566c 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -739,7 +739,7 @@ static int save_compressed_image(struct swap_map_handle *handle, data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); if (IS_ERR_OR_NULL(data[thr].cc)) { - pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); + pr_err("Could not allocate comp stream %pe\n", data[thr].cc); ret = -EFAULT; goto out_clean; } @@ -1243,7 +1243,7 @@ static int load_compressed_image(struct swap_map_handle *handle, data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); if (IS_ERR_OR_NULL(data[thr].cc)) { - pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); + pr_err("Could not allocate comp stream %pe\n", data[thr].cc); ret = -EFAULT; goto out_clean; } From 87bc1e34986d906129ed387e74fdb13de9c5fa89 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 14:27:47 +0200 Subject: [PATCH 43/76] tools/power: intel_pstate_tracer: avoid optional imports for help intel_pstate_tracer imports Gnuplot and numpy before parsing command-line options. As a result, even "-h" fails if those optional runtime modules are not installed. Move the imports to the paths that need them. This lets the help and invalid-argument paths describe usage without requiring plotting/data dependencies. While there, fix a typo in the help text and matching comments. Signed-off-by: Yousef Alhouseen Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260624122747.5418-1-alhouseenyousef@gmail.com Signed-off-by: Rafael J. Wysocki --- .../intel_pstate_tracer/intel_pstate_tracer.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py index 38cfbdcdedb7..64001bc80f68 100755 --- a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py +++ b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py @@ -32,8 +32,6 @@ import re import signal import sys import getopt -import Gnuplot -from numpy import * from decimal import * __author__ = "Srinivas Pandruvada" @@ -88,8 +86,8 @@ def print_help(driver_name): print(' kbytes: Kilo bytes of memory per CPU to allocate to the trace buffer. Default: 10240') print(' Output:') print(' If not already present, creates a "results/test_name" folder in the current working directory with:') - print(' cpu.csv - comma seperated values file with trace contents and some additional calculations.') - print(' cpu???.csv - comma seperated values file for CPU number ???.') + print(' cpu.csv - comma separated values file with trace contents and some additional calculations.') + print(' cpu???.csv - comma separated values file for CPU number ???.') print(' *.png - a variety of PNG format plot files created from the trace contents and the additional calculations.') print(' Notes:') print(' Avoid the use of _ (underscore) in test names, because in gnuplot it is a subscript directive.') @@ -295,6 +293,8 @@ def common_all_gnuplot_settings(output_png): def common_gnuplot_settings(): """ common gnuplot settings. """ + import Gnuplot + g_plot = Gnuplot.Gnuplot(persist=1) # The following line is for rigor only. It seems to be assumed for .csv files g_plot('set datafile separator \",\"') @@ -343,7 +343,7 @@ def store_csv(cpu_int, time_pre_dec, time_post_dec, core_busy, scaled, _from, _t graph_data_present = True; def split_csv(current_max_cpu, cpu_mask): - """ seperate the all csv file into per CPU csv files. """ + """ separate the main csv file into per CPU csv files. """ if os.path.exists('cpu.csv'): for index in range(0, current_max_cpu + 1): @@ -482,7 +482,7 @@ def read_trace_data(filename, cpu_mask): if cpu_int > current_max_cpu: current_max_cpu = cpu_int # End of for each trace line loop -# Now seperate the main overall csv file into per CPU csv files. +# Now separate the main overall csv file into per CPU csv files. split_csv(current_max_cpu, cpu_mask) def signal_handler(signal, frame): @@ -508,8 +508,6 @@ if __name__ == "__main__": valid1 = False valid2 = False - cpu_mask = zeros((MAX_CPUS,), dtype=int) - try: opts, args = getopt.getopt(sys.argv[1:],"ht:i:c:n:m:",["help","trace_file=","interval=","cpu=","name=","memory="]) except getopt.GetoptError: @@ -538,6 +536,10 @@ if __name__ == "__main__": print_help('intel_pstate') sys.exit() + from numpy import zeros + + cpu_mask = zeros((MAX_CPUS,), dtype=int) + if cpu_list: for p in re.split("[,]", cpu_list): if int(p) < MAX_CPUS : From 8d31bb1451643f328db0cea0e21e63ef54b4faf2 Mon Sep 17 00:00:00 2001 From: Qianheng Peng Date: Thu, 16 Jul 2026 16:51:39 +0800 Subject: [PATCH 44/76] cpufreq: amd-pstate-ut: Skip tests when amd-pstate driver is not active The crash issue may occur when modprobe amd_pstate_ut on intel platform. amd_pstate_ut: 1 amd_pstate_ut_acpi_cpc_valid success! amd_pstate_ut: 2 amd_pstate_ut_check_enabled success! BUG: kernel NULL pointer dereference, address: 0000000000000080 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: 0000 [#1] SMP NOPTI CPU: 0 PID: 20300 Comm: modprobe Kdump: loaded Tainted: G O 6.6.0-0010.rc1.ctl4.x86_64 #1 Hardware name: FiberHome R2200 V5/Xeon Boards, BIOS 3.1a 02/24/2020 RIP: 0010:amd_pstate_ut_check_perf+0x141/0x280 [amd_pstate_ut] Call Trace: amd_pstate_ut_init+0x1b/0xff0 [amd_pstate_ut] ? __pfx_amd_pstate_ut_init+0x10/0x10 [amd_pstate_ut] do_one_initcall+0x42/0x2e0 ? kmalloc_trace+0x26/0x90 do_init_module+0x60/0x240 __se_sys_init_module+0x185/0x1c0 do_syscall_64+0x62/0x190 entry_SYSCALL_64_after_hwframe+0x76/0x7e Add state detection to amd pstate driver to prevent amd_pstate_ut driver from testing on non-AMD platforms. Fixes: 14eb1c96e3a3 ("cpufreq: amd-pstate: Add test module for amd-pstate driver") Suggested-by: Li Xiong Suggested-by: Xibo Wang Signed-off-by: Qianheng Peng Reviewed-by: Zhongqiu Han Link: https://lore.kernel.org/r/1784191899-28957-1-git-send-email-pengqh1@chinatelecom.cn (ML: adjust title) Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate-ut.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c index 735b29f76438..2142838ad6cc 100644 --- a/drivers/cpufreq/amd-pstate-ut.c +++ b/drivers/cpufreq/amd-pstate-ut.c @@ -560,6 +560,11 @@ static int amd_pstate_ut_check_freq_attrs(u32 index) static int __init amd_pstate_ut_init(void) { u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); + enum amd_pstate_mode mode = amd_pstate_get_status(); + + /* don't test if no running amd-pstate driver */ + if (mode == AMD_PSTATE_UNDEFINED || mode == AMD_PSTATE_DISABLE) + return -EOPNOTSUPP; for (i = 0; i < arr_size; i++) { int ret; From 57476909c3000a04e84a1d6018d63ba1b2aa20ab Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Tue, 9 Jun 2026 09:29:03 +0200 Subject: [PATCH 45/76] cpufreq/amd-pstate: Fix EPP return type and handle errors during initialization Currently, the EPP getter helper functions (msr_get_epp, shmem_get_epp, and the static call wrapper amd_pstate_get_epp) return u8 or s16. This makes it difficult to correctly propagate negative error values returned by the underlying MSR read or CPPC helpers (such as rdmsrq_on_cpu or cppc_get_epp_perf). Modify the return type of these functions to int, allowing them to return negative error codes properly. Additionally, in amd_pstate_epp_cpu_init(), fetch the firmware-programmed default EPP value and validate it before assigning it to the EPP variables. If amd_pstate_get_epp() returns an error code, propagate the error and abort the CPU initialization to prevent subsequent configuration failures. Fixes: 555bbe67a622 ("cpufreq/amd-pstate: Convert all perf values to u8") Assisted-by: Antigravity:gemini-3.5-flash Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Signed-off-by: Marco Scardovi Reviewed-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260609073042.81275-2-scardracs@disroot.org Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 3a6b4b224a66..477c17398fc2 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -199,7 +199,7 @@ static inline int get_mode_idx_from_str(const char *str, size_t size) static DEFINE_MUTEX(amd_pstate_driver_lock); -static u8 msr_get_epp(struct amd_cpudata *cpudata) +static int msr_get_epp(struct amd_cpudata *cpudata) { u64 value; int ret; @@ -215,12 +215,12 @@ static u8 msr_get_epp(struct amd_cpudata *cpudata) DEFINE_STATIC_CALL(amd_pstate_get_epp, msr_get_epp); -static inline s16 amd_pstate_get_epp(struct amd_cpudata *cpudata) +static inline int amd_pstate_get_epp(struct amd_cpudata *cpudata) { return static_call(amd_pstate_get_epp)(cpudata); } -static u8 shmem_get_epp(struct amd_cpudata *cpudata) +static int shmem_get_epp(struct amd_cpudata *cpudata) { u64 epp; int ret; @@ -1876,6 +1876,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) struct amd_cpudata *cpudata; union perf_cached perf; struct device *dev; + int default_epp; int ret; /* @@ -1924,6 +1925,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) policy->boost_supported = READ_ONCE(cpudata->boost_supported); + /* Fetch the firmware programmed default EPP value */ + default_epp = amd_pstate_get_epp(cpudata); + if (default_epp < 0) { + ret = default_epp; + goto free_cpudata1; + } + /* * Set the policy to provide a valid fallback value in case * the default cpufreq governor is neither powersave nor performance. @@ -1931,7 +1939,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) if (amd_pstate_acpi_pm_profile_server() || amd_pstate_acpi_pm_profile_undefined()) { policy->policy = CPUFREQ_POLICY_PERFORMANCE; - cpudata->epp_default_ac = cpudata->epp_default_dc = amd_pstate_get_epp(cpudata); + cpudata->epp_default_ac = cpudata->epp_default_dc = default_epp; cpudata->current_profile = PLATFORM_PROFILE_PERFORMANCE; } else { policy->policy = CPUFREQ_POLICY_POWERSAVE; From 9dfd13f80c856eab79130403a13fa3b83199346b Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Tue, 9 Jun 2026 09:29:04 +0200 Subject: [PATCH 46/76] cpufreq/amd-pstate: Toggle auto_sel in active mode on shared memory systems On shared memory systems, the EPP configuration path (handled via cppc_set_epp_perf()) is responsible for toggling on the CPPC autonomous selection register (auto_sel). Currently, shmem_init_perf() returns early without doing any of the auto_sel configuration steps if cppc_state is AMD_PSTATE_ACTIVE. This skips enabling auto_sel, leaving the CPU in non-autonomous mode. Remove the early return check in shmem_init_perf() when cppc_state is AMD_PSTATE_ACTIVE. Toggling auto_sel is necessary for the active mode on shared memory systems to function based on the ACPI spec for CPPC v2 and below. Fixes: 2dd6d0ebf740 ("cpufreq: amd-pstate: Add guided autonomous mode") Assisted-by: Antigravity:gemini-3.5-flash Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Signed-off-by: Marco Scardovi Reviewed-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260609073042.81275-3-scardracs@disroot.org Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 477c17398fc2..3c2995686a50 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -526,9 +526,6 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) WRITE_ONCE(cpudata->perf, perf); WRITE_ONCE(cpudata->prefcore_ranking, cppc_perf.highest_perf); - if (cppc_state == AMD_PSTATE_ACTIVE) - return 0; - ret = cppc_get_auto_sel(cpudata->cpu, &auto_sel); if (ret) { pr_warn("failed to get auto_sel, ret: %d\n", ret); From d0f4c1c8cbffcfd5db683bd3f54396bfec12e9f5 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Tue, 9 Jun 2026 09:29:05 +0200 Subject: [PATCH 47/76] cpufreq/amd-pstate: Cache the firmware programmed EPP value At CPU EPP initialization, the private cpudata structure is allocated via kzalloc, which means cpudata->cppc_req_cached is initialized to 0. This makes the default cached EPP value 0 (AMD_CPPC_EPP_PERFORMANCE). When initializing a system that defaults to performance EPP, the driver attempts to configure the EPP via amd_pstate_set_epp(). Because the requested EPP (0) matches the uninitialized cached value (0), the cache guard check triggers, and the driver skips writing to the hardware. Cache the firmware-programmed default EPP value in cppc_req_cached during CPU EPP initialization. This saves on an unnecessary reprogramming later when the EPP is first set. Assisted-by: Antigravity:gemini-3.5-flash Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Signed-off-by: Marco Scardovi Link: https://lore.kernel.org/r/20260609073042.81275-4-scardracs@disroot.org Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 3c2995686a50..19d9e574ce5a 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1922,12 +1922,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) policy->boost_supported = READ_ONCE(cpudata->boost_supported); - /* Fetch the firmware programmed default EPP value */ + /* Cache the firmware programmed EPP */ default_epp = amd_pstate_get_epp(cpudata); if (default_epp < 0) { ret = default_epp; goto free_cpudata1; } + FIELD_MODIFY(AMD_CPPC_EPP_PERF_MASK, &cpudata->cppc_req_cached, default_epp); /* * Set the policy to provide a valid fallback value in case From 39c0cf62fc7851a17782e7efe8dfb2948739c681 Mon Sep 17 00:00:00 2001 From: EDAMAMEX Date: Wed, 20 May 2026 16:02:11 +0900 Subject: [PATCH 48/76] cpufreq/amd-pstate: handle missing policy in dynamic EPP callbacks cpufreq_cpu_get() returns NULL when no cpufreq policy is associated with the requested CPU, for example because the CPU is offline or the policy has already been torn down. Both amd_pstate_power_supply_notifier() and amd_pstate_profile_set() acquire a policy via cpufreq_cpu_get() and then pass that pointer to amd_pstate_get_balanced_epp() and amd_pstate_set_epp(), which dereference it unconditionally. A racing CPU hotplug or driver teardown can therefore lead to a NULL pointer dereference on either of these dynamic EPP paths. The third cpufreq_cpu_get() caller in this file, amd_pstate_verify(), already handles the NULL case. Bring the two new callers in line with that pattern: return NOTIFY_OK from the power-supply notifier (matching the other "nothing to do" exits) and -ENODEV from amd_pstate_profile_set() (the usual cpufreq error for a missing CPU policy). Found by code inspection; not tested on hardware. Fixes: e30ca6dd5345 ("cpufreq/amd-pstate: Add dynamic energy performance preference") Fixes: 798c47593cca ("cpufreq/amd-pstate: Add support for platform profile class") Signed-off-by: EDAMAMEX Link: https://lore.kernel.org/r/20260520070211.2753183-1-edame8080@gmail.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 19d9e574ce5a..b357189f4f8e 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1170,6 +1170,9 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, if (cpudata->current_profile != PLATFORM_PROFILE_BALANCED) return 0; + if (!policy) + return NOTIFY_OK; + epp = amd_pstate_get_balanced_epp(policy); ret = amd_pstate_set_epp(policy, epp); @@ -1205,6 +1208,9 @@ static int amd_pstate_profile_set(struct device *dev, struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); int ret; + if (!policy) + return -ENODEV; + switch (profile) { case PLATFORM_PROFILE_LOW_POWER: ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); From c7aa1af7b327bb9af163fbd7b83ade6ac07845fd Mon Sep 17 00:00:00 2001 From: Jinseok Kim Date: Mon, 6 Jul 2026 23:38:53 +0900 Subject: [PATCH 49/76] selftests/cpufreq: Remove unused local variables from switch_show_governor() switch_show_governor() assigns the current governor and frequency to local variables before switching governors. However, these variables are never referenced afterwards. The function does not restore the previous governor or use the saved frequency, as backup_governor() and restore_governor() already handle state preservation elsewhere. Signed-off-by: Jinseok Kim Acked-by: Viresh Kumar Link: https://patch.msgid.link/20260706143857.3306-1-always.starving0@gmail.com Signed-off-by: Rafael J. Wysocki --- tools/testing/selftests/cpufreq/governor.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/testing/selftests/cpufreq/governor.sh b/tools/testing/selftests/cpufreq/governor.sh index fe37df79c087..212ef1cf43d5 100755 --- a/tools/testing/selftests/cpufreq/governor.sh +++ b/tools/testing/selftests/cpufreq/governor.sh @@ -100,11 +100,6 @@ switch_governor() # $1: policy, $2: governor switch_show_governor() { - cur_gov=find_current_governor - if [ $cur_gov == "userspace" ]; then - cur_freq=find_current_freq - fi - # switch governor __switch_governor $1 $2 From 60e32ff9ed850db22d16ff91a17e99a9efbca8cf Mon Sep 17 00:00:00 2001 From: Jinseok Kim Date: Mon, 6 Jul 2026 23:38:54 +0900 Subject: [PATCH 50/76] selftests/cpufreq: Remove unnecessary sudo from quick_shuffle() The cpufreq selftests are always executed through main.sh, which verifies that the test is run as root before dispatching any test case. Therefore, invoking sudo inside quick_shuffle() is redundant and may cause failures in environments where sudo is unavailable. Signed-off-by: Jinseok Kim Acked-by: Viresh Kumar Link: https://patch.msgid.link/20260706143857.3306-2-always.starving0@gmail.com Signed-off-by: Rafael J. Wysocki --- tools/testing/selftests/cpufreq/special-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/cpufreq/special-tests.sh b/tools/testing/selftests/cpufreq/special-tests.sh index 8d40505dc468..f45eb525f3b1 100755 --- a/tools/testing/selftests/cpufreq/special-tests.sh +++ b/tools/testing/selftests/cpufreq/special-tests.sh @@ -65,8 +65,8 @@ quick_shuffle() # this is called concurrently from governor_race for I in `seq 1000` do - echo ondemand | sudo tee $CPUFREQROOT/policy*/scaling_governor & - echo userspace | sudo tee $CPUFREQROOT/policy*/scaling_governor & + echo ondemand | tee $CPUFREQROOT/policy*/scaling_governor & + echo userspace | tee $CPUFREQROOT/policy*/scaling_governor & done } From 4763f0db60a538c29d882fb6ab9e9371fbeb697c Mon Sep 17 00:00:00 2001 From: Yiwei Lin Date: Wed, 8 Jul 2026 00:36:47 +0800 Subject: [PATCH 51/76] kselftest: cpufreq: Backup and restore governor for sptests After executing cpufreq sptest, the system governor will be overwritten with the governor switched during the test. Restore this setting to maintain consistency before and after the test. Signed-off-by: Yiwei Lin Acked-by: Viresh Kumar Link: https://patch.msgid.link/20260707163647.6646-1-s921975628@gmail.com Signed-off-by: Rafael J. Wysocki --- tools/testing/selftests/cpufreq/governor.sh | 21 +++++++++++++++++++ .../selftests/cpufreq/special-tests.sh | 14 +++++++++++++ 2 files changed, 35 insertions(+) diff --git a/tools/testing/selftests/cpufreq/governor.sh b/tools/testing/selftests/cpufreq/governor.sh index 212ef1cf43d5..cf59e63f8e14 100755 --- a/tools/testing/selftests/cpufreq/governor.sh +++ b/tools/testing/selftests/cpufreq/governor.sh @@ -16,6 +16,12 @@ source cpufreq.sh CUR_GOV= CUR_FREQ= +# Per-policy backup, keyed by policy so multiple policies can be saved at once +# (backup_governor/restore_governor also keep CUR_GOV/CUR_FREQ for callers that +# read them directly). +declare -A SAVED_GOVERNORS +declare -A SAVED_FREQS + # Find governor's directory path # $1: policy, $2: governor find_gov_directory() @@ -39,11 +45,13 @@ find_current_governor() backup_governor() { CUR_GOV=$(find_current_governor $1) + SAVED_GOVERNORS[$1]=$CUR_GOV printf "Governor backup done for $1: $CUR_GOV\n" if [ $CUR_GOV == "userspace" ]; then CUR_FREQ=$(find_current_freq $1) + SAVED_FREQS[$1]=$CUR_FREQ printf "Governor frequency backup done for $1: $CUR_FREQ\n" fi @@ -53,11 +61,13 @@ backup_governor() # $1: policy restore_governor() { + CUR_GOV=${SAVED_GOVERNORS[$1]} __switch_governor $1 $CUR_GOV printf "Governor restored for $1 to $CUR_GOV\n" if [ $CUR_GOV == "userspace" ]; then + CUR_FREQ=${SAVED_FREQS[$1]} set_cpu_frequency $1 $CUR_FREQ printf "Governor frequency restored for $1: $CUR_FREQ\n" fi @@ -65,6 +75,17 @@ restore_governor() printf "\n" } +# Save/restore governors for every policy at once +save_all_governors() +{ + for_each_policy backup_governor +} + +restore_all_governors() +{ + for_each_policy restore_governor +} + # param: # $1: policy, $2: governor __switch_governor() diff --git a/tools/testing/selftests/cpufreq/special-tests.sh b/tools/testing/selftests/cpufreq/special-tests.sh index f45eb525f3b1..e87ed7c8e5e5 100755 --- a/tools/testing/selftests/cpufreq/special-tests.sh +++ b/tools/testing/selftests/cpufreq/special-tests.sh @@ -40,7 +40,9 @@ simple_lockdep() { printf "** Test: Running ${FUNCNAME[0]} **\n" + save_all_governors for_each_policy __simple_lockdep + restore_all_governors } # Test 2 @@ -56,7 +58,10 @@ concurrent_lockdep() { printf "** Test: Running ${FUNCNAME[0]} **\n" + save_all_governors for_each_policy_concurrent __concurrent_lockdep + wait + restore_all_governors } # Test 3 @@ -68,17 +73,23 @@ quick_shuffle() echo ondemand | tee $CPUFREQROOT/policy*/scaling_governor & echo userspace | tee $CPUFREQROOT/policy*/scaling_governor & done + wait } governor_race() { printf "** Test: Running ${FUNCNAME[0]} **\n" + save_all_governors + # run 8 concurrent instances for I in `seq 8` do quick_shuffle & done + wait + + restore_all_governors } # Test 4 @@ -112,5 +123,8 @@ hotplug_with_updates_cpu() hotplug_with_updates() { + save_all_governors for_each_non_boot_cpu hotplug_with_updates_cpu + wait + restore_all_governors } From 391b4b1d5476a39058bdd24d9c69430386f79659 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Mon, 20 Jul 2026 09:25:10 +0200 Subject: [PATCH 52/76] cpufreq: apple-soc: Calculate frequency as a 64-bit value The current frequency calculation is done in 32 bit, causing problems if run on a future SoC that can boost higher than 4.2GHz. Ideally, we should use a true u64 instead of unsigned long and "knowning" that this only runs on 64 bit machines, but the core code uses ulong everywhere, so this should be good enough. Signed-off-by: Sasha Finkelstein Reviewed-by: Joshua Peisach Reviewed-by: Zhongqiu Han Reviewed-by: Janne Grunau Signed-off-by: Viresh Kumar --- drivers/cpufreq/apple-soc-cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c index 3f64f266e695..7c50ce74f911 100644 --- a/drivers/cpufreq/apple-soc-cpufreq.c +++ b/drivers/cpufreq/apple-soc-cpufreq.c @@ -280,7 +280,7 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) /* Get OPP levels (p-state indexes) and stash them in driver_data */ for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { - unsigned long rate = freq_table[i].frequency * 1000 + 999; + unsigned long rate = freq_table[i].frequency * 1000UL + 999; struct dev_pm_opp *opp = dev_pm_opp_find_freq_floor(cpu_dev, &rate); if (IS_ERR(opp)) { From c0139e2b2c047d054271f484a1765d5edccd816a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 22 Jul 2026 14:53:31 +0200 Subject: [PATCH 53/76] intel_idle: Update documentation after adding ACPI _LPI support After adding ACPI _LPI support to intel_idle, update its admin-guide documentation to cover the changed behavior. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/12962673.O9o76ZdvQC@rafael.j.wysocki --- Documentation/admin-guide/pm/intel_idle.rst | 54 ++++++++++++--------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/Documentation/admin-guide/pm/intel_idle.rst b/Documentation/admin-guide/pm/intel_idle.rst index 188d52cd26e8..996e5bd83c42 100644 --- a/Documentation/admin-guide/pm/intel_idle.rst +++ b/Documentation/admin-guide/pm/intel_idle.rst @@ -87,17 +87,22 @@ tables with any processor model recognized by it; see `below `_.] If the ACPI tables are going to be used for building the list of available idle -states, ``intel_idle`` first looks for a ``_CST`` object under one of the ACPI -objects corresponding to the CPUs in the system (refer to the ACPI specification -[2]_ for the description of ``_CST`` and its output package). Because the -``CPUIdle`` subsystem expects that the list of idle states supplied by the -driver will be suitable for all of the CPUs handled by it and ``intel_idle`` is -registered as the ``CPUIdle`` driver for all of the CPUs in the system, the -driver looks for the first ``_CST`` object returning at least one valid idle -state description and such that all of the idle states included in its return -package are of the FFH (Functional Fixed Hardware) type, which means that the -``MWAIT`` instruction is expected to be used to tell the processor that it can -enter one of them. The return package of that ``_CST`` is then assumed to be +states, ``intel_idle`` will be looking for ``_LPI`` or ``_CST`` objects in them +(refer to the ACPI specification [2]_ for the definitions of the ``_LPI`` and +``_CST`` objects). If ``_LPI`` is present under at least one of the ACPI +objects representing the CPUs in the system and ``_LPI`` processing produces a +non-empty list of valid idle states, it will be used. Otherwise, ``_CST`` will +be used so long as it is present under at least one of the ACPI objects +representing the CPUs in the system and it returns a non-empty list of valid +idle states. In either case, since the ``CPUIdle`` subsystem expects that the +list of idle states supplied by the driver will be suitable for all of the CPUs +handled by it and ``intel_idle`` is registered as the ``CPUIdle`` driver for all +of the CPUs in the system, ``intel_idle`` looks for the first CPU where the +ACPI-supplied list of idle states (coming from either ``_LPI`` or ``_CST``) +is not empty. Moreover, all of the states in that list need to be of the FFH +(Functional Fixed Hardware) type, which means that the ``MWAIT`` instruction is +expected to be used to tell the processor that the given idle state may be +entered. If that expectation is met, the list of idle states is assumed to be applicable to all of the other CPUs in the system and the idle state descriptions extracted from it are stored in a preliminary list of idle states coming from the ACPI tables. [This step is skipped if ``intel_idle`` is @@ -129,18 +134,21 @@ If the given processor model is not recognized by ``intel_idle``, but it supports ``MWAIT``, the preliminary list of idle states coming from the ACPI tables is used for building the final list that will be supplied to the ``CPUIdle`` core during driver registration. For each idle state in that list, -the description, ``MWAIT`` hint and exit latency are copied to the corresponding -entry in the final list of idle states. The name of the idle state represented -by it (to be returned by the ``name`` idle state attribute in ``sysfs``) is -"CX_ACPI", where X is the index of that idle state in the final list (note that -the minimum value of X is 1, because 0 is reserved for the "polling" state), and -its target residency is based on the exit latency value. Specifically, for -C1-type idle states the exit latency value is also used as the target residency -(for compatibility with the majority of the "internal" tables of idle states for -various processor models recognized by ``intel_idle``) and for the other idle -state types (C2 and C3) the target residency value is 3 times the exit latency -(again, that is because it reflects the target residency to exit latency ratio -in the majority of cases for the processor models recognized by ``intel_idle``). +the description, ``MWAIT`` hint and exit (wake) latency are copied to the +corresponding entry in the final list of idle states. If the preliminary list +of idle states has been obtained through ``_LPI`` processing, the minimum +residency parameter of the given idle state is taken as its target residency. +Otherwise, for C1-type idle states, the exit latency value is also used as the +target residency (for compatibility with the majority of the "internal" tables +of idle states for various processor models recognized by ``intel_idle``), and +for the other idle state types (C2 and C3) the target residency value is 3 times +the exit latency (again, that is because it reflects the target residency to +exit latency ratio in the majority of cases for the processor models recognized +by ``intel_idle``). The name of the idle state (to be returned by the ``name`` +idle state attribute in ``sysfs``) is either "Cx_LPI" (if it comes from ``_LPI`` +processing) or "Cx_ACPI", where x is the index of that idle state in the final +list (note that the minimum value of x is 1, because 0 is reserved for the +"polling" state), and its target residency is based on the exit latency value. All of the idle states in the final list are enabled by default in this case. From 21d5c4cee31c5ce78f6decc7fafc7e7759af391f Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Sat, 11 Jul 2026 20:22:45 +0530 Subject: [PATCH 54/76] PM: hibernate: Fix memory leak in snapshot_write_next() error path When memory_bm_create() succeeds for copy_bm but fails for zero_bm, the function returns without freeing the resources allocated for copy_bm. This results in a memory leak that includes radix tree nodes, zone structures, and page lists. Fix this by calling memory_bm_free() to release copy_bm's resources before returning the error code when zero_bm allocation fails. Fixes: 005e8dddd497 ("PM: hibernate: don't store zero pages in the image file") Signed-off-by: Malaya Kumar Rout Acked-by: Brian Geffon Link: https://patch.msgid.link/20260711145246.8625-1-malayarout91@gmail.com Signed-off-by: Rafael J. Wysocki --- kernel/power/snapshot.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 38c0a9d31d1a..b209712cb2c3 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -2797,9 +2797,10 @@ int snapshot_write_next(struct snapshot_handle *handle) return error; error = memory_bm_create(&zero_bm, GFP_ATOMIC, PG_ANY); - if (error) + if (error) { + memory_bm_free(©_bm, PG_UNSAFE_CLEAR); return error; - + } nr_zero_pages = 0; hibernate_restore_protection_begin(); From 032270c30836ef1bf7739b7b9568bd77de1d9a9b Mon Sep 17 00:00:00 2001 From: Haesung Kim Date: Tue, 14 Jul 2026 16:26:27 +0900 Subject: [PATCH 55/76] PM: hibernate: swap: defer linking the next map page Delay allocating and linking the next swap_map_page until another image page actually needs to be recorded. The previous code linked and wrote a new swap map page as soon as the current one became full. When the image size was an exact multiple of MAP_PAGE_ENTRIES, that left an empty final map page that existed only to terminate the on-disk chain. Instead, keep a full map page in memory and only allocate the next map page when the next image page arrives. This preserves the resume chain while avoiding an unnecessary swap slot allocation and write for the empty swap map page. Signed-off-by: Haesung Kim Link: https://patch.msgid.link/20260714072627.3165744-1-mattkim513@gmail.com Signed-off-by: Rafael J. Wysocki --- kernel/power/swap.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/kernel/power/swap.c b/kernel/power/swap.c index 67570f74566c..c78f1593600b 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -430,19 +430,22 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, if (!handle->cur) return -EINVAL; - offset = alloc_swapdev_block(root_swap); - error = write_page(buf, offset, hb); - if (error) - return error; - handle->cur->entries[handle->k++] = offset; + + /* + * If the current map page is full, allocate and link next one first. + * Delaying this until here avoids writing an empty swap map page when + * the image size is an exact MAP_PAGE_ENTRIES multiple. + */ if (handle->k >= MAP_PAGE_ENTRIES) { offset = alloc_swapdev_block(root_swap); if (!offset) return -ENOSPC; + handle->cur->next_swap = offset; error = write_page(handle->cur, handle->cur_swap, hb); if (error) - goto out; + return error; + clear_page(handle->cur); handle->cur_swap = offset; handle->k = 0; @@ -450,7 +453,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, if (hb && low_free_pages() <= handle->reqd_free_pages) { error = hib_wait_io(hb); if (error) - goto out; + return error; /* * Recalculate the number of required free pages, to * make sure we never take more than half. @@ -458,14 +461,21 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, handle->reqd_free_pages = reqd_free_pages(); } } - out: - return error; + + offset = alloc_swapdev_block(root_swap); + error = write_page(buf, offset, hb); + if (error) + return error; + handle->cur->entries[handle->k++] = offset; + return 0; } static int flush_swap_writer(struct swap_map_handle *handle) { - if (handle->cur && handle->cur_swap) + if (handle->cur && handle->cur_swap && handle->k) return write_page(handle->cur, handle->cur_swap, NULL); + else if (handle->cur && handle->cur_swap) + return 0; else return -EINVAL; } From 058c1ac0e6f6924baf9c23152417865b27c67e9e Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 20 Jul 2026 03:08:20 +0000 Subject: [PATCH 56/76] PM: sleep: Rename module parameters prefix to "pm" Currently, the module parameters defined in drivers/base/power/main.c use the default prefix "main" (derived from the filename). The prefix is too generic and non-descriptive. Redefine MODULE_PARAM_PREFIX to "pm." to group the module parameters under the namespace instead. This makes the parameters more descriptive. Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260720030821.2780257-2-tzungbi@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index f71467f6ada4..49ea6e2cd735 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -41,6 +41,9 @@ #include "../base.h" #include "power.h" +#undef MODULE_PARAM_PREFIX +#define MODULE_PARAM_PREFIX "pm." + typedef int (*pm_callback_t)(struct device *); /* From dca82e209e5ced94b9e53c004a935bc44162073d Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 20 Jul 2026 03:08:21 +0000 Subject: [PATCH 57/76] PM: sleep: Allow disabling DPM watchdog by default Introduce the "dpm_watchdog_enabled" module parameter to allow the DPM watchdog to be enabled or disabled at boot time and runtime. Additionally, introduce the CONFIG_DPM_WATCHDOG_ENABLED Kconfig option to set the default value of the module parameter at compile time. The primary motivation for this configurability resolves around Android GKI (Generic Kernel Image). We want to enable CONFIG_DPM_WATCHDOG in the GKI so the feature is available. However, because the GKI is shared across many different devices, we don't want to inadvertently affect devices that are unaware of this feature. This provides a way to compile it in, but keep it disabled by default for those devices via the kernel command line or module parameters. To maintain backward compatibility, CONFIG_DPM_WATCHDOG_ENABLED relies on `default y`. Previously, the DPM watchdog was always active if CONFIG_DPM_WATCHDOG was set. Defaulting this new option to 'y' ensures that the behavior remains unchanged for existing users and defconfigs when they upgrade. Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260720030821.2780257-3-tzungbi@kernel.org Signed-off-by: Rafael J. Wysocki --- Documentation/admin-guide/kernel-parameters.txt | 7 +++++++ drivers/base/power/main.c | 11 +++++++++++ kernel/power/Kconfig | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index b5493a7f8f22..ea0b70f87472 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -47,6 +47,7 @@ PCI PCI bus support is enabled. PCIE PCI Express support is enabled. PCMCIA The PCMCIA subsystem is enabled. + PM Power Management support is enabled. PNP Plug & Play support is enabled. PPC PowerPC architecture is enabled. PPT Parallel port support is enabled. @@ -5354,6 +5355,12 @@ Kernel parameters pm_debug_messages [SUSPEND,KNL] Enable suspend/resume debug messages during boot up. + pm.dpm_watchdog_enabled= + [PM] Enable or disable the DPM watchdog. Requires + CONFIG_PM_SLEEP and CONFIG_DPM_WATCHDOG enabled. + Format: + Default value is set by CONFIG_DPM_WATCHDOG_ENABLED. + pnp.debug=1 [PNP] Enable PNP debug messages (depends on the CONFIG_PNP_DEBUG_MESSAGES option). Change at run-time diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 49ea6e2cd735..184dc4b3b938 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -535,6 +535,11 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644); MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace, "Backtrace all CPUs on DPM watchdog timeout"); +static bool __read_mostly dpm_watchdog_enabled = + IS_ENABLED(CONFIG_DPM_WATCHDOG_ENABLED); +module_param(dpm_watchdog_enabled, bool, 0644); +MODULE_PARM_DESC(dpm_watchdog_enabled, "Enable DPM watchdog"); + static unsigned int __read_mostly dpm_watchdog_timeout = CONFIG_DPM_WATCHDOG_TIMEOUT; static unsigned int __read_mostly dpm_watchdog_warning_timeout = CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT; @@ -630,6 +635,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev) { struct timer_list *timer = &wd->timer; + if (!dpm_watchdog_enabled) + return; + wd->dev = dev; wd->tsk = current; wd->fatal = dpm_watchdog_timeout == dpm_watchdog_warning_timeout; @@ -648,6 +656,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd) { struct timer_list *timer = &wd->timer; + if (!dpm_watchdog_enabled) + return; + timer_delete_sync(timer); timer_destroy_on_stack(timer); } diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 530c897311d4..71165e7f04f4 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -268,6 +268,16 @@ config DPM_WATCHDOG captured in pstore device for inspection in subsequent boot session. +config DPM_WATCHDOG_ENABLED + bool "Enable DPM watchdog by default" + depends on DPM_WATCHDOG + default y + help + If you say Y here, the DPM watchdog will be enabled by default. + If you say N, it will be compiled in but disabled. It can be + enabled at boot time via the "pm.dpm_watchdog_enabled" kernel + parameter or at runtime via sysfs. + config DPM_WATCHDOG_TIMEOUT int "Watchdog timeout to panic in seconds" range 1 120 From 9229916d59918ec9d3639e7263e1e97be638e361 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Thu, 23 Jul 2026 22:53:20 +0530 Subject: [PATCH 58/76] powercap: intel_rapl_tpmi: Handle PMU registration failure during probe intel_rapl_tpmi_probe() invokes rapl_package_add_pmu() but ignores its return value, so a PMU registration failure would leave the driver reporting probe success despite the PMU being absent, with no log trace. Since PMU registration is an optional auxiliary feature for perf energy counters, its failure should not break the primary powercap functionality. Check the return value and log a warning to ensure graceful degradation. Fixes: 963a9ad3c589 ("powercap: intel_rapl_tpmi: Enable PMU support") Signed-off-by: Sumeet Pawnikar [ rjw: Changed the log level of the new message to "info" ] Link: https://patch.msgid.link/20260723172321.5960-1-sumeet4linux@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/powercap/intel_rapl_tpmi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/powercap/intel_rapl_tpmi.c b/drivers/powercap/intel_rapl_tpmi.c index 7f41491d9cd1..73f36d9c09b1 100644 --- a/drivers/powercap/intel_rapl_tpmi.c +++ b/drivers/powercap/intel_rapl_tpmi.c @@ -414,7 +414,10 @@ static int intel_rapl_tpmi_probe(struct auxiliary_device *auxdev, goto err; } - rapl_package_add_pmu(trp->rp); + ret = rapl_package_add_pmu(trp->rp); + if (ret) + dev_info(&auxdev->dev, "Failed to add RAPL PMU for Package%d, %d\n", + info->package_id, ret); auxiliary_set_drvdata(auxdev, trp); From 5c3ecf36d2918facff40548ee6ae28eef0865266 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:48 +0000 Subject: [PATCH 59/76] cpufreq/amd-pstate: Set min_limit_freq based on bios_min_perf amd_pstate_update_min_max_limit() sets the min_limit_perf to the nominal_perf to avoid frequency throttling when the system is idling. This was found to be an ideal default but is suboptimal for users who have profiled their workload at different operating frequencies and have configured the optimal idling frequency via bios_min_perf. Use the bios_min_perf (if configured) as the min_limit_perf when running with performance governor. In absence of bios_min_perf, continue using nominal_perf as the default min_limit_perf to avoid throttling. Fixes: 608a76b65288 ("cpufreq/amd-pstate: Add support for the "Requested CPU Min frequency" BIOS option") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-2-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index b357189f4f8e..25a179b89a74 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -699,9 +699,12 @@ static void amd_pstate_update_min_max_limit(struct cpufreq_policy *policy) WRITE_ONCE(cpudata->max_limit_freq, policy->max); if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) { + u8 min_limit_perf = perf.bios_min_perf ?: perf.nominal_perf; + u32 min_limit_freq; + /* - * For performance policy, set MinPerf to nominal_perf rather than - * highest_perf or lowest_nonlinear_perf. + * For performance policy, set MinPerf to nominal_perf / bios_min_perf + * rather than highest_perf or lowest_nonlinear_perf. * * Per commit 0c411b39e4f4c, using highest_perf was observed * to cause frequency throttling on power-limited platforms, leading to @@ -709,11 +712,18 @@ static void amd_pstate_update_min_max_limit(struct cpufreq_policy *policy) * performance too much for HPC workloads requiring high frequency * operation and minimal wakeup latency from idle states. * - * nominal_perf therefore provides a balance by avoiding throttling - * while still maintaining enough performance for HPC workloads. + * nominal_perf therefore provides a balanced default by avoiding + * throttling while still maintaining enough performance for HPC + * workloads when bios_min_perf is not available. + * + * When bios_min_perf is available, users have profiled their workloads + * to understand the best idling frequency. Use that instead. */ - perf.min_limit_perf = min(perf.nominal_perf, perf.max_limit_perf); - WRITE_ONCE(cpudata->min_limit_freq, min(cpudata->nominal_freq, cpudata->max_limit_freq)); + min_limit_perf = min(min_limit_perf, perf.max_limit_perf); + min_limit_freq = perf_to_freq(perf, cpudata->nominal_freq, min_limit_perf); + perf.min_limit_perf = min_limit_perf; + + WRITE_ONCE(cpudata->min_limit_freq, min(min_limit_freq, cpudata->max_limit_freq)); } else { perf.min_limit_perf = freq_to_perf(perf, cpudata->nominal_freq, policy->min); WRITE_ONCE(cpudata->min_limit_freq, policy->min); From 0e850278016ee7207b58f4e38fa5303ea6a751b4 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:49 +0000 Subject: [PATCH 60/76] cpufreq/amd-pstate: Remove the defensive check for bios_min_perf Initialization of bios_min_perf (BIOS Requested CPU Min Freq.) only succeeds when the driver init finds the CPPC_REQ MSRs to have all 0s except for MIN_PERF bits. A kexec puts the driver through the suspend path which, although resets the min_perf back to bios_min_perf, keeps the rest of the CPPR_REQ intact with the last value at the time of suspend. The defensive check for bios_min_perf exists to prevent the min perf from last CPPC_REQ being incorrectly considered as bios_min_perf when a kexec switches from an older kernel running the version of driver which is not aware of bios_min_perf to a newer one. This scenario is extremely unlikely and Mario suggested it is better to simplify the initialization rather than complicating the suspend resume paths. Drop the defensive check for bios_min_perf initialization and add a debug message to dump the BIOS Requested Min Freq. to console leaving enough breadcrumbs for debug if a situation so arises. Suggested-by: Mario Limonciello Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-3-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 25a179b89a74..4c608dad1b0b 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -462,7 +462,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) { union perf_cached perf = READ_ONCE(cpudata->perf); u64 cap1, numerator, cppc_req; - u8 min_perf; int ret = rdmsrq_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, &cap1); @@ -478,16 +477,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) return ret; WRITE_ONCE(cpudata->cppc_req_cached, cppc_req); - min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); - - /* - * Clear out the min_perf part to check if the rest of the MSR is 0, if yes, this is an - * indication that the min_perf value is the one specified through the BIOS option - */ - cppc_req &= ~(AMD_CPPC_MIN_PERF_MASK); - - if (!cppc_req) - perf.bios_min_perf = min_perf; perf.highest_perf = numerator; perf.max_limit_perf = numerator; @@ -495,6 +484,7 @@ static int msr_init_perf(struct amd_cpudata *cpudata) perf.nominal_perf = FIELD_GET(AMD_CPPC_NOMINAL_PERF_MASK, cap1); perf.lowest_nonlinear_perf = FIELD_GET(AMD_CPPC_LOWNONLIN_PERF_MASK, cap1); perf.lowest_perf = FIELD_GET(AMD_CPPC_LOWEST_PERF_MASK, cap1); + perf.bios_min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); WRITE_ONCE(cpudata->perf, perf); WRITE_ONCE(cpudata->prefcore_ranking, FIELD_GET(AMD_CPPC_HIGHEST_PERF_MASK, cap1)); WRITE_ONCE(cpudata->floor_perf_cnt, FIELD_GET(AMD_CPPC_FLOOR_PERF_CNT_MASK, cap1)); @@ -1044,6 +1034,13 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) return -EINVAL; } + if (perf.bios_min_perf) { + u32 bios_min_freq = perf_to_freq(perf, cpudata->nominal_freq, perf.bios_min_perf); + + pr_debug("Found Requested CPU Min Frequency of %uKHz on CPU%d\n", + bios_min_freq, cpudata->cpu); + } + return 0; } From 77b049427b6de17b8036411da7a91279fe20709e Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:50 +0000 Subject: [PATCH 61/76] cpufreq/amd-pstate: Extract platform profile to EPP conversion into a helper Avoid duplication by extracting the switch case that derives EPP based on platform profile into the amd_pstate_get_epp_from_platform_profile() helper. No functional changes intended. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-4-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 67 +++++++++++++++++------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 4c608dad1b0b..5b10a0a0e6b6 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1189,6 +1189,24 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, return NOTIFY_OK; } +static int amd_pstate_get_epp_from_platform_profile(struct cpufreq_policy *policy, + enum platform_profile_option profile) +{ + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + return AMD_CPPC_EPP_PERFORMANCE; + case PLATFORM_PROFILE_BALANCED: + return amd_pstate_get_balanced_epp(policy); + case PLATFORM_PROFILE_LOW_POWER: + return AMD_CPPC_EPP_POWERSAVE; + default: + break; + } + + pr_err("Unknown Platform Profile %d\n", profile); + return -EOPNOTSUPP; +} + static int amd_pstate_profile_probe(void *drvdata, unsigned long *choices) { set_bit(PLATFORM_PROFILE_LOW_POWER, choices); @@ -1214,31 +1232,19 @@ static int amd_pstate_profile_set(struct device *dev, struct amd_cpudata *cpudata = dev_get_drvdata(dev); struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); int ret; + u8 epp; if (!policy) return -ENODEV; - switch (profile) { - case PLATFORM_PROFILE_LOW_POWER: - ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); - if (ret) - return ret; - break; - case PLATFORM_PROFILE_BALANCED: - ret = amd_pstate_set_epp(policy, - amd_pstate_get_balanced_epp(policy)); - if (ret) - return ret; - break; - case PLATFORM_PROFILE_PERFORMANCE: - ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_PERFORMANCE); - if (ret) - return ret; - break; - default: - pr_err("Unknown Platform Profile %d\n", profile); - return -EOPNOTSUPP; - } + ret = amd_pstate_get_epp_from_platform_profile(policy, profile); + if (ret < 0) + return ret; + + epp = (u8)ret; + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; cpudata->current_profile = profile; @@ -1272,20 +1278,11 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) int ret; u8 epp; - switch (cpudata->current_profile) { - case PLATFORM_PROFILE_PERFORMANCE: - epp = AMD_CPPC_EPP_PERFORMANCE; - break; - case PLATFORM_PROFILE_LOW_POWER: - epp = AMD_CPPC_EPP_POWERSAVE; - break; - case PLATFORM_PROFILE_BALANCED: - epp = amd_pstate_get_balanced_epp(policy); - break; - default: - pr_err("Unknown Platform Profile %d\n", cpudata->current_profile); - return -EOPNOTSUPP; - } + ret = amd_pstate_get_epp_from_platform_profile(policy, cpudata->current_profile); + if (ret < 0) + return ret; + + epp = (u8)ret; ret = amd_pstate_set_epp(policy, epp); if (ret) return ret; From b7294b627598d2c709715be422c0b77e491d82f1 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:51 +0000 Subject: [PATCH 62/76] cpufreq/amd-pstate: Add dynamic EPP as an "energy_performance_preference" mode Convert the global "dynamic_epp" toggle into a per-CPU "energy_performance_preference" mode "dynamic" that allows toggling the functionality of "dynamic_epp" at a per-CPU level. Instead of being a system-wide toggle, users can opt into the functionality of dynamic EPP on a per-CPU basis by switching to the powersave governor and selecting the "dynamic" mode from the available performance preferences. Unlike the previous implementation that had to check for driver mode before toggling on the functionality, block writes to certain sysfs files, potentially disallow policy change, etc. the per-CPU toggle fits naturally into the intended design and provides more granular control to the user. The dynamic_epp file is now redundant as the option to toggle it on is controlled via energy_performance_preference, and the dynamic_epp file will be removed in the subsequent commit. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-5-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 59 +++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 5b10a0a0e6b6..fdf3106f6e07 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -106,6 +106,7 @@ static struct quirk_entry *quirks; * 3 balance_power * 4 power * 5 custom (for raw EPP values) + * 6 dynamic (platform profile driven selection) */ enum energy_perf_value_index { EPP_INDEX_DEFAULT = 0, @@ -114,6 +115,7 @@ enum energy_perf_value_index { EPP_INDEX_BALANCE_POWERSAVE, EPP_INDEX_POWERSAVE, EPP_INDEX_CUSTOM, + EPP_INDEX_DYNAMIC, EPP_INDEX_MAX, }; @@ -124,6 +126,7 @@ static const char * const energy_perf_strings[] = { [EPP_INDEX_BALANCE_POWERSAVE] = "balance_power", [EPP_INDEX_POWERSAVE] = "power", [EPP_INDEX_CUSTOM] = "custom", + [EPP_INDEX_DYNAMIC] = "dynamic", }; static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); @@ -134,7 +137,7 @@ static unsigned int epp_values[] = { [EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_BALANCE_POWERSAVE, [EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_POWERSAVE, }; -static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 1); +static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 2); typedef int (*cppc_mode_transition_fn)(int); @@ -1275,6 +1278,7 @@ EXPORT_SYMBOL_GPL(amd_pstate_clear_dynamic_epp); static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) { struct amd_cpudata *cpudata = policy->driver_data; + u64 prev = READ_ONCE(cpudata->cppc_req_cached); int ret; u8 epp; @@ -1315,6 +1319,9 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) cleanup: amd_pstate_clear_dynamic_epp(policy); + epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, prev); + /* Restore previous EPP if toggling Dynamic EPP failed. */ + amd_pstate_set_epp(policy, epp); return ret; } @@ -1385,7 +1392,7 @@ static ssize_t show_amd_pstate_hw_prefcore(struct cpufreq_policy *policy, static ssize_t show_energy_performance_available_preferences( struct cpufreq_policy *policy, char *buf) { - int offset = 0, i; + int i, offset = 0; struct amd_cpudata *cpudata = policy->driver_data; if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) @@ -1408,11 +1415,6 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, bool raw_epp = false; u8 epp; - if (cpudata->dynamic_epp) { - pr_debug("EPP cannot be set when dynamic EPP is enabled\n"); - return -EBUSY; - } - /* * if the value matches a number, use that, otherwise see if * matches an index in the energy_perf_strings array @@ -1423,6 +1425,25 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, ret = sysfs_match_string(energy_perf_strings, buf); if (ret < 0 || ret == EPP_INDEX_CUSTOM) return -EINVAL; + + if (ret == EPP_INDEX_DYNAMIC) { + if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) + return -EBUSY; + /* + * Dynamic EPP was already enabled for this CPU. + * Nothing to do. + */ + if (cpudata->dynamic_epp) + return count; + + cpudata->current_profile = PLATFORM_PROFILE_BALANCED; + ret = amd_pstate_set_dynamic_epp(policy); + if (ret) + return ret; + + return count; + } + if (ret) epp = epp_values[ret]; else @@ -1434,6 +1455,13 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, return -EBUSY; } + /* + * Dynamic EPP was enabled previously! + * Switch back to the static EPP mode. + */ + if (cpudata->dynamic_epp) + amd_pstate_clear_dynamic_epp(policy); + ret = amd_pstate_set_epp(policy, epp); if (ret) return ret; @@ -1451,7 +1479,7 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached); - if (cpudata->raw_epp) + if (!cpudata->dynamic_epp && cpudata->raw_epp) return sysfs_emit(buf, "%u\n", epp); switch (epp) { @@ -1471,6 +1499,9 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * return -EINVAL; } + if (cpudata->dynamic_epp) + return sysfs_emit(buf, "dynamic(profile:%s)\n", energy_perf_strings[preference]); + return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]); } EXPORT_SYMBOL_GPL(show_energy_performance_preference); @@ -2031,6 +2062,18 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy) if (!policy->cpuinfo.max_freq) return -ENODEV; + /* Must be a switch between PERFORMANCE and POWERSAVE */ + if (cpudata->policy != policy->policy) { + /* + * Disable dynamic_epp when switching + * out of CPUFREQ_POLICY_POWERSAVE. + */ + if (cpudata->dynamic_epp) { + WARN_ON_ONCE(cpudata->policy != CPUFREQ_POLICY_POWERSAVE); + amd_pstate_clear_dynamic_epp(policy); + } + } + cpudata->policy = policy->policy; ret = amd_pstate_epp_update_limit(policy, true); From 32692fcf61ed787df69263f0295714257f8f81db Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:52 +0000 Subject: [PATCH 63/76] cpufreq/amd-pstate: Remove "amd_dynamic_epp" cmdline and "dynamic_epp" sysfs Since dynamic_epp has been converted to an "energy_performance_preference", toggling the feature via the sysfs file or the kernel cmdline is now redundant. Remove the sysfs file and the "amd_dynamic_epp" cmdline and only depend on "energy_performance_preference" to toggle dynamic_epp. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-6-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 55 +----------------------------------- 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index fdf3106f6e07..a380c665461f 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -87,7 +87,6 @@ static struct cpufreq_driver amd_pstate_driver; static struct cpufreq_driver amd_pstate_epp_driver; static int cppc_state = AMD_PSTATE_UNDEFINED; static bool amd_pstate_prefcore = true; -static bool dynamic_epp; static struct quirk_entry *quirks; /* @@ -1837,50 +1836,12 @@ static ssize_t prefcore_show(struct device *dev, return sysfs_emit(buf, "%s\n", str_enabled_disabled(amd_pstate_prefcore)); } -static ssize_t dynamic_epp_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return sysfs_emit(buf, "%s\n", str_enabled_disabled(dynamic_epp)); -} - -static ssize_t dynamic_epp_store(struct device *a, struct device_attribute *b, - const char *buf, size_t count) -{ - bool enabled; - int ret; - - ret = kstrtobool(buf, &enabled); - if (ret) - return ret; - - guard(mutex)(&amd_pstate_driver_lock); - - if (cppc_state != AMD_PSTATE_ACTIVE) { - pr_debug("dynamic_epp can only be toggled in active mode\n"); - return -EINVAL; - } - - /* Nothing to do */ - if (dynamic_epp == enabled) - return count; - - /* reinitialize with desired dynamic EPP value */ - dynamic_epp = enabled; - ret = amd_pstate_change_driver_mode(cppc_state); - if (ret) - dynamic_epp = false; - - return ret ? ret : count; -} - static DEVICE_ATTR_RW(status); static DEVICE_ATTR_RO(prefcore); -static DEVICE_ATTR_RW(dynamic_epp); static struct attribute *pstate_global_attributes[] = { &dev_attr_status.attr, &dev_attr_prefcore.attr, - &dev_attr_dynamic_epp.attr, NULL }; @@ -1987,10 +1948,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) cpudata->current_profile = PLATFORM_PROFILE_BALANCED; } - if (dynamic_epp) - ret = amd_pstate_set_dynamic_epp(policy); - else - ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); + ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); if (ret) goto free_cpudata1; @@ -2401,19 +2359,8 @@ static int __init amd_prefcore_param(char *str) return 0; } -static int __init amd_dynamic_epp_param(char *str) -{ - if (!strcmp(str, "disable")) - dynamic_epp = false; - if (!strcmp(str, "enable")) - dynamic_epp = true; - - return 0; -} - early_param("amd_pstate", amd_pstate_param); early_param("amd_prefcore", amd_prefcore_param); -early_param("amd_dynamic_epp", amd_dynamic_epp_param); MODULE_AUTHOR("Huang Rui "); MODULE_DESCRIPTION("AMD Processor P-state Frequency Driver"); From d3a019bd0878d426cf3bd2161555fd8e62850237 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:53 +0000 Subject: [PATCH 64/76] Documentation/amd-pstate: Update dynamic_epp documentation with new behavior Update the admin-guide for dynamic_epp describing the latest integration into energy_performance_preference selections. Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-7-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- Documentation/admin-guide/pm/amd-pstate.rst | 41 ++++++++++----------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/Documentation/admin-guide/pm/amd-pstate.rst b/Documentation/admin-guide/pm/amd-pstate.rst index a95e2ebce005..c7bf7cc2db87 100644 --- a/Documentation/admin-guide/pm/amd-pstate.rst +++ b/Documentation/admin-guide/pm/amd-pstate.rst @@ -317,7 +317,9 @@ These profiles represent different hints that are provided to the low-level firmware about the user's desired energy vs efficiency tradeoff. ``default`` represents the epp value is set by platform firmware. ``custom`` designates that integer values 0-255 may be written -as well. This attribute is read-only. +as well. ``dynamic`` designates that the EPP is modified dynamically +on kernel events. See ``Dynamic energy performance profile`` section below to +know more about the ``dynamic`` mode. This attribute is read-only. ``energy_performance_preference`` @@ -326,13 +328,11 @@ and user can change current preference according to energy or performance needs Coarse named profiles are available in the attribute ``energy_performance_available_preferences``. Users can also write individual integer values between 0 to 255. -When dynamic EPP is enabled, writes to energy_performance_preference are blocked -even when EPP feature is enabled by platform firmware. Lower epp values shift the bias -towards improved performance while a higher epp value shifts the bias towards -power-savings. The exact impact can change from one platform to the other. -If a valid integer was last written, then a number will be returned on future reads. -If a valid string was last written then a string will be returned on future reads. -This attribute is read-write. +Lower epp values shift the bias towards improved performance while a higher epp +value shifts the bias towards power-savings. The exact impact can change from +one platform to the other. If a valid integer was last written, then a number +will be returned on future reads. If a valid string was last written then a +string will be returned on future reads. This attribute is read-write. ``boost`` The `boost` sysfs attribute provides control over the CPU core @@ -356,21 +356,20 @@ Other performance and frequency values can be read back from Dynamic energy performance profile ================================== The amd-pstate driver supports dynamically selecting the energy performance -profile based on whether the machine is running on AC or DC power. +profile based on the system profile and the current power source in active mode. -Whether this behavior is enabled by default depends on the kernel command line option -``amd_dynamic_epp`` is set. This behavior can also be overridden -at runtime by the sysfs file ``/sys/devices/system/cpu/amd_pstate/dynamic_epp``. +The ``dynamic`` mode is listed in +``/sys/devices/system/cpu/cpuX/cpufreq/energy_performance_available_preferences`` +when available while running under the ``powersave`` governor. The ``dynamic`` +mode can be toggled on by writing the same to the sysfs file +``/sys/devices/system/cpu/cpuX/cpufreq/energy_performance_preference`` when +available. -When set to enabled, the driver will select a different energy performance -profile when the machine is running on battery or AC power. The driver will -also register with the platform profile handler to receive notifications of -user desired power state and react to those. -When set to disabled, the driver will not change the energy performance profile -based on the power source and will not react to user desired power state. - -Attempting to manually write to the ``energy_performance_preference`` sysfs -file will fail when ``dynamic_epp`` is enabled. +When ``energy_performance_preference`` is set to ``dynamic``, the driver will +select a different energy performance profile when the machine is running on +battery or AC power. The driver will also register with the platform profile +handler to receive notifications of user desired power state and react to +those. ``amd-pstate`` vs ``acpi-cpufreq`` ====================================== From 047e65218dcd571f24e37ae13a095b0c21164858 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:54 +0000 Subject: [PATCH 65/76] cpufreq/amd-pstate: Reduce the scope of exported symbols Symbols exported by amd-pstate.c are ever only needed for amd-pstate-ut. Introduce EXPORT_SYMBOL_FOR_PSTATE_UT() to export these symbols selectively to "amd-pstate-ut" namespace as opposed to all GPL modules. No functional changes intended. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-8-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 14 +++++++------- drivers/cpufreq/amd-pstate.h | 7 +++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index a380c665461f..93d275da12c6 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -75,7 +75,7 @@ const char *amd_pstate_get_mode_string(enum amd_pstate_mode mode) mode = AMD_PSTATE_UNDEFINED; return amd_pstate_mode_string[mode]; } -EXPORT_SYMBOL_GPL(amd_pstate_get_mode_string); +EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_mode_string); struct quirk_entry { u32 nominal_freq; @@ -1272,7 +1272,7 @@ void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy) kfree(cpudata->profile_name); cpudata->dynamic_epp = false; } -EXPORT_SYMBOL_GPL(amd_pstate_clear_dynamic_epp); +EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_clear_dynamic_epp); static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) { @@ -1469,7 +1469,7 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, return count; } -EXPORT_SYMBOL_GPL(store_energy_performance_preference); +EXPORT_SYMBOL_FOR_PSTATE_UT(store_energy_performance_preference); ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf) { @@ -1503,7 +1503,7 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]); } -EXPORT_SYMBOL_GPL(show_energy_performance_preference); +EXPORT_SYMBOL_FOR_PSTATE_UT(show_energy_performance_preference); static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count) @@ -1603,7 +1603,7 @@ struct freq_attr **amd_pstate_get_current_attrs(void) return NULL; return current_pstate_driver->attr; } -EXPORT_SYMBOL_GPL(amd_pstate_get_current_attrs); +EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_current_attrs); static struct freq_attr **get_freq_attrs(void) { @@ -1788,7 +1788,7 @@ int amd_pstate_get_status(void) { return cppc_state; } -EXPORT_SYMBOL_GPL(amd_pstate_get_status); +EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status); int amd_pstate_update_status(const char *buf, size_t size) { @@ -1808,7 +1808,7 @@ int amd_pstate_update_status(const char *buf, size_t size) return 0; } -EXPORT_SYMBOL_GPL(amd_pstate_update_status); +EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_update_status); static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h index 23e8baa05849..edd697a5e29f 100644 --- a/drivers/cpufreq/amd-pstate.h +++ b/drivers/cpufreq/amd-pstate.h @@ -11,6 +11,13 @@ #include #include +#if IS_MODULE(CONFIG_X86_AMD_PSTATE_UT) +#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) \ + EXPORT_SYMBOL_FOR_MODULES(symbol, "amd-pstate-ut") +#else +#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) +#endif + /********************************************************************* * AMD P-state INTERFACE * *********************************************************************/ From d61c1ad39026682f0d5c32b96848dcaa619a4af8 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:55 +0000 Subject: [PATCH 66/76] cpufreq/amd-pstate-ut: Add unit test for "dynamic" EPP mode Extend the EPP unit test to cover the "dynamic" epp mode. Since "dynamic_epp" is no longer a system-wide toggle, remove the legacy "dynamic_epp" bits from the unit test. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-9-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate-ut.c | 45 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c index 2142838ad6cc..b432eae1dd52 100644 --- a/drivers/cpufreq/amd-pstate-ut.c +++ b/drivers/cpufreq/amd-pstate-ut.c @@ -275,6 +275,7 @@ static int amd_pstate_set_mode(enum amd_pstate_mode mode) static int amd_pstate_ut_epp(u32 index) { static const char * const epp_strings[] = { + "dynamic", "power", "balance_power", "balance_performance", @@ -282,10 +283,10 @@ static int amd_pstate_ut_epp(u32 index) }; char *buf __free(cleanup_page) = NULL; struct cpufreq_policy *policy = NULL; + unsigned long orig_dynamic_epp = 0; enum amd_pstate_mode orig_mode; struct amd_cpudata *cpudata; unsigned long orig_policy; - bool orig_dynamic_epp; int ret, cpu = 0; u16 epp; int i; @@ -294,9 +295,11 @@ static int amd_pstate_ut_epp(u32 index) if (!policy) return -ENODEV; - cpudata = policy->driver_data; orig_mode = amd_pstate_get_status(); - orig_dynamic_epp = cpudata->dynamic_epp; + if (policy->driver_data) { + cpudata = policy->driver_data; + orig_dynamic_epp = cpudata->dynamic_epp; + } /* Drop reference before potential driver change. */ cpufreq_cpu_put(policy); @@ -321,16 +324,6 @@ static int amd_pstate_ut_epp(u32 index) orig_policy = cpudata->policy; cpudata->policy = CPUFREQ_POLICY_POWERSAVE; - /* - * Disable dynamic EPP before running test. If "orig_dynamic_epp" is - * true, the driver will do a redundant switch at the end and there - * is no need for enabling it again at the end of the test. - */ - if (cpudata->dynamic_epp) { - pr_debug("Dynamic EPP is enabled, disabling it\n"); - amd_pstate_clear_dynamic_epp(policy); - } - for (epp = 0; epp <= U8_MAX; epp++) { u8 val; @@ -367,6 +360,11 @@ static int amd_pstate_ut_epp(u32 index) if (ret < 0) goto out; strreplace(buf, '\n', '\0'); + /* + * "dynamic" mode reports the EPP as "dynamic(profile:X)" + * Trim at "(" and just compare tie the epp string. + */ + strreplace(buf, '(', '\0'); if (strcmp(buf, epp_strings[i])) { pr_err("String EPP value mismatch: %s != %s\n", buf, epp_strings[i]); @@ -380,18 +378,23 @@ static int amd_pstate_ut_epp(u32 index) out: if (policy) { cpudata->policy = orig_policy; + /* + * If the driver had enabled dynamic_epp to begin with, + * restore it here before dropping policy reference. + */ + if (orig_dynamic_epp) { + int ret2; + + ret2 = store_energy_performance_preference(policy, + epp_strings[0], + strlen(epp_strings[0])); + if (!ret && (ret2 < 0)) + ret = ret2; + } up_write(&policy->rwsem); cpufreq_cpu_put(policy); } - if (orig_dynamic_epp) { - int ret2; - - ret2 = amd_pstate_set_mode(AMD_PSTATE_DISABLE); - if (!ret && ret2) - ret = ret2; - } - if (orig_mode != amd_pstate_get_status()) { int ret2; From d9a3b95f3713c88672b44ee579b6435d6ce10857 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Mon, 27 Jul 2026 07:20:56 +0000 Subject: [PATCH 67/76] cpufreq/amd-pstate-ut: Add unit test for CPPC Performance Priority Add a unit test for CPPC Performance Priority that modifies the floor perf and confirms if the modification was successful similar to the energy_performance_preference unit test. On platforms that do not support X86_FEATURE_CPPC_PERF_PRIO, the test returns -EOPNOTSUPP and amd_pstate_ut_check_floor_freq is marked as "skipped". Suggested-by: Kalpana Shetty Reviewed-by: Mario Limonciello (AMD) Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260727072056.1248-10-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate-ut.c | 86 ++++++++++++++++++++++++++++++++- drivers/cpufreq/amd-pstate.c | 7 +-- drivers/cpufreq/amd-pstate.h | 2 + 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c index b432eae1dd52..e23773680e05 100644 --- a/drivers/cpufreq/amd-pstate-ut.c +++ b/drivers/cpufreq/amd-pstate-ut.c @@ -59,6 +59,7 @@ static int amd_pstate_ut_check_freq(u32 index); static int amd_pstate_ut_epp(u32 index); static int amd_pstate_ut_check_driver(u32 index); static int amd_pstate_ut_check_freq_attrs(u32 index); +static int amd_pstate_ut_check_floor_freq(u32 index); static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid }, @@ -68,6 +69,7 @@ static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { {"amd_pstate_ut_epp", amd_pstate_ut_epp }, {"amd_pstate_ut_check_driver", amd_pstate_ut_check_driver }, {"amd_pstate_ut_check_freq_attrs", amd_pstate_ut_check_freq_attrs }, + {"amd_pstate_ut_check_floor_freq", amd_pstate_ut_check_floor_freq }, }; static bool test_in_list(const char *list, const char *name) @@ -560,6 +562,80 @@ static int amd_pstate_ut_check_freq_attrs(u32 index) return ret; } +static int amd_pstate_ut_check_floor_freq(u32 index) +{ + struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL; + char *buf __free(cleanup_page) = NULL; + unsigned int orig_floor_freq; + unsigned int floor_freq; + int ret, cpu = 0; + + if (!cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO)) + return -EOPNOTSUPP; + + policy = cpufreq_cpu_get(cpu); + if (!policy) + return -ENODEV; + + buf = (char *)__get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + guard(rwsem_write)(&policy->rwsem); + + if (!policy->driver_data) + return -ENODEV; + + /* Retrieve original floor frequency */ + memset(buf, 0, PAGE_SIZE); + ret = show_amd_pstate_floor_freq(policy, buf); + if (ret < 0) + return ret; + + ret = kstrtou32(buf, 0, &orig_floor_freq); + if (ret) + return ret; + + memset(buf, 0, PAGE_SIZE); + snprintf(buf, PAGE_SIZE, "%u", policy->cpuinfo.min_freq); + + /* Set floor frequency to cpuinfo.min_freq */ + ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); + if (ret < 0) { + pr_err("Failed to set floor frequency to %s\n", buf); + return ret; + } + + memset(buf, 0, PAGE_SIZE); + ret = show_amd_pstate_floor_freq(policy, buf); + if (ret < 0) + return ret; + + strreplace(buf, '\n', '\0'); + ret = kstrtou32(buf, 0, &floor_freq); + if (ret) + return ret; + + /* Confirm sysfs reflects the change correctly. */ + if (floor_freq != policy->cpuinfo.min_freq) { + pr_err("Floor frequency value mismatch: %u != %u\n", + floor_freq, policy->cpuinfo.min_freq); + return -EINVAL; + } + + memset(buf, 0, PAGE_SIZE); + snprintf(buf, PAGE_SIZE, "%u", orig_floor_freq); + + /* Restore the original value. */ + ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); + if (ret < 0) { + pr_err("Failed to restore floor frequency to %s\n", buf); + return ret; + } + + return 0; +} + static int __init amd_pstate_ut_init(void) { u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); @@ -578,10 +654,16 @@ static int __init amd_pstate_ut_init(void) ret = amd_pstate_ut_cases[i].func(i); - if (ret) + if (ret) { + /* Platform does not support the feature being tested. */ + if (ret == -EOPNOTSUPP) { + pr_err("%-4d %-20s\t skipped!\n", i+1, amd_pstate_ut_cases[i].name); + continue; + } pr_err("%-4d %-20s\t fail: %d!\n", i+1, amd_pstate_ut_cases[i].name, ret); - else + } else { pr_info("%-4d %-20s\t success!\n", i+1, amd_pstate_ut_cases[i].name); + } } return 0; diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 93d275da12c6..ea6cc072121f 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1505,8 +1505,7 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * } EXPORT_SYMBOL_FOR_PSTATE_UT(show_energy_performance_preference); -static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, - const char *buf, size_t count) +ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count) { struct amd_cpudata *cpudata = policy->driver_data; union perf_cached perf = READ_ONCE(cpudata->perf); @@ -1529,13 +1528,15 @@ static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, return ret ?: count; } +EXPORT_SYMBOL_FOR_PSTATE_UT(store_amd_pstate_floor_freq); -static ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) +ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) { struct amd_cpudata *cpudata = policy->driver_data; return sysfs_emit(buf, "%u\n", cpudata->floor_freq); } +EXPORT_SYMBOL_FOR_PSTATE_UT(show_amd_pstate_floor_freq); static ssize_t show_amd_pstate_floor_count(struct cpufreq_policy *policy, char *buf) { diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h index edd697a5e29f..f8e2f6ba1534 100644 --- a/drivers/cpufreq/amd-pstate.h +++ b/drivers/cpufreq/amd-pstate.h @@ -160,6 +160,8 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, const char *buf, size_t count); ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf); void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy); +ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count); +ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf); struct freq_attr; From d06c75c22d5c95ee27e01fedcaa07231c9bd5c88 Mon Sep 17 00:00:00 2001 From: David Vernet Date: Tue, 28 Jul 2026 02:31:47 -0500 Subject: [PATCH 68/76] cpufreq/amd-pstate: Document missing kernel-doc members kernel-doc warns about five undescribed members in amd-pstate.h: union perf_cached's @val and struct amd_cpudata's @raw_epp, @current_profile, @ppdev and @profile_name. Describe them. Signed-off-by: David Vernet Acked-by: Mario Limonciello (AMD) Link: https://lore.kernel.org/r/20260728073150.54964-2-void@manifault.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h index f8e2f6ba1534..9f5a81976eae 100644 --- a/drivers/cpufreq/amd-pstate.h +++ b/drivers/cpufreq/amd-pstate.h @@ -39,6 +39,7 @@ * @min_limit_perf: Cached value of the performance corresponding to policy->min * @max_limit_perf: Cached value of the performance corresponding to policy->max * @bios_min_perf: Cached perf value corresponding to the "Requested CPU Min Frequency" BIOS option + * @val: Raw 64-bit value for atomic access via READ_ONCE()/WRITE_ONCE() */ union perf_cached { struct { @@ -96,7 +97,12 @@ struct amd_aperf_mperf { * @epp_default_ac: Default EPP value for AC power source * @epp_default_dc: Default EPP value for DC power source * @dynamic_epp: Whether dynamic EPP is enabled + * @raw_epp: Whether the last EPP write was a raw numeric value rather than a + * named preference * @power_nb: Notifier block for power events + * @current_profile: Currently selected platform profile option + * @ppdev: Device registered with the platform profile handler + * @profile_name: Name under which @ppdev is registered * * The amd_cpudata is key private data for each CPU thread in AMD P-State, and * represents all the attributes and goals that AMD P-State requests at runtime. From 9e4cb21f2940230efc09f90c8335b5cbb3e26c41 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 29 Jul 2026 20:40:08 +0200 Subject: [PATCH 69/76] cpufreq: intel_pstate: Adjust policy->cur in active mode to policy Since arch_freq_get_on_cpu() on x86 falls back to cpufreq_quick_get(), which effectively causes policy->cur to be returned when intel_pstate is used, adjust intel_pstate_set_policy() to set policy->cur to reflect the P-state that is actually going to be requested in the "performance" policy case instead of setting it to policy->min (which is confusing because it causes scaling_cur_freq to show the minimum frequency while the CPU is likely running at the maximum one). For this purpose, rearrange intel_pstate_set_policy() to handle the HWP case separately, to avoid calling intel_pstate_set_pstate() pointlessly with HWP enabled, and use the observation that with HWP enabled in the active mode, the utilization update hook is only needed when HWP boost is used and the policy is not "performance". Signed-off-by: Rafael J. Wysocki Reviewed-by: Doug Smythies Tested-by: Doug Smythies Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/5144014.31r3eYUQgx@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 39 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 2cbf810db66e..7ce5981cbae5 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -2870,6 +2870,7 @@ static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) static int intel_pstate_set_policy(struct cpufreq_policy *policy) { + unsigned int freq = policy->min; struct cpudata *cpu; if (!policy->cpuinfo.max_freq) @@ -2885,7 +2886,23 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) intel_pstate_update_perf_limits(cpu, policy->min, policy->max); - if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + if (hwp_active) { + /* + * The active mode only requires an update util hook if HWP + * boost is used and the policy is not "performance". + */ + if (hwp_boost && cpu->policy != CPUFREQ_POLICY_PERFORMANCE) { + intel_pstate_set_update_util_hook(policy->cpu); + } else { + intel_pstate_clear_update_util_hook(policy->cpu); + if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + freq = cpu->max_perf_ratio * cpu->pstate.scaling; + if (cpu->pstate.scaling != cpu->pstate.perf_ctl_scaling) + freq = rounddown(freq, cpu->pstate.perf_ctl_scaling); + } + } + intel_pstate_hwp_set(policy->cpu); + } else if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { int pstate = max(cpu->pstate.min_pstate, cpu->max_perf_ratio); /* @@ -2894,25 +2911,17 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) */ intel_pstate_clear_update_util_hook(policy->cpu); intel_pstate_set_pstate(cpu, pstate); + freq = pstate * cpu->pstate.scaling; } else { intel_pstate_set_update_util_hook(policy->cpu); } - - if (hwp_active) { - /* - * When hwp_boost was active before and dynamically it - * was turned off, in that case we need to clear the - * update util hook. - */ - if (!hwp_boost) - intel_pstate_clear_update_util_hook(policy->cpu); - intel_pstate_hwp_set(policy->cpu); - } /* - * policy->cur is never updated with the intel_pstate driver, but it - * is used as a stale frequency value. So, keep it within limits. + * policy->cur is never updated in the intel_pstate driver, but it is + * used as a stale frequency value, so set it to reflect the actual + * requested P-state in the "performance" policy case and to the min + * otherwise. */ - policy->cur = policy->min; + policy->cur = freq; mutex_unlock(&intel_pstate_limits_lock); From adfe0057326ffbc2cd5ff69a57ec0e50b5e74095 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:37 +0900 Subject: [PATCH 70/76] cpupower: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Link: https://lore.kernel.org/linux-pm/20260723184538.3888637-36-ekffu200098@gmail.com/raw Signed-off-by: Sang-Heon Jeon Signed-off-by: Shuah Khan --- tools/power/cpupower/utils/powercap-info.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/power/cpupower/utils/powercap-info.c b/tools/power/cpupower/utils/powercap-info.c index e53033488218..88a5edb76315 100644 --- a/tools/power/cpupower/utils/powercap-info.c +++ b/tools/power/cpupower/utils/powercap-info.c @@ -47,8 +47,6 @@ static int powercap_print_one_zone(struct powercap_zone *zone) printf("\n"); - if (ret != 0) - return ret; return ret; } From 9eadbed788df453289b5927327bd22edb542f472 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 3 Aug 2026 20:11:10 +0200 Subject: [PATCH 71/76] intel_idle: Avoid using deep idle states during initialization Commit c0f691388992 ("intel_idle: Use subsys_initcall_sync() for initialization") effectively made intel_idle initialize earlier which turns out to interfere with USB EHCI probing on some platforms [1]. Investigation led to the conclusion that this was related to allowing package idle states to be used earlier than before. Work around that issue by making intel_idle set a CPU latency QoS request to prevent package idle states from being used on all platforms supported by it for the duration of the device_initcall() initialization phase. Fixes: c0f691388992 ("intel_idle: Use subsys_initcall_sync() for initialization") Reported-by: Julian Silver Tested-by: Julian Silver Closes: https://lore.kernel.org/linux-acpi/3353bdf3-4f33-44b1-809b-b0378bee5816@gmail.com/ Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/5120454.31r3eYUQgx@rafael.j.wysocki --- drivers/idle/intel_idle.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index d74b478db280..49c4ac9c7bb6 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -2697,6 +2698,9 @@ static void __init cmdline_table_adjust(struct cpuidle_driver *drv) pr_info("Failed to adjust C-states with data from 'intel_idle.table'\n"); } +#define INTEL_IDLE_INIT_QOS 20 +static struct pm_qos_request qos_req __initdata; + static int __init intel_idle_init(void) { const struct x86_cpu_id *id; @@ -2766,6 +2770,13 @@ static int __init intel_idle_init(void) if (retval) pr_warn("failed to initialized sysfs"); + /* + * Some platforms, in particular the Intel S1200BTL motherboard, have a + * problem with using package idle states too early, so prevent that + * from taking place until the device_initcall() phase is over. + */ + cpu_latency_qos_add_request(&qos_req, INTEL_IDLE_INIT_QOS); + retval = cpuidle_register_driver(&intel_idle_driver); if (retval) { struct cpuidle_driver *drv = cpuidle_get_driver(); @@ -2790,6 +2801,9 @@ static int __init intel_idle_init(void) intel_idle_cpuidle_devices_uninit(); cpuidle_unregister_driver(&intel_idle_driver); init_driver_fail: + if (cpu_latency_qos_request_active((&qos_req))) + cpu_latency_qos_remove_request(&qos_req); + intel_idle_sysfs_uninit(); free_percpu(intel_idle_cpuidle_devices); return retval; @@ -2797,6 +2811,15 @@ static int __init intel_idle_init(void) } subsys_initcall_sync(intel_idle_init); +static int __init intel_idle_init_complete(void) +{ + if (cpu_latency_qos_request_active((&qos_req))) + cpu_latency_qos_remove_request(&qos_req); + + return 0; +} +device_initcall_sync(intel_idle_init_complete); + /* * We are not really modular, but we used to support that. Meaning we also * support "intel_idle.max_cstate=..." at boot and also a read-only export of From 84f05af0975c9f9c8ffee0361d6f7cbb1f289231 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 27 Jul 2026 22:33:12 +0530 Subject: [PATCH 72/76] opp: Use clk_get_optional() to avoid leaving opp_table->clk as an error pointer _update_opp_table_clk() uses clk_get(dev, NULL) to acquire the device's clock. On platforms where the perf domain device has no Linux clock and is instead managed entirely by firmware via devm_pm_opp_of_add_table() (through of_genpd_add_provider_simple()/onecell()), clk_get() returns -ENOENT. That case is treated as valid (the OPP table can still have entries sourced from firmware), but opp_table->clk is left holding ERR_PTR(-ENOENT) rather than being reset to NULL: opp_table->clk = clk_get(dev, NULL); ret = PTR_ERR_OR_ZERO(opp_table->clk); ... if (ret == -ENOENT) { opp_table->clk_count = 1; return opp_table; /* opp_table->clk is still ERR_PTR(-ENOENT) */ } Consumers that only check IS_ERR(opp_table->clk) treat this as a valid clk and pass it straight into the clk consumer API. In particular, dev_pm_opp_set_rate() calls clk_round_rate(opp_table->clk, target_freq), and clk_round_rate() only guards against a NULL clk, so it dereferences the error pointer to read clk->exclusive_count and crashes: Unable to handle kernel NULL pointer dereference at virtual address 000000000000002e ... pc : clk_round_rate+0x3c/0x188 ... Call trace: clk_round_rate+0x3c/0x188 (P) dev_pm_opp_set_rate+0x114/0x33c Rather than teaching every clk consumer API to special-case ERR_PTR(-ENOENT), fix it at the source: use clk_get_optional() instead of clk_get() in _update_opp_table_clk(), which already translates -ENOENT into a NULL clk. This documents that the clock is genuinely optional for such devices, and keeps opp_table->clk holding either a valid clk or NULL, never a lingering -ENOENT error pointer. _opp_config_clk_single() is only wired up via opp_table->config_clks when a clk was actually found, and every other opp_table->clk consumer already tolerates NULL through the standard clk API (which treats a NULL clk as a no-op), so no other call site needs to change. Suggested-by: Sebastian Reichel Reviewed-by: Sebastian Reichel Signed-off-by: Praveen Talari Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 56 +++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index b6966e509f7d..cd0e82dae776 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1581,8 +1581,6 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, struct opp_table *opp_table, bool getclk) { - int ret; - /* * Return early if we don't need to get clk or we have already done it * earlier. @@ -1591,39 +1589,35 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, opp_table->clks) return opp_table; - /* Find clk for the device */ - opp_table->clk = clk_get(dev, NULL); + /* + * There are few platforms which don't want the OPP core to manage + * device's clock settings. In such cases neither the platform + * provides the clks explicitly to us, nor the DT contains a valid + * clk entry. The OPP nodes in DT may still contain "opp-hz" property + * though, which we need to parse and allow the platform to find an + * OPP based on freq later on. + * + * This is a simple solution to take care of such corner cases, i.e. + * make the clk_count 1, which lets us allocate space for frequency + * in opp->rates and also parse the entries in DT. Use + * clk_get_optional() instead of clk_get() so opp_table->clk stays + * NULL for such devices, instead of holding an ERR_PTR(-ENOENT) that + * consumers must remember to special-case. + */ + opp_table->clk = clk_get_optional(dev, NULL); - ret = PTR_ERR_OR_ZERO(opp_table->clk); - if (!ret) { + if (IS_ERR(opp_table->clk)) { + dev_pm_opp_put_opp_table(opp_table); + dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); + return ERR_CAST(opp_table->clk); + } + + if (opp_table->clk) opp_table->config_clks = _opp_config_clk_single; - opp_table->clk_count = 1; - return opp_table; - } - if (ret == -ENOENT) { - /* - * There are few platforms which don't want the OPP core to - * manage device's clock settings. In such cases neither the - * platform provides the clks explicitly to us, nor the DT - * contains a valid clk entry. The OPP nodes in DT may still - * contain "opp-hz" property though, which we need to parse and - * allow the platform to find an OPP based on freq later on. - * - * This is a simple solution to take care of such corner cases, - * i.e. make the clk_count 1, which lets us allocate space for - * frequency in opp->rates and also parse the entries in DT. - */ - opp_table->clk_count = 1; + opp_table->clk_count = 1; - dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); - return opp_table; - } - - dev_pm_opp_put_opp_table(opp_table); - dev_err_probe(dev, ret, "Couldn't find clock\n"); - - return ERR_PTR(ret); + return opp_table; } /* From b5e4771f20a37fdf19eac0824bf062dffb4e291f Mon Sep 17 00:00:00 2001 From: Priya Bala Govindasamy Date: Mon, 20 Jul 2026 20:50:35 +0000 Subject: [PATCH 73/76] rust: cpufreq: Add CPUFREQ_TABLE_END as last table entry in TableBuilder::to_table The `TableBuilder::to_table` function adds `Hertz(c_ulong::MAX).as_khz()` as the last frequency entry in the frequency table. But the C API expects the last entry to have frequency set to `CPUFREQ_TABLE_END` which is `~1u` as per include/linux/cpufreq.h. Fix this by setting the last frequency entry to `CPUFREQ_TABLE_END` instead of `Hertz(c_ulong::MAX).as_khz()`. Fixes: 2207856ff0bc8d953d6e89bda70b8978c2de8bab ("rust: cpufreq: Add initial abstractions for cpufreq framework") Reported-by: Dylan Zueck Reported-by: Yuan Tan Assisted-by: ChatGPT:gpt-5.6-terra Signed-off-by: Priya Bala Govindasamy Signed-off-by: Viresh Kumar --- rust/kernel/cpufreq.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index 58ac04c650a1..f360eb9578f3 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -361,23 +361,28 @@ pub fn new() -> Self { } } - /// Adds a new entry to the table. - pub fn add(&mut self, freq: Hertz, flags: u32, driver_data: u32) -> Result { + /// Adds a raw frequency-table entry. + fn push(&mut self, frequency: u32, flags: u32, driver_data: u32) -> Result { // Adds the new entry at the end of the vector. Ok(self.entries.push( bindings::cpufreq_frequency_table { flags, driver_data, - frequency: freq.as_khz() as u32, + frequency, }, GFP_KERNEL, )?) } + /// Adds a new entry to the table. + pub fn add(&mut self, freq: Hertz, flags: u32, driver_data: u32) -> Result { + self.push(freq.as_khz() as u32, flags, driver_data) + } + /// Consumes the [`TableBuilder`] and returns [`TableBox`]. pub fn to_table(mut self) -> Result { // Add last entry to the table. - self.add(Hertz(c_ulong::MAX), 0, 0)?; + self.push(bindings::CPUFREQ_TABLE_END as u32, 0, 0)?; TableBox::new(self.entries) } From 19c76bdd3fc02475c73c8576f6f4a55ff07886f1 Mon Sep 17 00:00:00 2001 From: Priya Bala Govindasamy Date: Mon, 20 Jul 2026 18:00:58 +0000 Subject: [PATCH 74/76] rust: cpufreq: Fix temporary write in Registration::bios_limit_callback In `Registration::bios_limit_callback`, the expression `&mut (unsafe { *limit })` creates a reference to a temporary copy of the value pointed to by `limit` on the stack. Therefore, writes made by `T::bios_limit` go to this temporary instead of the memory location pointed to by `limit`. Additionally, `limit` may be uninitialized, such as when `Registration::bios_limit_callback` is invoked by `show_bios_limit` in drivers/cpufreq/cpufreq.c. Therefore creating a reference to `limit` is unsound. Fix this by changing the signature of `T::bios_limit` to return the limit value. `Registration::bios_limit_callback` can then update `limit` directly. Fixes: c6af9a1191d042839e56abff69e8b0302d117988 ("rust: cpufreq: Extend abstractions for driver registration") Reported-by: Dylan Zueck Reported-by: Yuan Tan Assisted-by: ChatGPT:gpt-5.4 Signed-off-by: Priya Bala Govindasamy [ Viresh: Fix rustfmtcheck warning ] Signed-off-by: Viresh Kumar --- rust/kernel/cpufreq.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index f360eb9578f3..1158ef3a85f2 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -822,7 +822,9 @@ fn update_limits(_policy: &mut Policy) { } /// Driver's `bios_limit` callback. - fn bios_limit(_policy: &mut Policy, _limit: &mut u32) -> Result { + /// + /// Returns HW/BIOS max frequency limitations for the CPU. + fn bios_limit(_policy: &mut Policy) -> Result { build_error!(VTABLE_DEFAULT_ERROR) } @@ -1357,9 +1359,12 @@ impl Registration { from_result(|| { let mut policy = PolicyCpu::from_cpu(cpu_id)?; - + let val = T::bios_limit(&mut policy)?; // SAFETY: `limit` is guaranteed by the C code to be valid. - T::bios_limit(&mut policy, &mut (unsafe { *limit })).map(|()| 0) + unsafe { + *limit = val; + } + Ok(0) }) } From 22c23c72c3b21fa3ec3db5070dfc0582794e0ef9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 6 Aug 2026 07:09:02 +0200 Subject: [PATCH 75/76] cpufreq: imx6q: fix devres accumulation across driver rebind imx6_soc_volt is allocated with devm_kcalloc(cpu_dev, ...), where cpu_dev is the CPU device from get_cpu_device(0). That device is never unbound, so its devres list is never released, and imx6q_cpufreq_remove() does not free the array either. Every probe therefore adds an allocation that stays for the lifetime of the system. Allocate against the platform device instead. Its devres is released when the driver is unbound, which is exactly the lifetime the array wants: imx6q_set_target() reads it, and nothing may reach that after cpufreq_unregister_driver(). That makes the array actually go away on unbind, so also clear the file-scope pointer in remove and on the failed-probe path, rather than leave it pointing at memory devres is about to release. Tested by rebinding the driver on qemu's mcimx6ul-evk. Fixes: b4573d1d657a ("cpufreq: imx6q: correct VDDSOC/PU voltage scaling when cpufreq is changed") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Signed-off-by: Viresh Kumar --- drivers/cpufreq/imx6q-cpufreq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c index e93697d3edfd..8110c95059e0 100644 --- a/drivers/cpufreq/imx6q-cpufreq.c +++ b/drivers/cpufreq/imx6q-cpufreq.c @@ -400,7 +400,7 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev) } /* Make imx6_soc_volt array's size same as arm opp number */ - imx6_soc_volt = devm_kcalloc(cpu_dev, num, sizeof(*imx6_soc_volt), + imx6_soc_volt = devm_kcalloc(&pdev->dev, num, sizeof(*imx6_soc_volt), GFP_KERNEL); if (imx6_soc_volt == NULL) { ret = -ENOMEM; @@ -485,6 +485,7 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev) return 0; free_freq_table: + imx6_soc_volt = NULL; dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); out_free_opp: dev_pm_opp_of_remove_table(cpu_dev); @@ -506,6 +507,7 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev) static void imx6q_cpufreq_remove(struct platform_device *pdev) { cpufreq_unregister_driver(&imx6q_cpufreq_driver); + imx6_soc_volt = NULL; dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); dev_pm_opp_of_remove_table(cpu_dev); regulator_put(arm_reg); From 8c3afcf27fa4582c1ab912503dc8a4ebb8dc0f82 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 6 Aug 2026 07:02:39 +0200 Subject: [PATCH 76/76] cpufreq: imx6q: fix out-of-bounds write when probed more than once imx6_soc_volt is allocated fresh on every probe, sized to the number of ARM OPPs: imx6_soc_volt = devm_kcalloc(cpu_dev, num, sizeof(*imx6_soc_volt), GFP_KERNEL); but it is filled through soc_opp_count, which has static storage and is never reset. A second bind after an unbind keeps indexing from where the first one stopped, and writes past the end of the new array. Unbinding and rebinding the driver on qemu's mcimx6ul-evk, under KASAN: BUG: KASAN: slab-out-of-bounds in imx6q_cpufreq_probe+0x3b0/0xa34 Write of size 4 at addr c5e90480 by task binder/73 imx6q_cpufreq_probe from platform_probe+0x88/0xe4 platform_probe from really_probe+0x108/0x384 bind_store from kernfs_fop_write_iter+0x1b4/0x28c The write lands one u32 past the end of the allocation. soc_opp_count is only read a few lines below the loop that fills it, so it never needed static storage. Make it a local. Fixes: b4573d1d657a ("cpufreq: imx6q: correct VDDSOC/PU voltage scaling when cpufreq is changed") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Signed-off-by: Viresh Kumar --- drivers/cpufreq/imx6q-cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c index 8110c95059e0..731f5721ff1e 100644 --- a/drivers/cpufreq/imx6q-cpufreq.c +++ b/drivers/cpufreq/imx6q-cpufreq.c @@ -55,7 +55,6 @@ static unsigned int max_freq; static unsigned int transition_latency; static u32 *imx6_soc_volt; -static u32 soc_opp_count; static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index) { @@ -330,6 +329,7 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev) const struct property *prop; const __be32 *val; u32 nr, i, j; + u32 soc_opp_count = 0; cpu_dev = get_cpu_device(0); if (!cpu_dev) {