mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ACPI: CPPC: Validate and access PCC register layouts
PCC GAS Access Size identifies the subspace while Bit Width identifies
the field size. Support zero-offset byte-multiple fields up to 64 bits
with byte-oriented I/O and explicit little-endian encoding. This accepts
the unaligned 32-bit Delivered Performance Counter in the ACPI
implementation example and read-only widths such as 24 bits. A
per-subspace raw spinlock serializes aliased payload copies made under
the shared side of pcc_lock.
Defer unsupported-field decisions until the package is known. A provided
but inaccessible CPPC Enable or Autonomous Selection Enable control
fails probe. OSPM must set the former and cannot discard the latter
without knowing the hardware autonomous-mode state. An inaccessible
Desired control can be ignored only for immutable autonomous operation.
If both Minimum and Maximum controls are inaccessible, disable the pair
coherently; a one-sided bound fails probe. Other inaccessible optional
controls are marked absent.
Validate PCC ranges after the eight-byte header and reject overlapping
logical ranges when either is writable. Bit-level PCC RMW remains
unsupported.
Probe now rejects or disables unsupported writable PCC widths before
publishing the descriptor. Remove the earlier setter preflights which
repeat that validation.
Fixes: 85b1407bf6 ("ACPI / CPPC: Make CPPC ACPI driver aware of PCC subspace IDs")
Signed-off-by: Christian Loehle <christian.loehle@arm.com>
Tested-by: Sumit Gupta <sumitg@nvidia.com>
Link: https://patch.msgid.link/20260916162805.1039247-11-christian.loehle@arm.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
parent
09d911c996
commit
fd1bb9a6cb
|
|
@ -74,6 +74,8 @@ struct cppc_pcc_data {
|
|||
* Take write_lock for all purposes which gives exclusive access
|
||||
*/
|
||||
struct rw_semaphore pcc_lock;
|
||||
/* Serialize byte-oriented accesses to aliased PCC payload fields. */
|
||||
raw_spinlock_t payload_lock;
|
||||
|
||||
/* Wait queue for CPUs whose requests were batched */
|
||||
wait_queue_head_t pcc_write_wait_q;
|
||||
|
|
@ -132,9 +134,11 @@ static struct cpc_sysmem_node *cpc_sysmem_next(struct cpc_sysmem_node *node,
|
|||
return cpc_sysmem_itree_iter_next(node, start, last);
|
||||
}
|
||||
|
||||
#define CPC_PCC_HEADER_SIZE 0x8
|
||||
|
||||
/* pcc mapped address + header size + offset within PCC subspace */
|
||||
#define GET_PCC_VADDR(offs, pcc_ss_id) (pcc_data[pcc_ss_id]->pcc_channel->shmem + \
|
||||
0x8 + (offs))
|
||||
CPC_PCC_HEADER_SIZE + (offs))
|
||||
|
||||
/* Check if a CPC register is in PCC */
|
||||
#define CPC_IN_PCC(cpc) ((cpc)->type == ACPI_TYPE_BUFFER && \
|
||||
|
|
@ -286,19 +290,6 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, wraparound_time);
|
|||
(reg)->space_id != ACPI_ADR_SPACE_PLATFORM_COMM) ? \
|
||||
(8 << ((reg)->access_width - 1)) : (reg)->bit_width)
|
||||
|
||||
static bool cpc_pcc_write_supported(const struct cpc_register_resource *reg)
|
||||
{
|
||||
switch (GET_BIT_WIDTH(®->cpc_entry.reg)) {
|
||||
case 8:
|
||||
case 16:
|
||||
case 32:
|
||||
case 64:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Shift and apply the mask for CPC reads/writes */
|
||||
#define MASK_VAL_READ(reg, val) (((val) >> (reg)->bit_offset) & \
|
||||
GENMASK(((reg)->bit_width) - 1, 0))
|
||||
|
|
@ -472,6 +463,196 @@ static int cpc_validate_sysmem_reg(struct cpc_desc *cpc_desc,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static bool cpc_immutable_autonomous(const struct cpc_desc *cpc_desc)
|
||||
{
|
||||
const struct cpc_register_resource *reg;
|
||||
|
||||
reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE];
|
||||
return osc_sb_cppc2_support_acked && reg->type == ACPI_TYPE_INTEGER &&
|
||||
reg->cpc_entry.int_value == 1;
|
||||
}
|
||||
|
||||
static int cpc_resolve_unsupported(struct cpc_desc *cpc_desc,
|
||||
u32 unsupported)
|
||||
{
|
||||
unsigned int i;
|
||||
u32 bounds = BIT(MIN_PERF) | BIT(MAX_PERF);
|
||||
bool min_unusable, max_unusable;
|
||||
|
||||
if (unsupported & bounds) {
|
||||
min_unusable = (unsupported & BIT(MIN_PERF)) ||
|
||||
!cpc_is_writable(&cpc_desc->cpc_regs[MIN_PERF]);
|
||||
max_unusable = (unsupported & BIT(MAX_PERF)) ||
|
||||
!cpc_is_writable(&cpc_desc->cpc_regs[MAX_PERF]);
|
||||
if (min_unusable && max_unusable) {
|
||||
pr_warn("CPU%d: ignoring inaccessible Minimum and Maximum Performance registers\n",
|
||||
cpc_desc->cpu_id);
|
||||
cpc_disable_reg(cpc_desc, MIN_PERF);
|
||||
cpc_disable_reg(cpc_desc, MAX_PERF);
|
||||
unsupported &= ~bounds;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < cpc_desc->num_entries - 2; i++) {
|
||||
if (!(unsupported & BIT(i)))
|
||||
continue;
|
||||
|
||||
if (i == DESIRED_PERF && cpc_immutable_autonomous(cpc_desc)) {
|
||||
pr_warn("CPU%d: ignoring inaccessible Desired Performance register in autonomous mode\n",
|
||||
cpc_desc->cpu_id);
|
||||
cpc_disable_reg(cpc_desc, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* A present Enable or Autonomous Selection control must remain
|
||||
* usable. Disabling the latter could leave autonomous selection
|
||||
* enabled while OSPM believes that it has disabled it.
|
||||
*/
|
||||
if (i == ENABLE ||
|
||||
(i == AUTO_SEL_ENABLE && cpc_entry_present(&cpc_desc->cpc_regs[i])) ||
|
||||
i == MIN_PERF || i == MAX_PERF ||
|
||||
!IS_OPTIONAL_CPC_REG(i)) {
|
||||
pr_err("CPU%d: cannot access _CPC register %u\n",
|
||||
cpc_desc->cpu_id, i);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pr_warn("CPU%d: ignoring inaccessible optional _CPC register %u\n",
|
||||
cpc_desc->cpu_id, i);
|
||||
cpc_disable_reg(cpc_desc, i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpc_validate_required_controls(struct cpc_desc *cpc_desc)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
/*
|
||||
* Performance Limited is required by the specification, but tolerate a
|
||||
* NULL descriptor used by firmware which cannot report limiting events.
|
||||
* CPPC control does not depend on this status.
|
||||
*/
|
||||
for (i = 0; i < cpc_desc->num_entries - 2; i++) {
|
||||
if (i != DESIRED_PERF && i != PERF_LIMITED &&
|
||||
!IS_OPTIONAL_CPC_REG(i) &&
|
||||
!cpc_entry_present(&cpc_desc->cpc_regs[i])) {
|
||||
pr_debug("CPU:%d lacks mandatory _CPC register %u\n",
|
||||
cpc_desc->cpu_id, i);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desired may be absent only for immutable autonomous operation. */
|
||||
if (!cpc_is_writable(&cpc_desc->cpc_regs[DESIRED_PERF]) &&
|
||||
!cpc_immutable_autonomous(cpc_desc)) {
|
||||
pr_debug("CPU:%d lacks a writable Desired Performance register\n",
|
||||
cpc_desc->cpu_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpc_validate_bound_controls(struct cpc_desc *cpc_desc)
|
||||
{
|
||||
bool have_min, have_max;
|
||||
|
||||
have_min = cpc_is_writable(&cpc_desc->cpc_regs[MIN_PERF]);
|
||||
have_max = cpc_is_writable(&cpc_desc->cpc_regs[MAX_PERF]);
|
||||
if (have_min != have_max) {
|
||||
pr_err("CPU%d: _CPC must provide both Minimum and Maximum Performance or neither\n",
|
||||
cpc_desc->cpu_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cpc_validate_pcc_bounds(struct cpc_desc *cpc_desc,
|
||||
struct cppc_pcc_data *data,
|
||||
u32 *unsupported)
|
||||
{
|
||||
u64 shmem_size = data->pcc_channel->shmem_size;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < cpc_desc->num_entries - 2; i++) {
|
||||
struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i];
|
||||
struct cpc_reg *gas;
|
||||
u64 access_size;
|
||||
|
||||
if ((*unsupported & BIT(i)) || !CPC_SUPPORTED(reg) ||
|
||||
!CPC_IN_PCC(reg))
|
||||
continue;
|
||||
|
||||
gas = ®->cpc_entry.reg;
|
||||
access_size = gas->bit_width / 8;
|
||||
if (shmem_size >= CPC_PCC_HEADER_SIZE &&
|
||||
gas->address <= shmem_size - CPC_PCC_HEADER_SIZE &&
|
||||
access_size <= shmem_size - CPC_PCC_HEADER_SIZE - gas->address)
|
||||
continue;
|
||||
|
||||
pr_debug("CPU%d: _CPC register %u exceeds the PCC shared region\n",
|
||||
cpc_desc->cpu_id, i);
|
||||
*unsupported |= BIT(i);
|
||||
}
|
||||
}
|
||||
|
||||
static u64 cpc_non_mmio_access_size(const struct cpc_register_resource *reg)
|
||||
{
|
||||
const struct cpc_reg *gas = ®->cpc_entry.reg;
|
||||
|
||||
if (gas->space_id == ACPI_ADR_SPACE_PLATFORM_COMM)
|
||||
return gas->bit_width / 8;
|
||||
|
||||
return cpc_reg_access_width(gas) / 8;
|
||||
}
|
||||
|
||||
static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc,
|
||||
u8 space_id, const char *name)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0; i < cpc_desc->num_entries - 2; i++) {
|
||||
struct cpc_register_resource *a = &cpc_desc->cpc_regs[i];
|
||||
const struct cpc_reg *a_gas;
|
||||
u64 a_size;
|
||||
|
||||
if (!CPC_SUPPORTED(a) || a->type != ACPI_TYPE_BUFFER ||
|
||||
a->cpc_entry.reg.space_id != space_id)
|
||||
continue;
|
||||
|
||||
a_gas = &a->cpc_entry.reg;
|
||||
a_size = cpc_non_mmio_access_size(a);
|
||||
for (j = i + 1; j < cpc_desc->num_entries - 2; j++) {
|
||||
struct cpc_register_resource *b = &cpc_desc->cpc_regs[j];
|
||||
const struct cpc_reg *b_gas;
|
||||
u64 b_size;
|
||||
|
||||
if (!CPC_SUPPORTED(b) || b->type != ACPI_TYPE_BUFFER ||
|
||||
b->cpc_entry.reg.space_id != space_id)
|
||||
continue;
|
||||
|
||||
b_gas = &b->cpc_entry.reg;
|
||||
b_size = cpc_non_mmio_access_size(b);
|
||||
if (!cpc_reg_is_writable(i) && !cpc_reg_is_writable(j))
|
||||
continue;
|
||||
if (a_gas->address < b_gas->address ?
|
||||
b_gas->address - a_gas->address >= a_size :
|
||||
a_gas->address - b_gas->address >= b_size)
|
||||
continue;
|
||||
|
||||
pr_err("CPU%d: overlapping writable %s _CPC registers %u and %u\n",
|
||||
cpc_desc->cpu_id, name, i, j);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -847,6 +1028,8 @@ static int check_pcc_chan(int pcc_ss_id, bool chk_err_bit)
|
|||
pcc_ss_data->deadline_us);
|
||||
|
||||
if (likely(!ret)) {
|
||||
/* Order completion status before reading the returned payload. */
|
||||
rmb();
|
||||
pcc_ss_data->platform_owns_pcc = false;
|
||||
if (chk_err_bit && (status & PCC_ERROR_MASK))
|
||||
ret = -EIO;
|
||||
|
|
@ -1290,6 +1473,7 @@ static int pcc_data_alloc(int pcc_ss_id)
|
|||
ret = -ENOMEM;
|
||||
goto out_unlock;
|
||||
}
|
||||
raw_spin_lock_init(&data->payload_lock);
|
||||
pcc_data[pcc_ss_id] = data;
|
||||
}
|
||||
data->refcount++;
|
||||
|
|
@ -1365,11 +1549,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
|
|||
struct device *cpu_dev;
|
||||
acpi_handle handle = pr->handle;
|
||||
unsigned int num_ent, i, cpc_rev;
|
||||
u32 unsupported_regs = 0;
|
||||
int pcc_subspace_id = -1;
|
||||
bool pcc_data_ref = false;
|
||||
bool cpc_present = false;
|
||||
acpi_status status;
|
||||
int ret = -ENODATA;
|
||||
int ret = -EINVAL;
|
||||
int err;
|
||||
|
||||
if (per_cpu(cpc_desc_ptr, pr->id))
|
||||
|
|
@ -1519,6 +1704,24 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
|
|||
* so extract it only once.
|
||||
*/
|
||||
if (gas_t->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) {
|
||||
/* These registers have no specified 32-bit upper bound. */
|
||||
bool wide_write = i - 2 == PERF_LIMITED ||
|
||||
i - 2 == ENABLE ||
|
||||
i - 2 == AUTO_SEL_ENABLE;
|
||||
bool write_width_supported = gas_t->bit_width == 8 ||
|
||||
gas_t->bit_width == 16 ||
|
||||
gas_t->bit_width == 32 ||
|
||||
gas_t->bit_width == 64;
|
||||
|
||||
if (!gas_t->bit_width || gas_t->bit_width > 64 ||
|
||||
gas_t->bit_offset || gas_t->bit_width % 8 ||
|
||||
(cpc_reg_is_writable(i - 2) &&
|
||||
(!write_width_supported ||
|
||||
(!wide_write && gas_t->bit_width > 32)))) {
|
||||
unsupported_regs |= BIT(i - 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pcc_subspace_id < 0) {
|
||||
pcc_subspace_id = gas_t->access_width;
|
||||
err = pcc_data_alloc(pcc_subspace_id);
|
||||
|
|
@ -1530,6 +1733,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
|
|||
} else if (pcc_subspace_id != gas_t->access_width) {
|
||||
pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n",
|
||||
pr->id);
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
} else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
|
||||
|
|
@ -1537,21 +1741,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
|
|||
void __iomem *addr;
|
||||
size_t access_width;
|
||||
|
||||
err = cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2);
|
||||
if (err && (i - 2 == DESIRED_PERF ||
|
||||
i - 2 == OSPM_NOMINAL_PERF)) {
|
||||
const char *name = i - 2 == DESIRED_PERF ?
|
||||
"Desired Performance" :
|
||||
"OSPM Nominal Performance";
|
||||
|
||||
pr_warn("CPU%d: disabling inaccessible %s register\n",
|
||||
pr->id, name);
|
||||
cpc_disable_reg(cpc_ptr, i - 2);
|
||||
continue;
|
||||
}
|
||||
err = cpc_validate_sysmem_reg(cpc_ptr, gas_t,
|
||||
i - 2);
|
||||
if (err) {
|
||||
ret = err;
|
||||
goto out_free;
|
||||
unsupported_regs |= BIT(i - 2);
|
||||
continue;
|
||||
}
|
||||
if (!cpc_is_readable(&cpc_ptr->cpc_regs[i - 2]) &&
|
||||
!cpc_is_writable(&cpc_ptr->cpc_regs[i - 2]))
|
||||
|
|
@ -1625,35 +1819,14 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
|
|||
}
|
||||
per_cpu(cpu_pcc_subspace_idx, pr->id) = pcc_subspace_id;
|
||||
|
||||
/*
|
||||
* Performance Limited is required by the specification, but tolerate a
|
||||
* NULL descriptor used by firmware which cannot report limiting events.
|
||||
* CPPC control does not depend on this status.
|
||||
*/
|
||||
for (i = 0; i < num_ent - 2; i++) {
|
||||
if (i != DESIRED_PERF && i != PERF_LIMITED &&
|
||||
!IS_OPTIONAL_CPC_REG(i) &&
|
||||
!cpc_entry_present(&cpc_ptr->cpc_regs[i])) {
|
||||
pr_debug("CPU:%d lacks mandatory _CPC register %u\n",
|
||||
pr->id, i);
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In CPPC v1, DESIRED_PERF is mandatory. In CPPC v2, it is optional
|
||||
* only when AUTO_SEL_ENABLE is supported.
|
||||
*/
|
||||
if (!cpc_is_writable(&cpc_ptr->cpc_regs[DESIRED_PERF]) &&
|
||||
(!osc_sb_cppc2_support_acked ||
|
||||
cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].type != ACPI_TYPE_INTEGER ||
|
||||
cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].cpc_entry.int_value != 1)) {
|
||||
pr_debug("CPU:%d lacks a writable Desired Performance register\n",
|
||||
pr->id);
|
||||
ret = -EINVAL;
|
||||
ret = cpc_resolve_unsupported(cpc_ptr, unsupported_regs);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
unsupported_regs = 0;
|
||||
|
||||
ret = cpc_validate_required_controls(cpc_ptr);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the remaining cpc_regs as unsupported.
|
||||
|
|
@ -1684,6 +1857,24 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
|
|||
if (ret)
|
||||
goto out_free;
|
||||
}
|
||||
if (pcc_subspace_id >= 0) {
|
||||
cpc_validate_pcc_bounds(cpc_ptr, pcc_data[pcc_subspace_id],
|
||||
&unsupported_regs);
|
||||
|
||||
ret = cpc_resolve_unsupported(cpc_ptr, unsupported_regs);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
ret = cpc_validate_non_mmio_overlaps(cpc_ptr,
|
||||
ACPI_ADR_SPACE_PLATFORM_COMM,
|
||||
"PCC");
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
ret = cpc_validate_bound_controls(cpc_ptr);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
/* Everything looks okay */
|
||||
pr_debug("Parsed CPC struct for CPU: %d\n", pr->id);
|
||||
|
|
@ -1810,6 +2001,9 @@ int __weak cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val)
|
|||
static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val)
|
||||
{
|
||||
void __iomem *vaddr = NULL;
|
||||
unsigned long flags;
|
||||
u8 buf[sizeof(*val)];
|
||||
unsigned int i;
|
||||
int size;
|
||||
int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu);
|
||||
struct cpc_reg *reg = ®_res->cpc_entry.reg;
|
||||
|
|
@ -1837,15 +2031,30 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val)
|
|||
|
||||
*val = val_u32;
|
||||
return 0;
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) {
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) {
|
||||
if (pcc_ss_id < 0 || !pcc_data[pcc_ss_id])
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* For registers in PCC space, the register size is determined
|
||||
* by the bit width field; the access size is used to indicate
|
||||
* the PCC subspace id.
|
||||
*/
|
||||
vaddr = GET_PCC_VADDR(reg->address, pcc_ss_id);
|
||||
}
|
||||
else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
|
||||
size = reg->bit_width / 8;
|
||||
if (!size || size > sizeof(buf) || reg->bit_width % 8)
|
||||
return -EFAULT;
|
||||
|
||||
raw_spin_lock_irqsave(&pcc_data[pcc_ss_id]->payload_lock, flags);
|
||||
memcpy_fromio(buf, vaddr, size);
|
||||
raw_spin_unlock_irqrestore(&pcc_data[pcc_ss_id]->payload_lock,
|
||||
flags);
|
||||
|
||||
*val = 0;
|
||||
for (i = 0; i < size; i++)
|
||||
*val |= (u64)buf[i] << (i * 8);
|
||||
return 0;
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
|
||||
vaddr = reg_res->sys_mem_vaddr;
|
||||
else if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE)
|
||||
return cpc_read_ffh(cpu, reg, val);
|
||||
|
|
@ -1867,18 +2076,12 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val)
|
|||
*val = readq_relaxed(vaddr);
|
||||
break;
|
||||
default:
|
||||
if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
|
||||
pr_debug("Error: Cannot read %u bit width from system memory: 0x%llx\n",
|
||||
size, reg->address);
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) {
|
||||
pr_debug("Error: Cannot read %u bit width from PCC for ss: %d\n",
|
||||
size, pcc_ss_id);
|
||||
}
|
||||
pr_debug("Error: Cannot read %u bit width from system memory: 0x%llx\n",
|
||||
size, reg->address);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
|
||||
*val = MASK_VAL_READ(reg, *val);
|
||||
*val = MASK_VAL_READ(reg, *val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1893,6 +2096,8 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val)
|
|||
struct cpc_reg *reg;
|
||||
struct cpc_desc *cpc_desc;
|
||||
unsigned long flags;
|
||||
u8 buf[sizeof(val)];
|
||||
unsigned int i;
|
||||
bool locked = false;
|
||||
|
||||
if (reg_res->type != ACPI_TYPE_BUFFER)
|
||||
|
|
@ -1917,15 +2122,31 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val)
|
|||
}
|
||||
|
||||
return 0;
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) {
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) {
|
||||
if (pcc_ss_id < 0 || !pcc_data[pcc_ss_id])
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* For registers in PCC space, the register size is determined
|
||||
* by the bit width field; the access size is used to indicate
|
||||
* the PCC subspace id.
|
||||
*/
|
||||
vaddr = GET_PCC_VADDR(reg->address, pcc_ss_id);
|
||||
}
|
||||
else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
|
||||
size = reg->bit_width / 8;
|
||||
if (!size || size > sizeof(buf) || reg->bit_width % 8)
|
||||
return -EFAULT;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
buf[i] = val >> (i * 8);
|
||||
|
||||
raw_spin_lock_irqsave(&pcc_data[pcc_ss_id]->payload_lock, flags);
|
||||
memcpy_toio(vaddr, buf, size);
|
||||
/* Publish every payload byte before another CPU can ring the doorbell. */
|
||||
wmb();
|
||||
raw_spin_unlock_irqrestore(&pcc_data[pcc_ss_id]->payload_lock,
|
||||
flags);
|
||||
return 0;
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
|
||||
vaddr = reg_res->sys_mem_vaddr;
|
||||
else if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE)
|
||||
return cpc_write_ffh(cpu, reg, val);
|
||||
|
|
@ -1933,44 +2154,42 @@ 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) {
|
||||
/* Partial fields and local overlaps use the descriptor lock. */
|
||||
locked = reg_res->cpc_entry.use_rmw_lock;
|
||||
if (locked) {
|
||||
cpc_desc = per_cpu(cpc_desc_ptr, cpu);
|
||||
if (!cpc_desc) {
|
||||
pr_debug("No CPC descriptor for CPU:%d\n", cpu);
|
||||
return -ENODEV;
|
||||
}
|
||||
raw_spin_lock_irqsave(&cpc_desc->rmw_lock, flags);
|
||||
/* Partial fields and local overlaps use the descriptor lock. */
|
||||
locked = reg_res->cpc_entry.use_rmw_lock;
|
||||
if (locked) {
|
||||
cpc_desc = per_cpu(cpc_desc_ptr, cpu);
|
||||
if (!cpc_desc) {
|
||||
pr_debug("No CPC descriptor for CPU:%d\n", cpu);
|
||||
return -ENODEV;
|
||||
}
|
||||
raw_spin_lock_irqsave(&cpc_desc->rmw_lock, flags);
|
||||
}
|
||||
|
||||
if (reg->bit_offset || reg->bit_width != size) {
|
||||
/*
|
||||
* MASK_VAL_WRITE() discards the field's old bits, so undefined
|
||||
* readback from a write-only field is not propagated.
|
||||
*/
|
||||
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:
|
||||
if (locked)
|
||||
raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock,
|
||||
flags);
|
||||
return -EFAULT;
|
||||
}
|
||||
val = MASK_VAL_WRITE(reg, prev_val, val);
|
||||
if (reg->bit_offset || reg->bit_width != size) {
|
||||
/*
|
||||
* MASK_VAL_WRITE() discards the field's old bits, so undefined
|
||||
* readback from a write-only field is not propagated.
|
||||
*/
|
||||
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:
|
||||
if (locked)
|
||||
raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock,
|
||||
flags);
|
||||
return -EFAULT;
|
||||
}
|
||||
val = MASK_VAL_WRITE(reg, prev_val, val);
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
|
|
@ -1987,13 +2206,8 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val)
|
|||
writeq_relaxed(val, vaddr);
|
||||
break;
|
||||
default:
|
||||
if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
|
||||
pr_debug("Error: Cannot write %u bit width to system memory: 0x%llx\n",
|
||||
size, reg->address);
|
||||
} else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) {
|
||||
pr_debug("Error: Cannot write %u bit width to PCC for ss: %d\n",
|
||||
size, pcc_ss_id);
|
||||
}
|
||||
pr_debug("Error: Cannot write %u bit width to system memory: 0x%llx\n",
|
||||
size, reg->address);
|
||||
ret_val = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2074,9 +2288,6 @@ static int cppc_set_reg_val_in_pcc(int cpu, struct cpc_register_resource *reg, u
|
|||
pr_debug("Invalid pcc_ss_id\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!cpc_pcc_write_supported(reg))
|
||||
return -EFAULT;
|
||||
|
||||
pcc_ss_data = pcc_data[pcc_ss_id];
|
||||
if (!pcc_ss_data)
|
||||
return -ENODEV;
|
||||
|
|
@ -2490,9 +2701,6 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable)
|
|||
auto_sel_pcc = cpc_is_writable(auto_sel_reg) &&
|
||||
CPC_IN_PCC(auto_sel_reg);
|
||||
epp_pcc = cpc_is_writable(epp_set_reg) && CPC_IN_PCC(epp_set_reg);
|
||||
if ((auto_sel_pcc && !cpc_pcc_write_supported(auto_sel_reg)) ||
|
||||
(epp_pcc && !cpc_pcc_write_supported(epp_set_reg)))
|
||||
return -EFAULT;
|
||||
if ((cpc_is_writable(auto_sel_reg) &&
|
||||
CPC_IN_SYSTEM_IO(auto_sel_reg) &&
|
||||
!cpc_system_io_write_supported(auto_sel_reg)) ||
|
||||
|
|
@ -2838,12 +3046,6 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
|
|||
!CPC_IN_PCC(max_perf_reg));
|
||||
mixed_layout = pcc_layout && direct_layout;
|
||||
|
||||
/* Do not modify any control if a requested PCC field cannot be staged. */
|
||||
if ((desired_pcc && !cpc_pcc_write_supported(desired_reg)) ||
|
||||
(min_pcc && !cpc_pcc_write_supported(min_perf_reg)) ||
|
||||
(max_pcc && !cpc_pcc_write_supported(max_perf_reg)))
|
||||
return -EFAULT;
|
||||
|
||||
if (mixed_layout || pcc_update) {
|
||||
if (pcc_ss_id < 0) {
|
||||
pr_debug("Invalid pcc_ss_id\n");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user