Merge branch 'acpi-cppc'

Merge ACPI CPPC library updates the CPPC cpufreq driver updates based on
them for 7.4:

 - Fix multiple issues related to input validation, synchronization,
   access safety, memory layout and register field validation, and error
   handling in the ACPI CPPC library (Christian Loehle and Sumit Gupta)

 - Select the frequency-invariance callback per CPU in the CPPC cpufreq
   driver and create the FIE worker before enabling PCC callbacks in it
   to avoid queueing work through a missing worker and dereferencing a
   NULL pointer (Christian Loehle)

 - Update the CPPC cpufreq driver entry in MAINTAINERS and add a new
   entry for the ACPI CPPC library (Christian Loehle)

* acpi-cppc: (22 commits)
  MAINTAINERS: ACPI: Add a separate CPPC entry
  MAINTAINERS: cpufreq: Replace CPPC reviewer
  cpufreq: CPPC: Create the FIE worker before enabling PCC callbacks
  cpufreq: CPPC: Select the frequency-invariance callback per CPU
  ACPI: CPPC: Accept requests to retain immutable autonomous selection
  ACPI: CPPC: Propagate errors from cross-CPU FFH calls
  ACPI: CPPC: Validate FFH register fields before hardware access
  ACPI: CPPC: Keep Performance Limited clearable on NVIDIA T41
  ACPI: CPPC: Clear Performance Limited without a stale read
  ACPI: CPPC: Validate SystemIO overlaps across processors
  ACPI: CPPC: Validate PCC overlaps across processors
  ACPI: CPPC: Validate SystemIO register layouts
  ACPI: CPPC: Validate and access PCC register layouts
  ACPI: CPPC: Reject direct reads of write-only controls
  ACPI: CPPC: Reject unsafe cross-CPU SystemMemory RMW
  ACPI: CPPC: Release PCC data after probe failures
  ACPI: CPPC: Release CPC descriptors through kobject
  ACPI: CPPC: Serialize PCC EPP payload updates
  ACPI: CPPC: Serialize PCC single-register payload updates
  ACPI: CPPC: Propagate performance-control write errors
  ...
This commit is contained in:
Rafael J. Wysocki 2026-10-19 14:04:33 +02:00
commit 2da80f2b49
10 changed files with 2042 additions and 397 deletions

View File

@ -335,12 +335,16 @@ Description: Performance Limited
Read to check if platform throttling (thermal/power/current
limits) caused delivered performance to fall below the
requested level. A non-zero value indicates throttling occurred.
If firmware provides the register but Linux cannot access it
safely, reads return the literal string "<unsupported>".
Write the bitmask of bits to clear:
- 0x1 = clear bit 0 (desired performance excursion)
- 0x2 = clear bit 1 (minimum performance excursion)
- 0x3 = clear both bits
Write 0x3 to clear both bits. Selectively clearing one bit is
not supported because ACPI does not define the effect of writing
one to these status bits and an interlocked read-modify-write is
not generally available.
Writing 0x3 returns -EOPNOTSUPP when firmware describes the
register using an access which Linux cannot clear safely; reads
may remain available in that case.
The platform sets these bits; OSPM can only clear them.

View File

@ -327,6 +327,17 @@ F: drivers/acpi/acpica/
F: include/acpi/
F: tools/power/acpi/
ACPI CPPC
M: "Rafael J. Wysocki" <rafael@kernel.org>
M: Christian Loehle <christian.loehle@arm.com>
L: linux-acpi@vger.kernel.org
L: linux-pm@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
F: Documentation/admin-guide/acpi/cppc_sysfs.rst
F: drivers/acpi/cppc_acpi.c
F: include/acpi/cppc_acpi.h
ACPI FOR ARM64 (ACPI/arm64)
M: Lorenzo Pieralisi <lpieralisi@kernel.org>
M: Hanjun Guo <guohanjun@huawei.com>
@ -6765,7 +6776,7 @@ M: "Rafael J. Wysocki" <rafael@kernel.org>
M: Viresh Kumar <viresh.kumar@linaro.org>
R: Jie Zhan <zhanjie9@hisilicon.com>
R: Lifeng Zheng <zhenglifeng1@huawei.com>
R: Pierre Gondois <pierre.gondois@arm.com>
R: Christian Loehle <christian.loehle@arm.com>
R: Sumit Gupta <sumitg@nvidia.com>
L: linux-pm@vger.kernel.org
S: Maintained

View File

@ -426,7 +426,7 @@ int counters_read_on_cpu(int cpu, smp_call_func_t func, void *val)
return -EPERM;
func(val);
} else {
smp_call_function_single(cpu, func, val, 1);
return smp_call_function_single(cpu, func, val, 1);
}
return 0;
@ -470,6 +470,12 @@ static void amu_read_core_const_ctrs(void *val)
cpu_read_corecnt(&ctrs->corecnt);
}
static bool cpc_ffh_reg_valid(const struct cpc_reg *reg)
{
return reg->bit_width && reg->bit_width <= 64 &&
reg->bit_offset <= 64 - reg->bit_width;
}
static u64 cpc_ffh_extract_bits(const struct cpc_reg *reg, u64 val)
{
val &= GENMASK_ULL(reg->bit_offset + reg->bit_width - 1,
@ -506,7 +512,8 @@ int cpc_read_ffh_fb_ctrs(int cpu, struct cpc_reg *reg1, u64 *val1,
struct amu_ffh_ctrs ctrs;
int ret;
if (!is_amu_ctr_reg(reg1) || !is_amu_ctr_reg(reg2))
if (!is_amu_ctr_reg(reg1) || !is_amu_ctr_reg(reg2) ||
!cpc_ffh_reg_valid(reg1) || !cpc_ffh_reg_valid(reg2))
return -EINVAL;
ret = counters_read_on_cpu(cpu, amu_read_core_const_ctrs, &ctrs);
@ -530,6 +537,9 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val)
{
int ret = -EOPNOTSUPP;
if (!cpc_ffh_reg_valid(reg))
return -EINVAL;
switch ((u64)reg->address) {
case CPC_FFH_CTR_CORE:
ret = counters_read_on_cpu(cpu, cpu_read_corecnt, val);

View File

@ -5,6 +5,7 @@
*/
#include <linux/bitfield.h>
#include <linux/limits.h>
#include <acpi/cppc_acpi.h>
#include <asm/msr.h>
@ -45,10 +46,20 @@ bool cpc_ffh_supported(void)
return true;
}
static bool cpc_ffh_reg_valid(const struct cpc_reg *reg)
{
return reg->address <= U32_MAX && reg->bit_width &&
reg->bit_width <= 64 &&
reg->bit_offset <= 64 - reg->bit_width;
}
int cpc_read_ffh(int cpunum, struct cpc_reg *reg, u64 *val)
{
int err;
if (!cpc_ffh_reg_valid(reg))
return -EINVAL;
err = rdmsrq_safe_on_cpu(cpunum, reg->address, val);
if (!err) {
u64 mask = GENMASK_ULL(reg->bit_offset + reg->bit_width - 1,
@ -65,6 +76,9 @@ int cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val)
u64 rd_val;
int err;
if (!cpc_ffh_reg_valid(reg))
return -EINVAL;
err = rdmsrq_safe_on_cpu(cpunum, reg->address, &rd_val);
if (!err) {
u64 mask = GENMASK_ULL(reg->bit_offset + reg->bit_width - 1,

File diff suppressed because it is too large Load Diff

View File

@ -97,6 +97,7 @@ bool cpc_ffh_supported(void)
int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val)
{
struct sbi_cppc_data data;
int ret;
if (WARN_ON_ONCE(irqs_disabled()))
return -EPERM;
@ -107,19 +108,27 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val)
data.reg = FFH_CPPC_SBI_REG(reg->address);
smp_call_function_single(cpu, sbi_cppc_read, &data, 1);
ret = smp_call_function_single(cpu, sbi_cppc_read, &data, 1);
if (ret)
return ret;
if (data.ret.error)
return sbi_err_map_linux_errno(data.ret.error);
*val = data.ret.value;
return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
return 0;
} else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) {
data.reg = FFH_CPPC_CSR_NUM(reg->address);
smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1);
ret = smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1);
if (ret)
return ret;
if (data.ret.error)
return data.ret.error;
*val = data.ret.value;
return data.ret.error;
return 0;
}
return -EINVAL;
@ -128,6 +137,7 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val)
int cpc_write_ffh(int cpu, struct cpc_reg *reg, u64 val)
{
struct sbi_cppc_data data;
int ret;
if (WARN_ON_ONCE(irqs_disabled()))
return -EPERM;
@ -139,14 +149,18 @@ int cpc_write_ffh(int cpu, struct cpc_reg *reg, u64 val)
data.reg = FFH_CPPC_SBI_REG(reg->address);
data.val = val;
smp_call_function_single(cpu, sbi_cppc_write, &data, 1);
ret = smp_call_function_single(cpu, sbi_cppc_write, &data, 1);
if (ret)
return ret;
return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
} else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) {
data.reg = FFH_CPPC_CSR_NUM(reg->address);
data.val = val;
smp_call_function_single(cpu, cppc_ffh_csr_write, &data, 1);
ret = smp_call_function_single(cpu, cppc_ffh_csr_write, &data, 1);
if (ret)
return ret;
return data.ret.error;
}

View File

@ -1069,19 +1069,26 @@ EXPORT_SYMBOL(acpi_dev_is_video_device);
* @plat: pointer to acpi_platform_list table terminated by a NULL entry
*
* Return the matched index if the system is found in the platform list.
* Otherwise, return a negative error code.
* Return -ENODEV for no match, or another negative error code if a table
* header could not be read and no entry matched.
*/
int acpi_match_platform_list(const struct acpi_platform_list *plat)
{
struct acpi_table_header hdr;
acpi_status status;
int ret = -ENODEV;
int idx = 0;
if (acpi_disabled)
return -ENODEV;
for (; plat->oem_id[0]; plat++, idx++) {
if (ACPI_FAILURE(acpi_get_table_header(plat->table, 0, &hdr)))
status = acpi_get_table_header(plat->table, 0, &hdr);
if (ACPI_FAILURE(status)) {
if (status != AE_NOT_FOUND)
ret = status == AE_NO_MEMORY ? -ENOMEM : -EIO;
continue;
}
if (strncmp(plat->oem_id, hdr.oem_id, ACPI_OEM_ID_SIZE))
continue;
@ -1096,6 +1103,6 @@ int acpi_match_platform_list(const struct acpi_platform_list *plat)
return idx;
}
return -ENODEV;
return ret;
}
EXPORT_SYMBOL(acpi_match_platform_list);

View File

@ -544,7 +544,11 @@ static int shmem_update_perf(struct cpufreq_policy *policy, u8 min_perf,
u8 des_perf, u8 max_perf, u8 epp, bool fast_switch)
{
struct amd_cpudata *cpudata = policy->driver_data;
struct cppc_perf_ctrls perf_ctrls;
struct cppc_perf_ctrls perf_ctrls = {
.max_perf = max_perf,
.min_perf = min_perf,
.desired_perf = des_perf,
};
u64 value, prev;
int ret;
@ -577,10 +581,6 @@ static int shmem_update_perf(struct cpufreq_policy *policy, u8 min_perf,
if (value == prev)
return 0;
perf_ctrls.max_perf = max_perf;
perf_ctrls.min_perf = min_perf;
perf_ctrls.desired_perf = des_perf;
ret = cppc_set_perf(cpudata->cpu, &perf_ctrls);
if (ret)
return ret;
@ -1794,7 +1794,7 @@ EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status);
int amd_pstate_update_status(const char *buf, size_t size)
{
int mode_idx;
int cpu, mode_idx;
if (size > strlen("passive") || size < strlen("active"))
return -EINVAL;
@ -1803,12 +1803,23 @@ int amd_pstate_update_status(const char *buf, size_t size)
if (mode_idx < 0)
return mode_idx;
if (mode_state_machine[cppc_state][mode_idx]) {
guard(mutex)(&amd_pstate_driver_lock);
return mode_state_machine[cppc_state][mode_idx](mode_idx);
guard(mutex)(&amd_pstate_driver_lock);
if (!mode_state_machine[cppc_state][mode_idx])
return 0;
if (mode_idx == AMD_PSTATE_PASSIVE &&
!cpu_feature_enabled(X86_FEATURE_CPPC)) {
guard(cpus_read_lock)();
/* Check offline CPUs too, before changing or removing the driver. */
for_each_present_cpu(cpu) {
if (cppc_auto_sel_is_immutable(cpu))
return -EOPNOTSUPP;
}
}
return 0;
return mode_state_machine[cppc_state][mode_idx](mode_idx);
}
EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_update_status);

View File

@ -18,6 +18,7 @@
#include <linux/cpufreq.h>
#include <linux/irq_work.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <linux/time.h>
#include <linux/vmalloc.h>
#include <uapi/linux/sched/types.h>
@ -41,6 +42,7 @@ MODULE_PARM_DESC(fie_disabled, "Disable Frequency Invariance Engine (FIE)");
/* Frequency invariance support */
struct cppc_freq_invariance {
int cpu;
bool pcc_work_initialized;
struct irq_work irq_work;
struct kthread_work work;
struct cppc_perf_fb_ctrs prev_perf_fb_ctrs;
@ -49,10 +51,12 @@ struct cppc_freq_invariance {
static DEFINE_PER_CPU(struct cppc_freq_invariance, cppc_freq_inv);
static struct kthread_worker *kworker_fie;
static DEFINE_MUTEX(cppc_fie_lock);
static int cppc_perf_from_fbctrs(u64 reference_perf,
struct cppc_perf_fb_ctrs *fb_ctrs_t0,
struct cppc_perf_fb_ctrs *fb_ctrs_t1);
static int cppc_fie_kworker_init(void);
/**
* __cppc_scale_freq_tick - CPPC arch_freq_scale updater for frequency invariance
@ -149,7 +153,7 @@ static struct scale_freq_data cppc_sftd_pcc = {
static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy)
{
struct scale_freq_data *sftd = &cppc_sftd;
struct scale_freq_data *sftd;
struct cppc_freq_invariance *cppc_fi;
int cpu, ret;
@ -161,9 +165,12 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy)
cppc_fi->cpu = cpu;
cppc_fi->cpu_data = policy->driver_data;
if (cppc_perf_ctrs_in_pcc_cpu(cpu)) {
if (cppc_fie_kworker_init())
return;
kthread_init_work(&cppc_fi->work, cppc_scale_freq_workfn);
init_irq_work(&cppc_fi->irq_work, cppc_irq_work);
sftd = &cppc_sftd_pcc;
cppc_fi->pcc_work_initialized = true;
}
ret = cppc_get_perf_ctrs(cpu, &cppc_fi->prev_perf_fb_ctrs);
@ -179,17 +186,20 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy)
}
}
/* Register for freq-invariance */
topology_set_scale_freq_source(sftd, policy->cpus);
/* A shared policy may contain both PCC and non-PCC counters. */
for_each_cpu(cpu, policy->cpus) {
cppc_fi = &per_cpu(cppc_freq_inv, cpu);
if (cppc_fi->pcc_work_initialized)
sftd = &cppc_sftd_pcc;
else
sftd = &cppc_sftd;
topology_set_scale_freq_source(sftd, cpumask_of(cpu));
}
}
/*
* We free all the resources on policy's removal and not on CPU removal as the
* irq-work are per-cpu and the hotplug core takes care of flushing the pending
* irq-works (hint: smpcfd_dying_cpu()) on CPU hotplug. Even if the kthread-work
* fires on another CPU after the concerned CPU is removed, it won't harm.
*
* We just need to make sure to remove them all on policy->exit().
* Drain work initialized by this policy even if processor removal has
* already unpublished the CPU's CPC descriptor.
*/
static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy)
{
@ -203,16 +213,18 @@ static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy)
topology_clear_scale_freq_source(SCALE_FREQ_SOURCE_CPPC, policy->related_cpus);
for_each_cpu(cpu, policy->related_cpus) {
if (!cppc_perf_ctrs_in_pcc_cpu(cpu))
continue;
cppc_fi = &per_cpu(cppc_freq_inv, cpu);
if (!cppc_fi->pcc_work_initialized)
continue;
irq_work_sync(&cppc_fi->irq_work);
kthread_cancel_work_sync(&cppc_fi->work);
cppc_fi->pcc_work_initialized = false;
}
}
static void cppc_fie_kworker_init(void)
static int cppc_fie_kworker_init(void)
{
struct kthread_worker *worker;
struct sched_attr attr = {
.size = sizeof(struct sched_attr),
.sched_policy = SCHED_DEADLINE,
@ -228,23 +240,28 @@ static void cppc_fie_kworker_init(void)
};
int ret;
kworker_fie = kthread_run_worker(0, "cppc_fie");
if (IS_ERR(kworker_fie)) {
guard(mutex)(&cppc_fie_lock);
if (kworker_fie)
return 0;
worker = kthread_run_worker(0, "cppc_fie");
if (IS_ERR(worker)) {
pr_warn("%s: failed to create kworker_fie: %ld\n", __func__,
PTR_ERR(kworker_fie));
fie_disabled = FIE_DISABLED;
kworker_fie = NULL;
return;
PTR_ERR(worker));
return PTR_ERR(worker);
}
ret = sched_setattr_nocheck(kworker_fie->task, &attr);
ret = sched_setattr_nocheck(worker->task, &attr);
if (ret) {
pr_warn("%s: failed to set SCHED_DEADLINE: %d\n", __func__,
ret);
kthread_destroy_worker(kworker_fie);
fie_disabled = FIE_DISABLED;
kworker_fie = NULL;
kthread_destroy_worker(worker);
return ret;
}
kworker_fie = worker;
return 0;
}
static void __init cppc_freq_invariance_init(void)
@ -259,11 +276,6 @@ static void __init cppc_freq_invariance_init(void)
fie_disabled = FIE_ENABLED;
}
}
if (fie_disabled || !perf_ctrs_in_pcc)
return;
cppc_fie_kworker_init();
}
static void cppc_freq_invariance_exit(void)
@ -883,6 +895,7 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy,
const char *buf, size_t count)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
bool old_auto_sel = cpu_data->perf_ctrls.auto_sel;
bool val;
int ret;
@ -911,8 +924,8 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy,
if (ret) {
cpu_data->perf_ctrls.min_perf = old_min_perf;
cpu_data->perf_ctrls.max_perf = old_max_perf;
cppc_set_auto_sel(policy->cpu, false);
cpu_data->perf_ctrls.auto_sel = false;
cppc_set_auto_sel(policy->cpu, old_auto_sel);
cpu_data->perf_ctrls.auto_sel = old_auto_sel;
return ret;
}
}

View File

@ -72,11 +72,16 @@ struct cpc_register_resource {
struct {
struct cpc_reg reg;
bool use_rmw_lock;
bool read_unsupported;
bool write_unsupported;
};
u64 int_value;
} cpc_entry;
};
struct cpc_sysmem_node;
struct cpc_non_mmio_node;
/* Container to hold the CPC details for each CPU */
struct cpc_desc {
int num_entries;
@ -84,10 +89,12 @@ struct cpc_desc {
int cpu_id;
int write_cmd_status;
int write_cmd_id;
/* Lock used for RMW operations in cpc_write() */
/* Serialize partial SystemMemory writes within this descriptor. */
raw_spinlock_t rmw_lock;
struct cpc_register_resource cpc_regs[MAX_CPC_REG_ENT];
struct acpi_psd_package domain_info;
struct cpc_sysmem_node *sysmem_nodes;
struct cpc_non_mmio_node *non_mmio_nodes;
struct kobject kobj;
};
@ -141,6 +148,8 @@ struct cppc_perf_ctrls {
u32 desired_perf;
u32 energy_perf;
bool auto_sel;
/* Allow an explicit zero minimum; otherwise zero omits the update. */
bool min_perf_valid;
};
struct cppc_perf_fb_ctrs {
@ -188,6 +197,7 @@ extern int cppc_set_epp(int cpu, u64 epp_val);
extern int cppc_get_auto_act_window(int cpu, u64 *auto_act_window);
extern int cppc_set_auto_act_window(int cpu, u64 auto_act_window);
extern int cppc_get_auto_sel(int cpu, bool *enable);
bool cppc_auto_sel_is_immutable(int cpu);
extern int cppc_set_auto_sel(int cpu, bool enable);
extern int cppc_get_perf_limited(int cpu, u64 *perf_limited);
extern int cppc_set_perf_limited(int cpu, u64 bits_to_clear);
@ -289,6 +299,12 @@ static inline int cppc_get_auto_sel(int cpu, bool *enable)
{
return -EOPNOTSUPP;
}
static inline bool cppc_auto_sel_is_immutable(int cpu)
{
return false;
}
static inline int cppc_set_auto_sel(int cpu, bool enable)
{
return -EOPNOTSUPP;