diff mbox series

ACPI: CPPC: Fix MASK_VAL() usage

Message ID 20240715153336.3720653-1-cleger@rivosinc.com (mailing list archive)
State Changes Requested, archived
Headers show
Series ACPI: CPPC: Fix MASK_VAL() usage | expand

Commit Message

Clément Léger July 15, 2024, 3:33 p.m. UTC
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@rivosinc.com>

---
 drivers/acpi/cppc_acpi.c | 44 ++++++++++++++++++++++++++++++++++++----
 include/acpi/cppc_acpi.h |  2 ++
 2 files changed, 42 insertions(+), 4 deletions(-)

Comments

Rafael J. Wysocki Aug. 2, 2024, 3:13 p.m. UTC | #1
On Mon, Jul 15, 2024 at 5:33 PM Clément Léger <cleger@rivosinc.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@rivosinc.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 = &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
>
Clément Léger Aug. 26, 2024, 7:20 a.m. UTC | #2
On 02/08/2024 17:13, Rafael J. Wysocki wrote:
> On Mon, Jul 15, 2024 at 5:33 PM Clément Léger <cleger@rivosinc.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@rivosinc.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 = &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
>>
diff mbox series

Patch

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 = &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;
+		};
+		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;