On 02/08/2024 17:13, Rafael J. Wysocki wrote: > On Mon, Jul 15, 2024 at 5:33 PM Clément Léger <cleger@xxxxxxxxxxxx> wrote: >> >> MASK_VAL() was added a way to handle bit_offset and bit_width for >> registers located in system memory address space. However, while suited >> for reading, it does not work for writing and result in corrupted >> registers when writing values with bit_offset > 0. Moreover, when a >> register is collocated with another one at the same address but with a >> different mask, the current code results in the other registers being >> overwritten with 0s. The write procedure for SYSTEM_MEMORY registers >> should actually read the value, mask it, update it and write it with the >> updated value. Moreover, since registers can be located in the same >> word, we must take care of locking the access before doing it. We should >> potentially use a global lock since we don't know in if register >> addresses aren't shared with another _CPC package but better not >> encourage vendors to do so. Assume that registers can use the same word >> inside a _CPC package and thus, use a per _CPC package lock. >> >> Fixes: 2f4a4d63a193 ("ACPI: CPPC: Use access_width over bit_width for system memory accesses") >> Signed-off-by: Clément Léger <cleger@xxxxxxxxxxxx> >> >> --- >> drivers/acpi/cppc_acpi.c | 44 ++++++++++++++++++++++++++++++++++++---- >> include/acpi/cppc_acpi.h | 2 ++ >> 2 files changed, 42 insertions(+), 4 deletions(-) >> >> diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c >> index 1d857978f5f4..2e99cf1842ee 100644 >> --- a/drivers/acpi/cppc_acpi.c >> +++ b/drivers/acpi/cppc_acpi.c >> @@ -170,8 +170,11 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, wraparound_time); >> #define GET_BIT_WIDTH(reg) ((reg)->access_width ? (8 << ((reg)->access_width - 1)) : (reg)->bit_width) >> >> /* Shift and apply the mask for CPC reads/writes */ >> -#define MASK_VAL(reg, val) (((val) >> (reg)->bit_offset) & \ >> +#define MASK_VAL_READ(reg, val) (((val) >> (reg)->bit_offset) & \ >> GENMASK(((reg)->bit_width) - 1, 0)) >> +#define MASK_VAL_WRITE(reg, prev_val, val) \ >> + ((((val) & GENMASK(((reg)->bit_width) - 1, 0)) << (reg)->bit_offset) | \ >> + ((prev_val) & ~(GENMASK(((reg)->bit_width) - 1, 0) << (reg)->bit_offset))) \ >> >> static ssize_t show_feedback_ctrs(struct kobject *kobj, >> struct kobj_attribute *attr, char *buf) >> @@ -857,6 +860,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) >> >> /* Store CPU Logical ID */ >> cpc_ptr->cpu_id = pr->id; >> + spin_lock_init(&cpc_ptr->rmw_lock); >> >> /* Parse PSD data for this CPU */ >> ret = acpi_get_psd(cpc_ptr, handle); >> @@ -1062,7 +1066,7 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) >> } >> >> if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) >> - *val = MASK_VAL(reg, *val); >> + *val = MASK_VAL_READ(reg, *val); >> >> return 0; >> } >> @@ -1071,9 +1075,11 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) >> { >> int ret_val = 0; >> int size; >> + u64 prev_val; >> void __iomem *vaddr = NULL; >> int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); >> struct cpc_reg *reg = ®_res->cpc_entry.reg; >> + struct cpc_desc *cpc_desc; >> >> size = GET_BIT_WIDTH(reg); >> >> @@ -1106,8 +1112,34 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) >> return acpi_os_write_memory((acpi_physical_address)reg->address, >> val, size); >> >> - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) >> - val = MASK_VAL(reg, val); >> + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { >> + cpc_desc = per_cpu(cpc_desc_ptr, cpu); >> + if (!cpc_desc) { >> + pr_debug("No CPC descriptor for CPU:%d\n", cpu); >> + return -ENODEV; >> + } >> + >> + spin_lock(&cpc_desc->rmw_lock); >> + switch (size) { >> + case 8: >> + prev_val = readb_relaxed(vaddr); >> + break; >> + case 16: >> + prev_val = readw_relaxed(vaddr); >> + break; >> + case 32: >> + prev_val = readl_relaxed(vaddr); >> + break; >> + case 64: >> + prev_val = readq_relaxed(vaddr); >> + break; >> + default: >> + ret_val = -EFAULT; >> + goto out_unlock; > > I would do > > spin_unlock(&cpc_desc->rmw_lock); > return -EFAUL; > > here to avoid the check below which is redundant in this path and the > label would not be necessary then. Acked, I'll send a V2 with these modifications. Thanks, Clément > > LGTM otherwise. > >> + }; >> + val = MASK_VAL_WRITE(reg, prev_val, val); >> + val |= prev_val; >> + } >> >> switch (size) { >> case 8: >> @@ -1134,6 +1166,10 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) >> break; >> } >> >> +out_unlock: >> + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) >> + spin_unlock(&cpc_desc->rmw_lock); >> + >> return ret_val; >> } >> >> diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h >> index 930b6afba6f4..e1720d930666 100644 >> --- a/include/acpi/cppc_acpi.h >> +++ b/include/acpi/cppc_acpi.h >> @@ -64,6 +64,8 @@ struct cpc_desc { >> int cpu_id; >> int write_cmd_status; >> int write_cmd_id; >> + /* Lock used for RMW operations in cpc_write() */ >> + spinlock_t rmw_lock; >> struct cpc_register_resource cpc_regs[MAX_CPC_REG_ENT]; >> struct acpi_psd_package domain_info; >> struct kobject kobj; >> -- >> 2.45.2 >>