processor_thermal.c 9.9 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
/*
 * processor_thermal.c - Passive cooling 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
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or (at
 *  your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.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/processor.h>
#include <acpi/acpi_drivers.h>

#define ACPI_PROCESSOR_COMPONENT        0x01000000
#define ACPI_PROCESSOR_CLASS            "processor"
#define ACPI_PROCESSOR_DRIVER_NAME      "ACPI Processor Driver"
#define _COMPONENT              ACPI_PROCESSOR_COMPONENT
L
Len Brown 已提交
46
ACPI_MODULE_NAME("acpi_processor")
L
Linus Torvalds 已提交
47 48 49 50

/* --------------------------------------------------------------------------
                                 Limit Interface
   -------------------------------------------------------------------------- */
L
Len Brown 已提交
51
static int acpi_processor_apply_limit(struct acpi_processor *pr)
L
Linus Torvalds 已提交
52
{
L
Len Brown 已提交
53 54 55
	int result = 0;
	u16 px = 0;
	u16 tx = 0;
L
Linus Torvalds 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

	ACPI_FUNCTION_TRACE("acpi_processor_apply_limit");

	if (!pr)
		return_VALUE(-EINVAL);

	if (!pr->flags.limit)
		return_VALUE(-ENODEV);

	if (pr->flags.throttling) {
		if (pr->limit.user.tx > tx)
			tx = pr->limit.user.tx;
		if (pr->limit.thermal.tx > tx)
			tx = pr->limit.thermal.tx;

		result = acpi_processor_set_throttling(pr, tx);
		if (result)
			goto end;
	}

	pr->limit.state.px = px;
	pr->limit.state.tx = tx;

L
Len Brown 已提交
79 80 81
	ACPI_DEBUG_PRINT((ACPI_DB_INFO,
			  "Processor [%d] limit set to (P%d:T%d)\n", pr->id,
			  pr->limit.state.px, pr->limit.state.tx));
L
Linus Torvalds 已提交
82

L
Len Brown 已提交
83
      end:
L
Linus Torvalds 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
	if (result)
		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unable to set limit\n"));

	return_VALUE(result);
}

#ifdef CONFIG_CPU_FREQ

/* If a passive cooling situation is detected, primarily CPUfreq is used, as it
 * offers (in most cases) voltage scaling in addition to frequency scaling, and
 * thus a cubic (instead of linear) reduction of energy. Also, we allow for
 * _any_ cpufreq driver and not only the acpi-cpufreq driver.
 */

static unsigned int cpufreq_thermal_reduction_pctg[NR_CPUS];
static unsigned int acpi_thermal_cpufreq_is_init = 0;

static int cpu_has_cpufreq(unsigned int cpu)
{
	struct cpufreq_policy policy;
104
	if (!acpi_thermal_cpufreq_is_init || cpufreq_get_policy(&policy, cpu))
105 106
		return 0;
	return 1;
L
Linus Torvalds 已提交
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
}

static int acpi_thermal_cpufreq_increase(unsigned int cpu)
{
	if (!cpu_has_cpufreq(cpu))
		return -ENODEV;

	if (cpufreq_thermal_reduction_pctg[cpu] < 60) {
		cpufreq_thermal_reduction_pctg[cpu] += 20;
		cpufreq_update_policy(cpu);
		return 0;
	}

	return -ERANGE;
}

static int acpi_thermal_cpufreq_decrease(unsigned int cpu)
{
	if (!cpu_has_cpufreq(cpu))
		return -ENODEV;

128
	if (cpufreq_thermal_reduction_pctg[cpu] > 20)
L
Linus Torvalds 已提交
129
		cpufreq_thermal_reduction_pctg[cpu] -= 20;
130 131 132 133 134
	else
		cpufreq_thermal_reduction_pctg[cpu] = 0;
	cpufreq_update_policy(cpu);
	/* We reached max freq again and can leave passive mode */
	return !cpufreq_thermal_reduction_pctg[cpu];
L
Linus Torvalds 已提交
135 136
}

L
Len Brown 已提交
137 138
static int acpi_thermal_cpufreq_notifier(struct notifier_block *nb,
					 unsigned long event, void *data)
L
Linus Torvalds 已提交
139 140 141 142 143 144 145
{
	struct cpufreq_policy *policy = data;
	unsigned long max_freq = 0;

	if (event != CPUFREQ_ADJUST)
		goto out;

L
Len Brown 已提交
146 147 148
	max_freq =
	    (policy->cpuinfo.max_freq *
	     (100 - cpufreq_thermal_reduction_pctg[policy->cpu])) / 100;
L
Linus Torvalds 已提交
149 150 151

	cpufreq_verify_within_limits(policy, 0, max_freq);

L
Len Brown 已提交
152
      out:
L
Linus Torvalds 已提交
153 154 155 156 157 158 159
	return 0;
}

static struct notifier_block acpi_thermal_cpufreq_notifier_block = {
	.notifier_call = acpi_thermal_cpufreq_notifier,
};

L
Len Brown 已提交
160 161
void acpi_thermal_cpufreq_init(void)
{
L
Linus Torvalds 已提交
162 163
	int i;

L
Len Brown 已提交
164
	for (i = 0; i < NR_CPUS; i++)
L
Linus Torvalds 已提交
165 166
		cpufreq_thermal_reduction_pctg[i] = 0;

L
Len Brown 已提交
167 168
	i = cpufreq_register_notifier(&acpi_thermal_cpufreq_notifier_block,
				      CPUFREQ_POLICY_NOTIFIER);
L
Linus Torvalds 已提交
169 170 171 172
	if (!i)
		acpi_thermal_cpufreq_is_init = 1;
}

L
Len Brown 已提交
173 174
void acpi_thermal_cpufreq_exit(void)
{
L
Linus Torvalds 已提交
175
	if (acpi_thermal_cpufreq_is_init)
L
Len Brown 已提交
176 177 178
		cpufreq_unregister_notifier
		    (&acpi_thermal_cpufreq_notifier_block,
		     CPUFREQ_POLICY_NOTIFIER);
L
Linus Torvalds 已提交
179 180 181 182

	acpi_thermal_cpufreq_is_init = 0;
}

L
Len Brown 已提交
183
#else				/* ! CONFIG_CPU_FREQ */
L
Linus Torvalds 已提交
184

L
Len Brown 已提交
185 186 187 188 189 190 191 192
static int acpi_thermal_cpufreq_increase(unsigned int cpu)
{
	return -ENODEV;
}
static int acpi_thermal_cpufreq_decrease(unsigned int cpu)
{
	return -ENODEV;
}
L
Linus Torvalds 已提交
193 194 195

#endif

L
Len Brown 已提交
196
int acpi_processor_set_thermal_limit(acpi_handle handle, int type)
L
Linus Torvalds 已提交
197
{
L
Len Brown 已提交
198 199 200
	int result = 0;
	struct acpi_processor *pr = NULL;
	struct acpi_device *device = NULL;
201
	int tx = 0, max_tx_px = 0;
L
Linus Torvalds 已提交
202 203 204 205

	ACPI_FUNCTION_TRACE("acpi_processor_set_thermal_limit");

	if ((type < ACPI_PROCESSOR_LIMIT_NONE)
L
Len Brown 已提交
206
	    || (type > ACPI_PROCESSOR_LIMIT_DECREMENT))
L
Linus Torvalds 已提交
207 208 209 210 211 212
		return_VALUE(-EINVAL);

	result = acpi_bus_get_device(handle, &device);
	if (result)
		return_VALUE(result);

L
Len Brown 已提交
213
	pr = (struct acpi_processor *)acpi_driver_data(device);
L
Linus Torvalds 已提交
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
	if (!pr)
		return_VALUE(-ENODEV);

	/* Thermal limits are always relative to the current Px/Tx state. */
	if (pr->flags.throttling)
		pr->limit.thermal.tx = pr->throttling.state;

	/*
	 * Our default policy is to only use throttling at the lowest
	 * performance state.
	 */

	tx = pr->limit.thermal.tx;

	switch (type) {

	case ACPI_PROCESSOR_LIMIT_NONE:
		do {
			result = acpi_thermal_cpufreq_decrease(pr->id);
		} while (!result);
		tx = 0;
		break;

	case ACPI_PROCESSOR_LIMIT_INCREMENT:
		/* if going up: P-states first, T-states later */

		result = acpi_thermal_cpufreq_increase(pr->id);
		if (!result)
			goto end;
		else if (result == -ERANGE)
			ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
245
					  "At maximum performance state\n"));
L
Linus Torvalds 已提交
246 247 248 249

		if (pr->flags.throttling) {
			if (tx == (pr->throttling.state_count - 1))
				ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
250
						  "At maximum throttling state\n"));
L
Linus Torvalds 已提交
251 252 253 254 255 256 257 258 259
			else
				tx++;
		}
		break;

	case ACPI_PROCESSOR_LIMIT_DECREMENT:
		/* if going down: T-states first, P-states later */

		if (pr->flags.throttling) {
260 261
			if (tx == 0) {
				max_tx_px = 1;
L
Linus Torvalds 已提交
262
				ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
263
						  "At minimum throttling state\n"));
264
			} else {
L
Linus Torvalds 已提交
265 266 267 268 269 270
				tx--;
				goto end;
			}
		}

		result = acpi_thermal_cpufreq_decrease(pr->id);
271 272 273 274 275
		if (result) {
			/*
			 * We only could get -ERANGE, 1 or 0.
			 * In the first two cases we reached max freq again.
			 */
L
Linus Torvalds 已提交
276
			ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
277
					  "At minimum performance state\n"));
278 279 280
			max_tx_px = 1;
		} else
			max_tx_px = 0;
L
Linus Torvalds 已提交
281 282 283 284

		break;
	}

L
Len Brown 已提交
285
      end:
L
Linus Torvalds 已提交
286 287 288 289 290 291 292 293 294 295
	if (pr->flags.throttling) {
		pr->limit.thermal.px = 0;
		pr->limit.thermal.tx = tx;

		result = acpi_processor_apply_limit(pr);
		if (result)
			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
					  "Unable to set thermal limit\n"));

		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Thermal limit now (P%d:T%d)\n",
L
Len Brown 已提交
296
				  pr->limit.thermal.px, pr->limit.thermal.tx));
L
Linus Torvalds 已提交
297 298
	} else
		result = 0;
299 300 301 302
	if (max_tx_px)
		return_VALUE(1);
	else
		return_VALUE(result);
L
Linus Torvalds 已提交
303 304
}

L
Len Brown 已提交
305
int acpi_processor_get_limit_info(struct acpi_processor *pr)
L
Linus Torvalds 已提交
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
{
	ACPI_FUNCTION_TRACE("acpi_processor_get_limit_info");

	if (!pr)
		return_VALUE(-EINVAL);

	if (pr->flags.throttling)
		pr->flags.limit = 1;

	return_VALUE(0);
}

/* /proc interface */

static int acpi_processor_limit_seq_show(struct seq_file *seq, void *offset)
{
L
Len Brown 已提交
322
	struct acpi_processor *pr = (struct acpi_processor *)seq->private;
L
Linus Torvalds 已提交
323 324 325 326 327 328 329 330 331 332 333 334

	ACPI_FUNCTION_TRACE("acpi_processor_limit_seq_show");

	if (!pr)
		goto end;

	if (!pr->flags.limit) {
		seq_puts(seq, "<not supported>\n");
		goto end;
	}

	seq_printf(seq, "active limit:            P%d:T%d\n"
L
Len Brown 已提交
335 336 337 338 339
		   "user limit:              P%d:T%d\n"
		   "thermal limit:           P%d:T%d\n",
		   pr->limit.state.px, pr->limit.state.tx,
		   pr->limit.user.px, pr->limit.user.tx,
		   pr->limit.thermal.px, pr->limit.thermal.tx);
L
Linus Torvalds 已提交
340

L
Len Brown 已提交
341
      end:
L
Linus Torvalds 已提交
342 343 344 345 346 347
	return_VALUE(0);
}

static int acpi_processor_limit_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_processor_limit_seq_show,
L
Len Brown 已提交
348
			   PDE(inode)->data);
L
Linus Torvalds 已提交
349 350
}

L
Len Brown 已提交
351 352 353
ssize_t acpi_processor_write_limit(struct file * file,
				   const char __user * buffer,
				   size_t count, loff_t * data)
L
Linus Torvalds 已提交
354
{
L
Len Brown 已提交
355 356 357 358 359 360
	int result = 0;
	struct seq_file *m = (struct seq_file *)file->private_data;
	struct acpi_processor *pr = (struct acpi_processor *)m->private;
	char limit_string[25] = { '\0' };
	int px = 0;
	int tx = 0;
L
Linus Torvalds 已提交
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394

	ACPI_FUNCTION_TRACE("acpi_processor_write_limit");

	if (!pr || (count > sizeof(limit_string) - 1)) {
		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid argument\n"));
		return_VALUE(-EINVAL);
	}

	if (copy_from_user(limit_string, buffer, count)) {
		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data\n"));
		return_VALUE(-EFAULT);
	}

	limit_string[count] = '\0';

	if (sscanf(limit_string, "%d:%d", &px, &tx) != 2) {
		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid data format\n"));
		return_VALUE(-EINVAL);
	}

	if (pr->flags.throttling) {
		if ((tx < 0) || (tx > (pr->throttling.state_count - 1))) {
			ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid tx\n"));
			return_VALUE(-EINVAL);
		}
		pr->limit.user.tx = tx;
	}

	result = acpi_processor_apply_limit(pr);

	return_VALUE(count);
}

struct file_operations acpi_processor_limit_fops = {
L
Len Brown 已提交
395 396
	.open = acpi_processor_limit_open_fs,
	.read = seq_read,
397
	.write = acpi_processor_write_limit,
L
Len Brown 已提交
398 399
	.llseek = seq_lseek,
	.release = single_release,
L
Linus Torvalds 已提交
400
};