[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <5042c5ca-137c-4639-bd1a-12c836deabd0@rivosinc.com>
Date: Mon, 26 Aug 2024 09:20:05 +0200
From: Clément Léger <cleger@...osinc.com>
To: "Rafael J. Wysocki" <rafael@...nel.org>
Cc: Len Brown <lenb@...nel.org>, Robert Moore <robert.moore@...el.com>,
linux-acpi@...r.kernel.org, linux-kernel@...r.kernel.org,
acpica-devel@...ts.linux.dev
Subject: Re: [PATCH] ACPI: CPPC: Fix MASK_VAL() usage
On 02/08/2024 17:13, Rafael J. Wysocki wrote:
> On Mon, Jul 15, 2024 at 5:33 PM Clément Léger <cleger@...osinc.com> 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@...osinc.com>
>>
>> ---
>> 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
>>
Powered by blists - more mailing lists