/** * cppc_set_perf - Set a CPUs performance controls. * @cpu: CPU for which to set performance controls. * @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h * * Return: 0 for success, -ERRNO otherwise. */ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); struct cpc_register_resource *desired_reg; int ret = 0; if (!cpc_desc) { pr_debug("No CPC descriptor for CPU:%d\n", cpu); return -ENODEV; } desired_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; spin_lock(&pcc_lock); /* * Skip writing MIN/MAX until Linux knows how to come up with * useful values. */ cpc_write(&desired_reg->cpc_entry.reg, perf_ctrls->desired_perf); /* Is this a PCC reg ?*/ if (desired_reg->cpc_entry.reg.space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { /* Ring doorbell so Remote can get our perf request. */ if (send_pcc_cmd(CMD_WRITE)) ret = -EIO; } spin_unlock(&pcc_lock); return ret; }
/** * cppc_get_perf_caps - Get a CPUs performance capabilities. * @cpunum: CPU from which to get capabilities info. * @perf_caps: ptr to cppc_perf_caps. See cppc_acpi.h * * Return: 0 for success with perf_caps populated else -ERRNO. */ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); struct cpc_register_resource *highest_reg, *lowest_reg, *ref_perf, *nom_perf; u64 high, low, ref, nom; int ret = 0; if (!cpc_desc) { pr_debug("No CPC descriptor for CPU:%d\n", cpunum); return -ENODEV; } highest_reg = &cpc_desc->cpc_regs[HIGHEST_PERF]; lowest_reg = &cpc_desc->cpc_regs[LOWEST_PERF]; ref_perf = &cpc_desc->cpc_regs[REFERENCE_PERF]; nom_perf = &cpc_desc->cpc_regs[NOMINAL_PERF]; spin_lock(&pcc_lock); /* Are any of the regs PCC ?*/ if ((highest_reg->cpc_entry.reg.space_id == ACPI_ADR_SPACE_PLATFORM_COMM) || (lowest_reg->cpc_entry.reg.space_id == ACPI_ADR_SPACE_PLATFORM_COMM) || (ref_perf->cpc_entry.reg.space_id == ACPI_ADR_SPACE_PLATFORM_COMM) || (nom_perf->cpc_entry.reg.space_id == ACPI_ADR_SPACE_PLATFORM_COMM)) { /* Ring doorbell once to update PCC subspace */ if (send_pcc_cmd(CMD_READ)) { ret = -EIO; goto out_err; } } cpc_read(&highest_reg->cpc_entry.reg, &high); perf_caps->highest_perf = high; cpc_read(&lowest_reg->cpc_entry.reg, &low); perf_caps->lowest_perf = low; cpc_read(&ref_perf->cpc_entry.reg, &ref); perf_caps->reference_perf = ref; cpc_read(&nom_perf->cpc_entry.reg, &nom); perf_caps->nominal_perf = nom; if (!ref) perf_caps->reference_perf = perf_caps->nominal_perf; if (!high || !low || !nom) ret = -EFAULT; out_err: spin_unlock(&pcc_lock); return ret; }
/** * cppc_get_perf_caps - Get a CPUs performance capabilities. * @cpunum: CPU from which to get capabilities info. * @perf_caps: ptr to cppc_perf_caps. See cppc_acpi.h * * Return: 0 for success with perf_caps populated else -ERRNO. */ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); struct cpc_register_resource *highest_reg, *lowest_reg, *lowest_non_linear_reg, *nominal_reg; u64 high, low, nom, min_nonlinear; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpunum); struct cppc_pcc_data *pcc_ss_data; int ret = 0, regs_in_pcc = 0; if (!cpc_desc || pcc_ss_id < 0) { pr_debug("No CPC descriptor for CPU:%d\n", cpunum); return -ENODEV; } pcc_ss_data = pcc_data[pcc_ss_id]; highest_reg = &cpc_desc->cpc_regs[HIGHEST_PERF]; lowest_reg = &cpc_desc->cpc_regs[LOWEST_PERF]; lowest_non_linear_reg = &cpc_desc->cpc_regs[LOW_NON_LINEAR_PERF]; nominal_reg = &cpc_desc->cpc_regs[NOMINAL_PERF]; /* Are any of the regs PCC ?*/ if (CPC_IN_PCC(highest_reg) || CPC_IN_PCC(lowest_reg) || CPC_IN_PCC(lowest_non_linear_reg) || CPC_IN_PCC(nominal_reg)) { regs_in_pcc = 1; down_write(&pcc_ss_data->pcc_lock); /* Ring doorbell once to update PCC subspace */ if (send_pcc_cmd(pcc_ss_id, CMD_READ) < 0) { ret = -EIO; goto out_err; } } cpc_read(cpunum, highest_reg, &high); perf_caps->highest_perf = high; cpc_read(cpunum, lowest_reg, &low); perf_caps->lowest_perf = low; cpc_read(cpunum, nominal_reg, &nom); perf_caps->nominal_perf = nom; cpc_read(cpunum, lowest_non_linear_reg, &min_nonlinear); perf_caps->lowest_nonlinear_perf = min_nonlinear; if (!high || !low || !nom || !min_nonlinear) ret = -EFAULT; out_err: if (regs_in_pcc) up_write(&pcc_ss_data->pcc_lock); return ret; }
/** * cppc_get_perf_ctrs - Read a CPUs performance feedback counters. * @cpunum: CPU from which to read counters. * @perf_fb_ctrs: ptr to cppc_perf_fb_ctrs. See cppc_acpi.h * * Return: 0 for success with perf_fb_ctrs populated else -ERRNO. */ int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); struct cpc_register_resource *delivered_reg, *reference_reg; u64 delivered, reference; int ret = 0; if (!cpc_desc) { pr_debug("No CPC descriptor for CPU:%d\n", cpunum); return -ENODEV; } delivered_reg = &cpc_desc->cpc_regs[DELIVERED_CTR]; reference_reg = &cpc_desc->cpc_regs[REFERENCE_CTR]; spin_lock(&pcc_lock); /* Are any of the regs PCC ?*/ if ((delivered_reg->cpc_entry.reg.space_id == ACPI_ADR_SPACE_PLATFORM_COMM) || (reference_reg->cpc_entry.reg.space_id == ACPI_ADR_SPACE_PLATFORM_COMM)) { /* Ring doorbell once to update PCC subspace */ if (send_pcc_cmd(CMD_READ)) { ret = -EIO; goto out_err; } } cpc_read(&delivered_reg->cpc_entry.reg, &delivered); cpc_read(&reference_reg->cpc_entry.reg, &reference); if (!delivered || !reference) { ret = -EFAULT; goto out_err; } perf_fb_ctrs->delivered = delivered; perf_fb_ctrs->reference = reference; perf_fb_ctrs->delivered -= perf_fb_ctrs->prev_delivered; perf_fb_ctrs->reference -= perf_fb_ctrs->prev_reference; perf_fb_ctrs->prev_delivered = delivered; perf_fb_ctrs->prev_reference = reference; out_err: spin_unlock(&pcc_lock); return ret; }
/* * This function transfers the ownership of the PCC to the platform * So it must be called while holding write_lock(pcc_lock) */ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) { int ret = -EIO, i; struct cppc_pcc_data *pcc_ss_data = pcc_data[pcc_ss_id]; struct acpi_pcct_shared_memory *generic_comm_base = (struct acpi_pcct_shared_memory *)pcc_ss_data->pcc_comm_addr; unsigned int time_delta; /* * For CMD_WRITE we know for a fact the caller should have checked * the channel before writing to PCC space */ if (cmd == CMD_READ) { /* * If there are pending cpc_writes, then we stole the channel * before write completion, so first send a WRITE command to * platform */ if (pcc_ss_data->pending_pcc_write_cmd) send_pcc_cmd(pcc_ss_id, CMD_WRITE); ret = check_pcc_chan(pcc_ss_id, false); if (ret) goto end; } else /* CMD_WRITE */ pcc_ss_data->pending_pcc_write_cmd = FALSE; /* * Handle the Minimum Request Turnaround Time(MRTT) * "The minimum amount of time that OSPM must wait after the completion * of a command before issuing the next command, in microseconds" */ if (pcc_ss_data->pcc_mrtt) { time_delta = ktime_us_delta(ktime_get(), pcc_ss_data->last_cmd_cmpl_time); if (pcc_ss_data->pcc_mrtt > time_delta) udelay(pcc_ss_data->pcc_mrtt - time_delta); } /* * Handle the non-zero Maximum Periodic Access Rate(MPAR) * "The maximum number of periodic requests that the subspace channel can * support, reported in commands per minute. 0 indicates no limitation." * * This parameter should be ideally zero or large enough so that it can * handle maximum number of requests that all the cores in the system can * collectively generate. If it is not, we will follow the spec and just * not send the request to the platform after hitting the MPAR limit in * any 60s window */ if (pcc_ss_data->pcc_mpar) { if (pcc_ss_data->mpar_count == 0) { time_delta = ktime_ms_delta(ktime_get(), pcc_ss_data->last_mpar_reset); if ((time_delta < 60 * MSEC_PER_SEC) && pcc_ss_data->last_mpar_reset) { pr_debug("PCC cmd not sent due to MPAR limit"); ret = -EIO; goto end; } pcc_ss_data->last_mpar_reset = ktime_get(); pcc_ss_data->mpar_count = pcc_ss_data->pcc_mpar; } pcc_ss_data->mpar_count--; } /* Write to the shared comm region. */ writew_relaxed(cmd, &generic_comm_base->command); /* Flip CMD COMPLETE bit */ writew_relaxed(0, &generic_comm_base->status); pcc_ss_data->platform_owns_pcc = true; /* Ring doorbell */ ret = mbox_send_message(pcc_ss_data->pcc_channel, &cmd); if (ret < 0) { pr_err("Err sending PCC mbox message. cmd:%d, ret:%d\n", cmd, ret); goto end; } /* wait for completion and check for PCC errro bit */ ret = check_pcc_chan(pcc_ss_id, true); if (pcc_ss_data->pcc_mrtt) pcc_ss_data->last_cmd_cmpl_time = ktime_get(); if (pcc_ss_data->pcc_channel->mbox->txdone_irq) mbox_chan_txdone(pcc_ss_data->pcc_channel, ret); else mbox_client_txdone(pcc_ss_data->pcc_channel, ret); end: if (cmd == CMD_WRITE) { if (unlikely(ret)) { for_each_possible_cpu(i) { struct cpc_desc *desc = per_cpu(cpc_desc_ptr, i); if (!desc) continue; if (desc->write_cmd_id == pcc_ss_data->pcc_write_cnt) desc->write_cmd_status = ret; } } pcc_ss_data->pcc_write_cnt++; wake_up_all(&pcc_ss_data->pcc_write_wait_q); } return ret; }
/** * cppc_set_perf - Set a CPUs performance controls. * @cpu: CPU for which to set performance controls. * @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h * * Return: 0 for success, -ERRNO otherwise. */ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); struct cpc_register_resource *desired_reg; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cppc_pcc_data *pcc_ss_data; int ret = 0; if (!cpc_desc || pcc_ss_id < 0) { pr_debug("No CPC descriptor for CPU:%d\n", cpu); return -ENODEV; } pcc_ss_data = pcc_data[pcc_ss_id]; desired_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; /* * This is Phase-I where we want to write to CPC registers * -> We want all CPUs to be able to execute this phase in parallel * * Since read_lock can be acquired by multiple CPUs simultaneously we * achieve that goal here */ if (CPC_IN_PCC(desired_reg)) { down_read(&pcc_ss_data->pcc_lock); /* BEGIN Phase-I */ if (pcc_ss_data->platform_owns_pcc) { ret = check_pcc_chan(pcc_ss_id, false); if (ret) { up_read(&pcc_ss_data->pcc_lock); return ret; } } /* * Update the pending_write to make sure a PCC CMD_READ will not * arrive and steal the channel during the switch to write lock */ pcc_ss_data->pending_pcc_write_cmd = true; cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt; cpc_desc->write_cmd_status = 0; } /* * Skip writing MIN/MAX until Linux knows how to come up with * useful values. */ cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); if (CPC_IN_PCC(desired_reg)) up_read(&pcc_ss_data->pcc_lock); /* END Phase-I */ /* * This is Phase-II where we transfer the ownership of PCC to Platform * * Short Summary: Basically if we think of a group of cppc_set_perf * requests that happened in short overlapping interval. The last CPU to * come out of Phase-I will enter Phase-II and ring the doorbell. * * We have the following requirements for Phase-II: * 1. We want to execute Phase-II only when there are no CPUs * currently executing in Phase-I * 2. Once we start Phase-II we want to avoid all other CPUs from * entering Phase-I. * 3. We want only one CPU among all those who went through Phase-I * to run phase-II * * If write_trylock fails to get the lock and doesn't transfer the * PCC ownership to the platform, then one of the following will be TRUE * 1. There is at-least one CPU in Phase-I which will later execute * write_trylock, so the CPUs in Phase-I will be responsible for * executing the Phase-II. * 2. Some other CPU has beaten this CPU to successfully execute the * write_trylock and has already acquired the write_lock. We know for a * fact it(other CPU acquiring the write_lock) couldn't have happened * before this CPU's Phase-I as we held the read_lock. * 3. Some other CPU executing pcc CMD_READ has stolen the * down_write, in which case, send_pcc_cmd will check for pending * CMD_WRITE commands by checking the pending_pcc_write_cmd. * So this CPU can be certain that its request will be delivered * So in all cases, this CPU knows that its request will be delivered * by another CPU and can return * * After getting the down_write we still need to check for * pending_pcc_write_cmd to take care of the following scenario * The thread running this code could be scheduled out between * Phase-I and Phase-II. Before it is scheduled back on, another CPU * could have delivered the request to Platform by triggering the * doorbell and transferred the ownership of PCC to platform. So this * avoids triggering an unnecessary doorbell and more importantly before * triggering the doorbell it makes sure that the PCC channel ownership * is still with OSPM. * pending_pcc_write_cmd can also be cleared by a different CPU, if * there was a pcc CMD_READ waiting on down_write and it steals the lock * before the pcc CMD_WRITE is completed. pcc_send_cmd checks for this * case during a CMD_READ and if there are pending writes it delivers * the write command before servicing the read command */ if (CPC_IN_PCC(desired_reg)) { if (down_write_trylock(&pcc_ss_data->pcc_lock)) {/* BEGIN Phase-II */ /* Update only if there are pending write commands */ if (pcc_ss_data->pending_pcc_write_cmd) send_pcc_cmd(pcc_ss_id, CMD_WRITE); up_write(&pcc_ss_data->pcc_lock); /* END Phase-II */ } else /* Wait until pcc_write_cnt is updated by send_pcc_cmd */ wait_event(pcc_ss_data->pcc_write_wait_q, cpc_desc->write_cmd_id != pcc_ss_data->pcc_write_cnt); /* send_pcc_cmd updates the status in case of failure */ ret = cpc_desc->write_cmd_status; } return ret; }
/** * cppc_get_perf_ctrs - Read a CPUs performance feedback counters. * @cpunum: CPU from which to read counters. * @perf_fb_ctrs: ptr to cppc_perf_fb_ctrs. See cppc_acpi.h * * Return: 0 for success with perf_fb_ctrs populated else -ERRNO. */ int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); struct cpc_register_resource *delivered_reg, *reference_reg, *ref_perf_reg, *ctr_wrap_reg; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpunum); struct cppc_pcc_data *pcc_ss_data; u64 delivered, reference, ref_perf, ctr_wrap_time; int ret = 0, regs_in_pcc = 0; if (!cpc_desc || pcc_ss_id < 0) { pr_debug("No CPC descriptor for CPU:%d\n", cpunum); return -ENODEV; } pcc_ss_data = pcc_data[pcc_ss_id]; delivered_reg = &cpc_desc->cpc_regs[DELIVERED_CTR]; reference_reg = &cpc_desc->cpc_regs[REFERENCE_CTR]; ref_perf_reg = &cpc_desc->cpc_regs[REFERENCE_PERF]; ctr_wrap_reg = &cpc_desc->cpc_regs[CTR_WRAP_TIME]; /* * If refernce perf register is not supported then we should * use the nominal perf value */ if (!CPC_SUPPORTED(ref_perf_reg)) ref_perf_reg = &cpc_desc->cpc_regs[NOMINAL_PERF]; /* Are any of the regs PCC ?*/ if (CPC_IN_PCC(delivered_reg) || CPC_IN_PCC(reference_reg) || CPC_IN_PCC(ctr_wrap_reg) || CPC_IN_PCC(ref_perf_reg)) { down_write(&pcc_ss_data->pcc_lock); regs_in_pcc = 1; /* Ring doorbell once to update PCC subspace */ if (send_pcc_cmd(pcc_ss_id, CMD_READ) < 0) { ret = -EIO; goto out_err; } } cpc_read(cpunum, delivered_reg, &delivered); cpc_read(cpunum, reference_reg, &reference); cpc_read(cpunum, ref_perf_reg, &ref_perf); /* * Per spec, if ctr_wrap_time optional register is unsupported, then the * performance counters are assumed to never wrap during the lifetime of * platform */ ctr_wrap_time = (u64)(~((u64)0)); if (CPC_SUPPORTED(ctr_wrap_reg)) cpc_read(cpunum, ctr_wrap_reg, &ctr_wrap_time); if (!delivered || !reference || !ref_perf) { ret = -EFAULT; goto out_err; } perf_fb_ctrs->delivered = delivered; perf_fb_ctrs->reference = reference; perf_fb_ctrs->reference_perf = ref_perf; perf_fb_ctrs->wraparound_time = ctr_wrap_time; out_err: if (regs_in_pcc) up_write(&pcc_ss_data->pcc_lock); return ret; }
/** * cppc_get_perf_caps - Get a CPUs performance capabilities. * @cpunum: CPU from which to get capabilities info. * @perf_caps: ptr to cppc_perf_caps. See cppc_acpi.h * * Return: 0 for success with perf_caps populated else -ERRNO. */ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); struct cpc_register_resource *highest_reg, *lowest_reg, *lowest_non_linear_reg, *nominal_reg, *guaranteed_reg, *low_freq_reg = NULL, *nom_freq_reg = NULL; u64 high, low, guaranteed, nom, min_nonlinear, low_f = 0, nom_f = 0; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpunum); struct cppc_pcc_data *pcc_ss_data = NULL; int ret = 0, regs_in_pcc = 0; if (!cpc_desc) { pr_debug("No CPC descriptor for CPU:%d\n", cpunum); return -ENODEV; } highest_reg = &cpc_desc->cpc_regs[HIGHEST_PERF]; lowest_reg = &cpc_desc->cpc_regs[LOWEST_PERF]; lowest_non_linear_reg = &cpc_desc->cpc_regs[LOW_NON_LINEAR_PERF]; nominal_reg = &cpc_desc->cpc_regs[NOMINAL_PERF]; low_freq_reg = &cpc_desc->cpc_regs[LOWEST_FREQ]; nom_freq_reg = &cpc_desc->cpc_regs[NOMINAL_FREQ]; guaranteed_reg = &cpc_desc->cpc_regs[GUARANTEED_PERF]; /* Are any of the regs PCC ?*/ if (CPC_IN_PCC(highest_reg) || CPC_IN_PCC(lowest_reg) || CPC_IN_PCC(lowest_non_linear_reg) || CPC_IN_PCC(nominal_reg) || CPC_IN_PCC(low_freq_reg) || CPC_IN_PCC(nom_freq_reg)) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id\n"); return -ENODEV; } pcc_ss_data = pcc_data[pcc_ss_id]; regs_in_pcc = 1; down_write(&pcc_ss_data->pcc_lock); /* Ring doorbell once to update PCC subspace */ if (send_pcc_cmd(pcc_ss_id, CMD_READ) < 0) { ret = -EIO; goto out_err; } } cpc_read(cpunum, highest_reg, &high); perf_caps->highest_perf = high; cpc_read(cpunum, lowest_reg, &low); perf_caps->lowest_perf = low; cpc_read(cpunum, nominal_reg, &nom); perf_caps->nominal_perf = nom; cpc_read(cpunum, guaranteed_reg, &guaranteed); perf_caps->guaranteed_perf = guaranteed; cpc_read(cpunum, lowest_non_linear_reg, &min_nonlinear); perf_caps->lowest_nonlinear_perf = min_nonlinear; if (!high || !low || !nom || !min_nonlinear) ret = -EFAULT; /* Read optional lowest and nominal frequencies if present */ if (CPC_SUPPORTED(low_freq_reg)) cpc_read(cpunum, low_freq_reg, &low_f); if (CPC_SUPPORTED(nom_freq_reg)) cpc_read(cpunum, nom_freq_reg, &nom_f); perf_caps->lowest_freq = low_f; perf_caps->nominal_freq = nom_f; out_err: if (regs_in_pcc) up_write(&pcc_ss_data->pcc_lock); return ret; }