diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/acpi/processor_throttling.c | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/acpi/processor_throttling.c')
-rw-r--r-- | drivers/acpi/processor_throttling.c | 1242 |
1 files changed, 1242 insertions, 0 deletions
diff --git a/drivers/acpi/processor_throttling.c b/drivers/acpi/processor_throttling.c new file mode 100644 index 0000000000..00d045e5f5 --- /dev/null +++ b/drivers/acpi/processor_throttling.c @@ -0,0 +1,1242 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * processor_throttling.c - Throttling submodule of the ACPI processor driver + * + * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> + * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> + * Copyright (C) 2004 Dominik Brodowski <linux@brodo.de> + * Copyright (C) 2004 Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> + * - Added processor hotplug support + */ + +#define pr_fmt(fmt) "ACPI: " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/cpufreq.h> +#include <linux/acpi.h> +#include <acpi/processor.h> +#include <asm/io.h> +#include <linux/uaccess.h> + +/* ignore_tpc: + * 0 -> acpi processor driver doesn't ignore _TPC values + * 1 -> acpi processor driver ignores _TPC values + */ +static int ignore_tpc; +module_param(ignore_tpc, int, 0644); +MODULE_PARM_DESC(ignore_tpc, "Disable broken BIOS _TPC throttling support"); + +struct throttling_tstate { + unsigned int cpu; /* cpu nr */ + int target_state; /* target T-state */ +}; + +struct acpi_processor_throttling_arg { + struct acpi_processor *pr; + int target_state; + bool force; +}; + +#define THROTTLING_PRECHANGE (1) +#define THROTTLING_POSTCHANGE (2) + +static int acpi_processor_get_throttling(struct acpi_processor *pr); +static int __acpi_processor_set_throttling(struct acpi_processor *pr, + int state, bool force, bool direct); + +static int acpi_processor_update_tsd_coord(void) +{ + int count_target; + int retval = 0; + unsigned int i, j; + cpumask_var_t covered_cpus; + struct acpi_processor *pr, *match_pr; + struct acpi_tsd_package *pdomain, *match_pdomain; + struct acpi_processor_throttling *pthrottling, *match_pthrottling; + + if (!zalloc_cpumask_var(&covered_cpus, GFP_KERNEL)) + return -ENOMEM; + + /* + * Now that we have _TSD data from all CPUs, lets setup T-state + * coordination between all CPUs. + */ + for_each_possible_cpu(i) { + pr = per_cpu(processors, i); + if (!pr) + continue; + + /* Basic validity check for domain info */ + pthrottling = &(pr->throttling); + + /* + * If tsd package for one cpu is invalid, the coordination + * among all CPUs is thought as invalid. + * Maybe it is ugly. + */ + if (!pthrottling->tsd_valid_flag) { + retval = -EINVAL; + break; + } + } + if (retval) + goto err_ret; + + for_each_possible_cpu(i) { + pr = per_cpu(processors, i); + if (!pr) + continue; + + if (cpumask_test_cpu(i, covered_cpus)) + continue; + pthrottling = &pr->throttling; + + pdomain = &(pthrottling->domain_info); + cpumask_set_cpu(i, pthrottling->shared_cpu_map); + cpumask_set_cpu(i, covered_cpus); + /* + * If the number of processor in the TSD domain is 1, it is + * unnecessary to parse the coordination for this CPU. + */ + if (pdomain->num_processors <= 1) + continue; + + /* Validate the Domain info */ + count_target = pdomain->num_processors; + + for_each_possible_cpu(j) { + if (i == j) + continue; + + match_pr = per_cpu(processors, j); + if (!match_pr) + continue; + + match_pthrottling = &(match_pr->throttling); + match_pdomain = &(match_pthrottling->domain_info); + if (match_pdomain->domain != pdomain->domain) + continue; + + /* Here i and j are in the same domain. + * If two TSD packages have the same domain, they + * should have the same num_porcessors and + * coordination type. Otherwise it will be regarded + * as illegal. + */ + if (match_pdomain->num_processors != count_target) { + retval = -EINVAL; + goto err_ret; + } + + if (pdomain->coord_type != match_pdomain->coord_type) { + retval = -EINVAL; + goto err_ret; + } + + cpumask_set_cpu(j, covered_cpus); + cpumask_set_cpu(j, pthrottling->shared_cpu_map); + } + for_each_possible_cpu(j) { + if (i == j) + continue; + + match_pr = per_cpu(processors, j); + if (!match_pr) + continue; + + match_pthrottling = &(match_pr->throttling); + match_pdomain = &(match_pthrottling->domain_info); + if (match_pdomain->domain != pdomain->domain) + continue; + + /* + * If some CPUS have the same domain, they + * will have the same shared_cpu_map. + */ + cpumask_copy(match_pthrottling->shared_cpu_map, + pthrottling->shared_cpu_map); + } + } + +err_ret: + free_cpumask_var(covered_cpus); + + for_each_possible_cpu(i) { + pr = per_cpu(processors, i); + if (!pr) + continue; + + /* + * Assume no coordination on any error parsing domain info. + * The coordination type will be forced as SW_ALL. + */ + if (retval) { + pthrottling = &(pr->throttling); + cpumask_clear(pthrottling->shared_cpu_map); + cpumask_set_cpu(i, pthrottling->shared_cpu_map); + pthrottling->shared_type = DOMAIN_COORD_TYPE_SW_ALL; + } + } + + return retval; +} + +/* + * Update the T-state coordination after the _TSD + * data for all cpus is obtained. + */ +void acpi_processor_throttling_init(void) +{ + if (acpi_processor_update_tsd_coord()) + pr_debug("Assume no T-state coordination\n"); +} + +static int acpi_processor_throttling_notifier(unsigned long event, void *data) +{ + struct throttling_tstate *p_tstate = data; + struct acpi_processor *pr; + unsigned int cpu; + int target_state; + struct acpi_processor_limit *p_limit; + struct acpi_processor_throttling *p_throttling; + + cpu = p_tstate->cpu; + pr = per_cpu(processors, cpu); + if (!pr) { + pr_debug("Invalid pr pointer\n"); + return 0; + } + if (!pr->flags.throttling) { + acpi_handle_debug(pr->handle, + "Throttling control unsupported on CPU %d\n", + cpu); + return 0; + } + target_state = p_tstate->target_state; + p_throttling = &(pr->throttling); + switch (event) { + case THROTTLING_PRECHANGE: + /* + * Prechange event is used to choose one proper t-state, + * which meets the limits of thermal, user and _TPC. + */ + p_limit = &pr->limit; + if (p_limit->thermal.tx > target_state) + target_state = p_limit->thermal.tx; + if (p_limit->user.tx > target_state) + target_state = p_limit->user.tx; + if (pr->throttling_platform_limit > target_state) + target_state = pr->throttling_platform_limit; + if (target_state >= p_throttling->state_count) { + pr_warn("Exceed the limit of T-state \n"); + target_state = p_throttling->state_count - 1; + } + p_tstate->target_state = target_state; + acpi_handle_debug(pr->handle, + "PreChange Event: target T-state of CPU %d is T%d\n", + cpu, target_state); + break; + case THROTTLING_POSTCHANGE: + /* + * Postchange event is only used to update the + * T-state flag of acpi_processor_throttling. + */ + p_throttling->state = target_state; + acpi_handle_debug(pr->handle, + "PostChange Event: CPU %d is switched to T%d\n", + cpu, target_state); + break; + default: + pr_warn("Unsupported Throttling notifier event\n"); + break; + } + + return 0; +} + +/* + * _TPC - Throttling Present Capabilities + */ +static int acpi_processor_get_platform_limit(struct acpi_processor *pr) +{ + acpi_status status = 0; + unsigned long long tpc = 0; + + if (!pr) + return -EINVAL; + + if (ignore_tpc) + goto end; + + status = acpi_evaluate_integer(pr->handle, "_TPC", NULL, &tpc); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + acpi_evaluation_failure_warn(pr->handle, "_TPC", status); + + return -ENODEV; + } + +end: + pr->throttling_platform_limit = (int)tpc; + return 0; +} + +int acpi_processor_tstate_has_changed(struct acpi_processor *pr) +{ + int result = 0; + int throttling_limit; + int current_state; + struct acpi_processor_limit *limit; + int target_state; + + if (ignore_tpc) + return 0; + + result = acpi_processor_get_platform_limit(pr); + if (result) { + /* Throttling Limit is unsupported */ + return result; + } + + throttling_limit = pr->throttling_platform_limit; + if (throttling_limit >= pr->throttling.state_count) { + /* Uncorrect Throttling Limit */ + return -EINVAL; + } + + current_state = pr->throttling.state; + if (current_state > throttling_limit) { + /* + * The current state can meet the requirement of + * _TPC limit. But it is reasonable that OSPM changes + * t-states from high to low for better performance. + * Of course the limit condition of thermal + * and user should be considered. + */ + limit = &pr->limit; + target_state = throttling_limit; + if (limit->thermal.tx > target_state) + target_state = limit->thermal.tx; + if (limit->user.tx > target_state) + target_state = limit->user.tx; + } else if (current_state == throttling_limit) { + /* + * Unnecessary to change the throttling state + */ + return 0; + } else { + /* + * If the current state is lower than the limit of _TPC, it + * will be forced to switch to the throttling state defined + * by throttling_platfor_limit. + * Because the previous state meets with the limit condition + * of thermal and user, it is unnecessary to check it again. + */ + target_state = throttling_limit; + } + return acpi_processor_set_throttling(pr, target_state, false); +} + +/* + * This function is used to reevaluate whether the T-state is valid + * after one CPU is onlined/offlined. + * It is noted that it won't reevaluate the following properties for + * the T-state. + * 1. Control method. + * 2. the number of supported T-state + * 3. TSD domain + */ +void acpi_processor_reevaluate_tstate(struct acpi_processor *pr, + bool is_dead) +{ + int result = 0; + + if (is_dead) { + /* When one CPU is offline, the T-state throttling + * will be invalidated. + */ + pr->flags.throttling = 0; + return; + } + /* the following is to recheck whether the T-state is valid for + * the online CPU + */ + if (!pr->throttling.state_count) { + /* If the number of T-state is invalid, it is + * invalidated. + */ + pr->flags.throttling = 0; + return; + } + pr->flags.throttling = 1; + + /* Disable throttling (if enabled). We'll let subsequent + * policy (e.g.thermal) decide to lower performance if it + * so chooses, but for now we'll crank up the speed. + */ + + result = acpi_processor_get_throttling(pr); + if (result) + goto end; + + if (pr->throttling.state) { + result = acpi_processor_set_throttling(pr, 0, false); + if (result) + goto end; + } + +end: + if (result) + pr->flags.throttling = 0; +} +/* + * _PTC - Processor Throttling Control (and status) register location + */ +static int acpi_processor_get_throttling_control(struct acpi_processor *pr) +{ + int result = 0; + acpi_status status = 0; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *ptc = NULL; + union acpi_object obj; + struct acpi_processor_throttling *throttling; + + status = acpi_evaluate_object(pr->handle, "_PTC", NULL, &buffer); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + acpi_evaluation_failure_warn(pr->handle, "_PTC", status); + + return -ENODEV; + } + + ptc = (union acpi_object *)buffer.pointer; + if (!ptc || (ptc->type != ACPI_TYPE_PACKAGE) + || (ptc->package.count != 2)) { + pr_err("Invalid _PTC data\n"); + result = -EFAULT; + goto end; + } + + /* + * control_register + */ + + obj = ptc->package.elements[0]; + + if ((obj.type != ACPI_TYPE_BUFFER) + || (obj.buffer.length < sizeof(struct acpi_ptc_register)) + || (obj.buffer.pointer == NULL)) { + pr_err("Invalid _PTC data (control_register)\n"); + result = -EFAULT; + goto end; + } + memcpy(&pr->throttling.control_register, obj.buffer.pointer, + sizeof(struct acpi_ptc_register)); + + /* + * status_register + */ + + obj = ptc->package.elements[1]; + + if ((obj.type != ACPI_TYPE_BUFFER) + || (obj.buffer.length < sizeof(struct acpi_ptc_register)) + || (obj.buffer.pointer == NULL)) { + pr_err("Invalid _PTC data (status_register)\n"); + result = -EFAULT; + goto end; + } + + memcpy(&pr->throttling.status_register, obj.buffer.pointer, + sizeof(struct acpi_ptc_register)); + + throttling = &pr->throttling; + + if ((throttling->control_register.bit_width + + throttling->control_register.bit_offset) > 32) { + pr_err("Invalid _PTC control register\n"); + result = -EFAULT; + goto end; + } + + if ((throttling->status_register.bit_width + + throttling->status_register.bit_offset) > 32) { + pr_err("Invalid _PTC status register\n"); + result = -EFAULT; + goto end; + } + +end: + kfree(buffer.pointer); + + return result; +} + +/* + * _TSS - Throttling Supported States + */ +static int acpi_processor_get_throttling_states(struct acpi_processor *pr) +{ + int result = 0; + acpi_status status = AE_OK; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; + struct acpi_buffer state = { 0, NULL }; + union acpi_object *tss = NULL; + int i; + + status = acpi_evaluate_object(pr->handle, "_TSS", NULL, &buffer); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + acpi_evaluation_failure_warn(pr->handle, "_TSS", status); + + return -ENODEV; + } + + tss = buffer.pointer; + if (!tss || (tss->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _TSS data\n"); + result = -EFAULT; + goto end; + } + + acpi_handle_debug(pr->handle, "Found %d throttling states\n", + tss->package.count); + + pr->throttling.state_count = tss->package.count; + pr->throttling.states_tss = + kmalloc_array(tss->package.count, + sizeof(struct acpi_processor_tx_tss), + GFP_KERNEL); + if (!pr->throttling.states_tss) { + result = -ENOMEM; + goto end; + } + + for (i = 0; i < pr->throttling.state_count; i++) { + + struct acpi_processor_tx_tss *tx = + (struct acpi_processor_tx_tss *)&(pr->throttling. + states_tss[i]); + + state.length = sizeof(struct acpi_processor_tx_tss); + state.pointer = tx; + + acpi_handle_debug(pr->handle, "Extracting state %d\n", i); + + status = acpi_extract_package(&(tss->package.elements[i]), + &format, &state); + if (ACPI_FAILURE(status)) { + acpi_handle_warn(pr->handle, "Invalid _TSS data: %s\n", + acpi_format_exception(status)); + result = -EFAULT; + kfree(pr->throttling.states_tss); + goto end; + } + + if (!tx->freqpercentage) { + pr_err("Invalid _TSS data: freq is zero\n"); + result = -EFAULT; + kfree(pr->throttling.states_tss); + goto end; + } + } + +end: + kfree(buffer.pointer); + + return result; +} + +/* + * _TSD - T-State Dependencies + */ +static int acpi_processor_get_tsd(struct acpi_processor *pr) +{ + int result = 0; + acpi_status status = AE_OK; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; + struct acpi_buffer state = { 0, NULL }; + union acpi_object *tsd = NULL; + struct acpi_tsd_package *pdomain; + struct acpi_processor_throttling *pthrottling; + + pthrottling = &pr->throttling; + pthrottling->tsd_valid_flag = 0; + + status = acpi_evaluate_object(pr->handle, "_TSD", NULL, &buffer); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + acpi_evaluation_failure_warn(pr->handle, "_TSD", status); + + return -ENODEV; + } + + tsd = buffer.pointer; + if (!tsd || (tsd->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _TSD data\n"); + result = -EFAULT; + goto end; + } + + if (tsd->package.count != 1) { + pr_err("Invalid _TSD data\n"); + result = -EFAULT; + goto end; + } + + pdomain = &(pr->throttling.domain_info); + + state.length = sizeof(struct acpi_tsd_package); + state.pointer = pdomain; + + status = acpi_extract_package(&(tsd->package.elements[0]), + &format, &state); + if (ACPI_FAILURE(status)) { + pr_err("Invalid _TSD data\n"); + result = -EFAULT; + goto end; + } + + if (pdomain->num_entries != ACPI_TSD_REV0_ENTRIES) { + pr_err("Unknown _TSD:num_entries\n"); + result = -EFAULT; + goto end; + } + + if (pdomain->revision != ACPI_TSD_REV0_REVISION) { + pr_err("Unknown _TSD:revision\n"); + result = -EFAULT; + goto end; + } + + pthrottling = &pr->throttling; + pthrottling->tsd_valid_flag = 1; + pthrottling->shared_type = pdomain->coord_type; + cpumask_set_cpu(pr->id, pthrottling->shared_cpu_map); + /* + * If the coordination type is not defined in ACPI spec, + * the tsd_valid_flag will be clear and coordination type + * will be forecd as DOMAIN_COORD_TYPE_SW_ALL. + */ + if (pdomain->coord_type != DOMAIN_COORD_TYPE_SW_ALL && + pdomain->coord_type != DOMAIN_COORD_TYPE_SW_ANY && + pdomain->coord_type != DOMAIN_COORD_TYPE_HW_ALL) { + pthrottling->tsd_valid_flag = 0; + pthrottling->shared_type = DOMAIN_COORD_TYPE_SW_ALL; + } + +end: + kfree(buffer.pointer); + return result; +} + +/* -------------------------------------------------------------------------- + Throttling Control + -------------------------------------------------------------------------- */ +static int acpi_processor_get_throttling_fadt(struct acpi_processor *pr) +{ + int state = 0; + u32 value = 0; + u32 duty_mask = 0; + u32 duty_value = 0; + + if (!pr) + return -EINVAL; + + if (!pr->flags.throttling) + return -ENODEV; + + /* + * We don't care about error returns - we just try to mark + * these reserved so that nobody else is confused into thinking + * that this region might be unused.. + * + * (In particular, allocating the IO range for Cardbus) + */ + request_region(pr->throttling.address, 6, "ACPI CPU throttle"); + + pr->throttling.state = 0; + + duty_mask = pr->throttling.state_count - 1; + + duty_mask <<= pr->throttling.duty_offset; + + local_irq_disable(); + + value = inl(pr->throttling.address); + + /* + * Compute the current throttling state when throttling is enabled + * (bit 4 is on). + */ + if (value & 0x10) { + duty_value = value & duty_mask; + duty_value >>= pr->throttling.duty_offset; + + if (duty_value) + state = pr->throttling.state_count - duty_value; + } + + pr->throttling.state = state; + + local_irq_enable(); + + acpi_handle_debug(pr->handle, + "Throttling state is T%d (%d%% throttling applied)\n", + state, pr->throttling.states[state].performance); + + return 0; +} + +#ifdef CONFIG_X86 +static int acpi_throttling_rdmsr(u64 *value) +{ + u64 msr_high, msr_low; + u64 msr = 0; + int ret = -1; + + if ((this_cpu_read(cpu_info.x86_vendor) != X86_VENDOR_INTEL) || + !this_cpu_has(X86_FEATURE_ACPI)) { + pr_err("HARDWARE addr space,NOT supported yet\n"); + } else { + msr_low = 0; + msr_high = 0; + rdmsr_safe(MSR_IA32_THERM_CONTROL, + (u32 *)&msr_low, (u32 *) &msr_high); + msr = (msr_high << 32) | msr_low; + *value = (u64) msr; + ret = 0; + } + return ret; +} + +static int acpi_throttling_wrmsr(u64 value) +{ + int ret = -1; + u64 msr; + + if ((this_cpu_read(cpu_info.x86_vendor) != X86_VENDOR_INTEL) || + !this_cpu_has(X86_FEATURE_ACPI)) { + pr_err("HARDWARE addr space,NOT supported yet\n"); + } else { + msr = value; + wrmsr_safe(MSR_IA32_THERM_CONTROL, + msr & 0xffffffff, msr >> 32); + ret = 0; + } + return ret; +} +#else +static int acpi_throttling_rdmsr(u64 *value) +{ + pr_err("HARDWARE addr space,NOT supported yet\n"); + return -1; +} + +static int acpi_throttling_wrmsr(u64 value) +{ + pr_err("HARDWARE addr space,NOT supported yet\n"); + return -1; +} +#endif + +static int acpi_read_throttling_status(struct acpi_processor *pr, + u64 *value) +{ + u32 bit_width, bit_offset; + u32 ptc_value; + u64 ptc_mask; + struct acpi_processor_throttling *throttling; + int ret = -1; + + throttling = &pr->throttling; + switch (throttling->status_register.space_id) { + case ACPI_ADR_SPACE_SYSTEM_IO: + bit_width = throttling->status_register.bit_width; + bit_offset = throttling->status_register.bit_offset; + + acpi_os_read_port((acpi_io_address) throttling->status_register. + address, &ptc_value, + (u32) (bit_width + bit_offset)); + ptc_mask = (1 << bit_width) - 1; + *value = (u64) ((ptc_value >> bit_offset) & ptc_mask); + ret = 0; + break; + case ACPI_ADR_SPACE_FIXED_HARDWARE: + ret = acpi_throttling_rdmsr(value); + break; + default: + pr_err("Unknown addr space %d\n", + (u32) (throttling->status_register.space_id)); + } + return ret; +} + +static int acpi_write_throttling_state(struct acpi_processor *pr, + u64 value) +{ + u32 bit_width, bit_offset; + u64 ptc_value; + u64 ptc_mask; + struct acpi_processor_throttling *throttling; + int ret = -1; + + throttling = &pr->throttling; + switch (throttling->control_register.space_id) { + case ACPI_ADR_SPACE_SYSTEM_IO: + bit_width = throttling->control_register.bit_width; + bit_offset = throttling->control_register.bit_offset; + ptc_mask = (1 << bit_width) - 1; + ptc_value = value & ptc_mask; + + acpi_os_write_port((acpi_io_address) throttling-> + control_register.address, + (u32) (ptc_value << bit_offset), + (u32) (bit_width + bit_offset)); + ret = 0; + break; + case ACPI_ADR_SPACE_FIXED_HARDWARE: + ret = acpi_throttling_wrmsr(value); + break; + default: + pr_err("Unknown addr space %d\n", + (u32) (throttling->control_register.space_id)); + } + return ret; +} + +static int acpi_get_throttling_state(struct acpi_processor *pr, + u64 value) +{ + int i; + + for (i = 0; i < pr->throttling.state_count; i++) { + struct acpi_processor_tx_tss *tx = + (struct acpi_processor_tx_tss *)&(pr->throttling. + states_tss[i]); + if (tx->control == value) + return i; + } + return -1; +} + +static int acpi_get_throttling_value(struct acpi_processor *pr, + int state, u64 *value) +{ + int ret = -1; + + if (state >= 0 && state <= pr->throttling.state_count) { + struct acpi_processor_tx_tss *tx = + (struct acpi_processor_tx_tss *)&(pr->throttling. + states_tss[state]); + *value = tx->control; + ret = 0; + } + return ret; +} + +static int acpi_processor_get_throttling_ptc(struct acpi_processor *pr) +{ + int state = 0; + int ret; + u64 value; + + if (!pr) + return -EINVAL; + + if (!pr->flags.throttling) + return -ENODEV; + + pr->throttling.state = 0; + + value = 0; + ret = acpi_read_throttling_status(pr, &value); + if (ret >= 0) { + state = acpi_get_throttling_state(pr, value); + if (state == -1) { + acpi_handle_debug(pr->handle, + "Invalid throttling state, reset\n"); + state = 0; + ret = __acpi_processor_set_throttling(pr, state, true, + true); + if (ret) + return ret; + } + pr->throttling.state = state; + } + + return 0; +} + +static long __acpi_processor_get_throttling(void *data) +{ + struct acpi_processor *pr = data; + + return pr->throttling.acpi_processor_get_throttling(pr); +} + +static int acpi_processor_get_throttling(struct acpi_processor *pr) +{ + if (!pr) + return -EINVAL; + + if (!pr->flags.throttling) + return -ENODEV; + + /* + * This is either called from the CPU hotplug callback of + * processor_driver or via the ACPI probe function. In the latter + * case the CPU is not guaranteed to be online. Both call sites are + * protected against CPU hotplug. + */ + if (!cpu_online(pr->id)) + return -ENODEV; + + return call_on_cpu(pr->id, __acpi_processor_get_throttling, pr, false); +} + +static int acpi_processor_get_fadt_info(struct acpi_processor *pr) +{ + int i, step; + + if (!pr->throttling.address) { + acpi_handle_debug(pr->handle, "No throttling register\n"); + return -EINVAL; + } else if (!pr->throttling.duty_width) { + acpi_handle_debug(pr->handle, "No throttling states\n"); + return -EINVAL; + } + /* TBD: Support duty_cycle values that span bit 4. */ + else if ((pr->throttling.duty_offset + pr->throttling.duty_width) > 4) { + pr_warn("duty_cycle spans bit 4\n"); + return -EINVAL; + } + + pr->throttling.state_count = 1 << acpi_gbl_FADT.duty_width; + + /* + * Compute state values. Note that throttling displays a linear power + * performance relationship (at 50% performance the CPU will consume + * 50% power). Values are in 1/10th of a percent to preserve accuracy. + */ + + step = (1000 / pr->throttling.state_count); + + for (i = 0; i < pr->throttling.state_count; i++) { + pr->throttling.states[i].performance = 1000 - step * i; + pr->throttling.states[i].power = 1000 - step * i; + } + return 0; +} + +static int acpi_processor_set_throttling_fadt(struct acpi_processor *pr, + int state, bool force) +{ + u32 value = 0; + u32 duty_mask = 0; + u32 duty_value = 0; + + if (!pr) + return -EINVAL; + + if ((state < 0) || (state > (pr->throttling.state_count - 1))) + return -EINVAL; + + if (!pr->flags.throttling) + return -ENODEV; + + if (!force && (state == pr->throttling.state)) + return 0; + + if (state < pr->throttling_platform_limit) + return -EPERM; + /* + * Calculate the duty_value and duty_mask. + */ + if (state) { + duty_value = pr->throttling.state_count - state; + + duty_value <<= pr->throttling.duty_offset; + + /* Used to clear all duty_value bits */ + duty_mask = pr->throttling.state_count - 1; + + duty_mask <<= acpi_gbl_FADT.duty_offset; + duty_mask = ~duty_mask; + } + + local_irq_disable(); + + /* + * Disable throttling by writing a 0 to bit 4. Note that we must + * turn it off before you can change the duty_value. + */ + value = inl(pr->throttling.address); + if (value & 0x10) { + value &= 0xFFFFFFEF; + outl(value, pr->throttling.address); + } + + /* + * Write the new duty_value and then enable throttling. Note + * that a state value of 0 leaves throttling disabled. + */ + if (state) { + value &= duty_mask; + value |= duty_value; + outl(value, pr->throttling.address); + + value |= 0x00000010; + outl(value, pr->throttling.address); + } + + pr->throttling.state = state; + + local_irq_enable(); + + acpi_handle_debug(pr->handle, + "Throttling state set to T%d (%d%%)\n", state, + (pr->throttling.states[state].performance ? pr-> + throttling.states[state].performance / 10 : 0)); + + return 0; +} + +static int acpi_processor_set_throttling_ptc(struct acpi_processor *pr, + int state, bool force) +{ + int ret; + u64 value; + + if (!pr) + return -EINVAL; + + if ((state < 0) || (state > (pr->throttling.state_count - 1))) + return -EINVAL; + + if (!pr->flags.throttling) + return -ENODEV; + + if (!force && (state == pr->throttling.state)) + return 0; + + if (state < pr->throttling_platform_limit) + return -EPERM; + + value = 0; + ret = acpi_get_throttling_value(pr, state, &value); + if (ret >= 0) { + acpi_write_throttling_state(pr, value); + pr->throttling.state = state; + } + + return 0; +} + +static long acpi_processor_throttling_fn(void *data) +{ + struct acpi_processor_throttling_arg *arg = data; + struct acpi_processor *pr = arg->pr; + + return pr->throttling.acpi_processor_set_throttling(pr, + arg->target_state, arg->force); +} + +static int __acpi_processor_set_throttling(struct acpi_processor *pr, + int state, bool force, bool direct) +{ + int ret = 0; + unsigned int i; + struct acpi_processor *match_pr; + struct acpi_processor_throttling *p_throttling; + struct acpi_processor_throttling_arg arg; + struct throttling_tstate t_state; + + if (!pr) + return -EINVAL; + + if (!pr->flags.throttling) + return -ENODEV; + + if ((state < 0) || (state > (pr->throttling.state_count - 1))) + return -EINVAL; + + if (cpu_is_offline(pr->id)) { + /* + * the cpu pointed by pr->id is offline. Unnecessary to change + * the throttling state any more. + */ + return -ENODEV; + } + + t_state.target_state = state; + p_throttling = &(pr->throttling); + + /* + * The throttling notifier will be called for every + * affected cpu in order to get one proper T-state. + * The notifier event is THROTTLING_PRECHANGE. + */ + for_each_cpu_and(i, cpu_online_mask, p_throttling->shared_cpu_map) { + t_state.cpu = i; + acpi_processor_throttling_notifier(THROTTLING_PRECHANGE, + &t_state); + } + /* + * The function of acpi_processor_set_throttling will be called + * to switch T-state. If the coordination type is SW_ALL or HW_ALL, + * it is necessary to call it for every affected cpu. Otherwise + * it can be called only for the cpu pointed by pr. + */ + if (p_throttling->shared_type == DOMAIN_COORD_TYPE_SW_ANY) { + arg.pr = pr; + arg.target_state = state; + arg.force = force; + ret = call_on_cpu(pr->id, acpi_processor_throttling_fn, &arg, + direct); + } else { + /* + * When the T-state coordination is SW_ALL or HW_ALL, + * it is necessary to set T-state for every affected + * cpus. + */ + for_each_cpu_and(i, cpu_online_mask, + p_throttling->shared_cpu_map) { + match_pr = per_cpu(processors, i); + /* + * If the pointer is invalid, we will report the + * error message and continue. + */ + if (!match_pr) { + acpi_handle_debug(pr->handle, + "Invalid Pointer for CPU %d\n", i); + continue; + } + /* + * If the throttling control is unsupported on CPU i, + * we will report the error message and continue. + */ + if (!match_pr->flags.throttling) { + acpi_handle_debug(pr->handle, + "Throttling Control unsupported on CPU %d\n", i); + continue; + } + + arg.pr = match_pr; + arg.target_state = state; + arg.force = force; + ret = call_on_cpu(pr->id, acpi_processor_throttling_fn, + &arg, direct); + } + } + /* + * After the set_throttling is called, the + * throttling notifier is called for every + * affected cpu to update the T-states. + * The notifier event is THROTTLING_POSTCHANGE + */ + for_each_cpu_and(i, cpu_online_mask, p_throttling->shared_cpu_map) { + t_state.cpu = i; + acpi_processor_throttling_notifier(THROTTLING_POSTCHANGE, + &t_state); + } + + return ret; +} + +int acpi_processor_set_throttling(struct acpi_processor *pr, int state, + bool force) +{ + return __acpi_processor_set_throttling(pr, state, force, false); +} + +int acpi_processor_get_throttling_info(struct acpi_processor *pr) +{ + int result = 0; + struct acpi_processor_throttling *pthrottling; + + acpi_handle_debug(pr->handle, + "pblk_address[0x%08x] duty_offset[%d] duty_width[%d]\n", + pr->throttling.address, + pr->throttling.duty_offset, + pr->throttling.duty_width); + + /* + * Evaluate _PTC, _TSS and _TPC + * They must all be present or none of them can be used. + */ + if (acpi_processor_get_throttling_control(pr) || + acpi_processor_get_throttling_states(pr) || + acpi_processor_get_platform_limit(pr)) { + pr->throttling.acpi_processor_get_throttling = + &acpi_processor_get_throttling_fadt; + pr->throttling.acpi_processor_set_throttling = + &acpi_processor_set_throttling_fadt; + if (acpi_processor_get_fadt_info(pr)) + return 0; + } else { + pr->throttling.acpi_processor_get_throttling = + &acpi_processor_get_throttling_ptc; + pr->throttling.acpi_processor_set_throttling = + &acpi_processor_set_throttling_ptc; + } + + /* + * If TSD package for one CPU can't be parsed successfully, it means + * that this CPU will have no coordination with other CPUs. + */ + if (acpi_processor_get_tsd(pr)) { + pthrottling = &pr->throttling; + pthrottling->tsd_valid_flag = 0; + cpumask_set_cpu(pr->id, pthrottling->shared_cpu_map); + pthrottling->shared_type = DOMAIN_COORD_TYPE_SW_ALL; + } + + /* + * PIIX4 Errata: We don't support throttling on the original PIIX4. + * This shouldn't be an issue as few (if any) mobile systems ever + * used this part. + */ + if (errata.piix4.throttle) { + acpi_handle_debug(pr->handle, + "Throttling not supported on PIIX4 A- or B-step\n"); + return 0; + } + + acpi_handle_debug(pr->handle, "Found %d throttling states\n", + pr->throttling.state_count); + + pr->flags.throttling = 1; + + /* + * Disable throttling (if enabled). We'll let subsequent policy (e.g. + * thermal) decide to lower performance if it so chooses, but for now + * we'll crank up the speed. + */ + + result = acpi_processor_get_throttling(pr); + if (result) + goto end; + + if (pr->throttling.state) { + acpi_handle_debug(pr->handle, + "Disabling throttling (was T%d)\n", + pr->throttling.state); + result = acpi_processor_set_throttling(pr, 0, false); + if (result) + goto end; + } + +end: + if (result) + pr->flags.throttling = 0; + + return result; +} + |