#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/sched.h>
#include <linux/cpufreq.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
#include <acpi/processor.h>
-#define ACPI_PROCESSOR_COMPONENT 0x01000000
#define ACPI_PROCESSOR_CLASS "processor"
#define _COMPONENT ACPI_PROCESSOR_COMPONENT
ACPI_MODULE_NAME("processor_throttling");
+/* 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 */
+};
+
+#define THROTTLING_PRECHANGE (1)
+#define THROTTLING_POSTCHANGE (2)
+
static int acpi_processor_get_throttling(struct acpi_processor *pr);
int acpi_processor_set_throttling(struct acpi_processor *pr, int state);
+static int acpi_processor_update_tsd_coord(void)
+{
+ int count, 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 (!alloc_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;
+
+ cpumask_clear(covered_cpus);
+ 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;
+ count = 1;
+
+ 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);
+ count++;
+ }
+ 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())
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+ "Assume no T-state coordination\n"));
+
+ return;
+}
+
+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) {
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Invalid pr pointer\n"));
+ return 0;
+ }
+ if (!pr->flags.throttling) {
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Throttling control is "
+ "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) {
+ printk(KERN_WARNING
+ "Exceed the limit of T-state \n");
+ target_state = p_throttling->state_count - 1;
+ }
+ p_tstate->target_state = target_state;
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "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_DEBUG_PRINT((ACPI_DB_INFO, "PostChange Event:"
+ "CPU %d is switched to T%d\n",
+ cpu, target_state));
+ break;
+ default:
+ printk(KERN_WARNING
+ "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 tpc = 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) {
}
return -ENODEV;
}
+
+end:
pr->throttling_platform_limit = (int)tpc;
return 0;
}
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 */
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)) {
tsd = buffer.pointer;
if (!tsd || (tsd->type != ACPI_TYPE_PACKAGE)) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _TSD data\n"));
+ printk(KERN_ERR PREFIX "Invalid _TSD data\n");
result = -EFAULT;
goto end;
}
if (tsd->package.count != 1) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _TSD data\n"));
+ printk(KERN_ERR PREFIX "Invalid _TSD data\n");
result = -EFAULT;
goto end;
}
status = acpi_extract_package(&(tsd->package.elements[0]),
&format, &state);
if (ACPI_FAILURE(status)) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _TSD data\n"));
+ printk(KERN_ERR PREFIX "Invalid _TSD data\n");
result = -EFAULT;
goto end;
}
if (pdomain->num_entries != ACPI_TSD_REV0_ENTRIES) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unknown _TSD:num_entries\n"));
+ printk(KERN_ERR PREFIX "Unknown _TSD:num_entries\n");
result = -EFAULT;
goto end;
}
if (pdomain->revision != ACPI_TSD_REV0_REVISION) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unknown _TSD:revision\n"));
+ printk(KERN_ERR PREFIX "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;
} else {
msr_low = 0;
msr_high = 0;
- rdmsr_on_cpu(cpu, MSR_IA32_THERM_CONTROL,
+ rdmsr_safe(MSR_IA32_THERM_CONTROL,
(u32 *)&msr_low , (u32 *) &msr_high);
msr = (msr_high << 32) | msr_low;
*value = (acpi_integer) msr;
"HARDWARE addr space,NOT supported yet\n");
} else {
msr = value;
- wrmsr_on_cpu(cpu, MSR_IA32_THERM_CONTROL,
+ wrmsr_safe(MSR_IA32_THERM_CONTROL,
msr & 0xffffffff, msr >> 32);
ret = 0;
}
(struct acpi_processor_tx_tss *)&(pr->throttling.
states_tss[i]);
if (tx->control == value)
- break;
+ return i;
}
- if (i > pr->throttling.state_count)
- i = -1;
- return i;
+ return -1;
}
static int acpi_get_throttling_value(struct acpi_processor *pr,
return -ENODEV;
pr->throttling.state = 0;
- local_irq_disable();
+
value = 0;
ret = acpi_read_throttling_status(pr, &value);
if (ret >= 0) {
state = acpi_get_throttling_state(pr, value);
+ if (state == -1) {
+ ACPI_WARNING((AE_INFO,
+ "Invalid throttling state, reset"));
+ state = 0;
+ ret = acpi_processor_set_throttling(pr, state);
+ if (ret)
+ return ret;
+ }
pr->throttling.state = state;
}
- local_irq_enable();
return 0;
}
static int acpi_processor_get_throttling(struct acpi_processor *pr)
{
- return pr->throttling.acpi_processor_get_throttling(pr);
+ cpumask_var_t saved_mask;
+ int ret;
+
+ if (!pr)
+ return -EINVAL;
+
+ if (!pr->flags.throttling)
+ return -ENODEV;
+
+ if (!alloc_cpumask_var(&saved_mask, GFP_KERNEL))
+ return -ENOMEM;
+
+ /*
+ * Migrate task to the cpu pointed by pr.
+ */
+ cpumask_copy(saved_mask, ¤t->cpus_allowed);
+ /* FIXME: use work_on_cpu() */
+ set_cpus_allowed_ptr(current, cpumask_of(pr->id));
+ ret = pr->throttling.acpi_processor_get_throttling(pr);
+ /* restore the previous state */
+ set_cpus_allowed_ptr(current, saved_mask);
+ free_cpumask_var(saved_mask);
+
+ return ret;
}
static int acpi_processor_get_fadt_info(struct acpi_processor *pr)
if (state < pr->throttling_platform_limit)
return -EPERM;
- local_irq_disable();
value = 0;
ret = acpi_get_throttling_value(pr, state, &value);
if (ret >= 0) {
acpi_write_throttling_state(pr, value);
pr->throttling.state = state;
}
- local_irq_enable();
return 0;
}
int acpi_processor_set_throttling(struct acpi_processor *pr, int state)
{
- return pr->throttling.acpi_processor_set_throttling(pr, state);
+ cpumask_var_t saved_mask;
+ int ret = 0;
+ unsigned int i;
+ struct acpi_processor *match_pr;
+ struct acpi_processor_throttling *p_throttling;
+ struct throttling_tstate t_state;
+ cpumask_var_t online_throttling_cpus;
+
+ if (!pr)
+ return -EINVAL;
+
+ if (!pr->flags.throttling)
+ return -ENODEV;
+
+ if ((state < 0) || (state > (pr->throttling.state_count - 1)))
+ return -EINVAL;
+
+ if (!alloc_cpumask_var(&saved_mask, GFP_KERNEL))
+ return -ENOMEM;
+
+ if (!alloc_cpumask_var(&online_throttling_cpus, GFP_KERNEL)) {
+ free_cpumask_var(saved_mask);
+ return -ENOMEM;
+ }
+
+ cpumask_copy(saved_mask, ¤t->cpus_allowed);
+ t_state.target_state = state;
+ p_throttling = &(pr->throttling);
+ cpumask_and(online_throttling_cpus, cpu_online_mask,
+ p_throttling->shared_cpu_map);
+ /*
+ * 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(i, online_throttling_cpus) {
+ 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) {
+ /* FIXME: use work_on_cpu() */
+ set_cpus_allowed_ptr(current, cpumask_of(pr->id));
+ ret = p_throttling->acpi_processor_set_throttling(pr,
+ t_state.target_state);
+ } 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(i, online_throttling_cpus) {
+ match_pr = per_cpu(processors, i);
+ /*
+ * If the pointer is invalid, we will report the
+ * error message and continue.
+ */
+ if (!match_pr) {
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+ "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_DEBUG_PRINT((ACPI_DB_INFO,
+ "Throttling Controll is unsupported "
+ "on CPU %d\n", i));
+ continue;
+ }
+ t_state.cpu = i;
+ /* FIXME: use work_on_cpu() */
+ set_cpus_allowed_ptr(current, cpumask_of(i));
+ ret = match_pr->throttling.
+ acpi_processor_set_throttling(
+ match_pr, t_state.target_state);
+ }
+ }
+ /*
+ * 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(i, online_throttling_cpus) {
+ t_state.cpu = i;
+ acpi_processor_throttling_notifier(THROTTLING_POSTCHANGE,
+ &t_state);
+ }
+ /* restore the previous state */
+ /* FIXME: use work_on_cpu() */
+ set_cpus_allowed_ptr(current, saved_mask);
+ free_cpumask_var(online_throttling_cpus);
+ free_cpumask_var(saved_mask);
+ return ret;
}
int acpi_processor_get_throttling_info(struct acpi_processor *pr)
{
int result = 0;
+ struct acpi_processor_throttling *pthrottling;
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"pblk_address[0x%08x] duty_offset[%d] duty_width[%d]\n",
acpi_processor_get_throttling_states(pr) ||
acpi_processor_get_platform_limit(pr))
{
- if (acpi_processor_get_fadt_info(pr))
- return 0;
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;
&acpi_processor_set_throttling_ptc;
}
- acpi_processor_get_tsd(pr);
+ /*
+ * 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.
int result = 0;
struct seq_file *m = file->private_data;
struct acpi_processor *pr = m->private;
- char state_string[12] = { '\0' };
+ char state_string[5] = "";
+ char *charp = NULL;
+ size_t state_val = 0;
+ char tmpbuf[5] = "";
if (!pr || (count > sizeof(state_string) - 1))
return -EINVAL;
return -EFAULT;
state_string[count] = '\0';
+ if ((count > 0) && (state_string[count-1] == '\n'))
+ state_string[count-1] = '\0';
+
+ charp = state_string;
+ if ((state_string[0] == 't') || (state_string[0] == 'T'))
+ charp++;
+
+ state_val = simple_strtoul(charp, NULL, 0);
+ if (state_val >= pr->throttling.state_count)
+ return -EINVAL;
+
+ snprintf(tmpbuf, 5, "%zu", state_val);
+
+ if (strcmp(tmpbuf, charp) != 0)
+ return -EINVAL;
- result = acpi_processor_set_throttling(pr,
- simple_strtoul(state_string,
- NULL, 0));
+ result = acpi_processor_set_throttling(pr, state_val);
if (result)
return result;
return count;
}
-struct file_operations acpi_processor_throttling_fops = {
+const struct file_operations acpi_processor_throttling_fops = {
+ .owner = THIS_MODULE,
.open = acpi_processor_throttling_open_fs,
.read = seq_read,
.write = acpi_processor_write_throttling,