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. 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 >