therm_throt.c 7.9 KB
Newer Older
1
/*
2 3
 * Thermal throttle event support code (such as syslog messaging and rate
 * limiting) that was factored out from x86_64 (mce_intel.c) and i386 (p4.c).
I
Ingo Molnar 已提交
4
 *
5 6 7 8 9
 * This allows consistent reporting of CPU thermal throttle events.
 *
 * Maintains a counter in /sys that keeps track of the number of thermal
 * events, such that the user knows how bad the thermal problem might be
 * (since the logging to syslog and mcelog is rate limited).
10 11 12 13
 *
 * Author: Dmitriy Zavin (dmitriyz@google.com)
 *
 * Credits: Adapted from Zwane Mwaikambo's original code in mce_intel.c.
14
 *          Inspired by Ross Biro's and Al Borchers' counter code.
15
 */
16
#include <linux/interrupt.h>
I
Ingo Molnar 已提交
17 18
#include <linux/notifier.h>
#include <linux/jiffies.h>
19
#include <linux/kernel.h>
20
#include <linux/percpu.h>
21
#include <linux/sysdev.h>
22 23 24
#include <linux/types.h>
#include <linux/init.h>
#include <linux/smp.h>
25
#include <linux/cpu.h>
I
Ingo Molnar 已提交
26

27
#include <asm/therm_throt.h>
28 29 30
#include <asm/processor.h>
#include <asm/system.h>
#include <asm/apic.h>
31 32
#include <asm/idle.h>
#include <asm/mce.h>
33
#include <asm/msr.h>
34 35

/* How long to wait between reporting thermal events */
I
Ingo Molnar 已提交
36
#define CHECK_INTERVAL		(300 * HZ)
37

38 39
static DEFINE_PER_CPU(__u64, next_check) = INITIAL_JIFFIES;
static DEFINE_PER_CPU(unsigned long, thermal_throttle_count);
I
Ingo Molnar 已提交
40 41

atomic_t therm_throt_en		= ATOMIC_INIT(0);
42 43

#ifdef CONFIG_SYSFS
I
Ingo Molnar 已提交
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
#define define_therm_throt_sysdev_one_ro(_name)				\
	static SYSDEV_ATTR(_name, 0444, therm_throt_sysdev_show_##_name, NULL)

#define define_therm_throt_sysdev_show_func(name)			\
static ssize_t therm_throt_sysdev_show_##name(struct sys_device *dev,	\
					struct sysdev_attribute *attr,	\
					      char *buf)		\
{									\
	unsigned int cpu = dev->id;					\
	ssize_t ret;							\
									\
	preempt_disable();	/* CPU hotplug */			\
	if (cpu_online(cpu))						\
		ret = sprintf(buf, "%lu\n",				\
			      per_cpu(thermal_throttle_##name, cpu));	\
	else								\
		ret = 0;						\
	preempt_enable();						\
									\
	return ret;							\
64 65 66 67 68 69 70 71 72 73 74
}

define_therm_throt_sysdev_show_func(count);
define_therm_throt_sysdev_one_ro(count);

static struct attribute *thermal_throttle_attrs[] = {
	&attr_count.attr,
	NULL
};

static struct attribute_group thermal_throttle_attr_group = {
I
Ingo Molnar 已提交
75 76
	.attrs	= thermal_throttle_attrs,
	.name	= "thermal_throttle"
77 78
};
#endif /* CONFIG_SYSFS */
79 80

/***
81
 * therm_throt_process - Process thermal throttling event from interrupt
82 83 84 85
 * @curr: Whether the condition is current or not (boolean), since the
 *        thermal interrupt normally gets called both when the thermal
 *        event begins and once the event has ended.
 *
86
 * This function is called by the thermal interrupt after the
87 88 89 90 91 92 93 94 95 96 97 98
 * IRQ has been acknowledged.
 *
 * It will take care of rate limiting and printing messages to the syslog.
 *
 * Returns: 0 : Event should NOT be further logged, i.e. still in
 *              "timeout" from previous log message.
 *          1 : Event should be logged further, and a message has been
 *              printed to the syslog.
 */
int therm_throt_process(int curr)
{
	unsigned int cpu = smp_processor_id();
99
	__u64 tmp_jiffs = get_jiffies_64();
100

101 102 103
	if (curr)
		__get_cpu_var(thermal_throttle_count)++;

104
	if (time_before64(tmp_jiffs, __get_cpu_var(next_check)))
105 106
		return 0;

107
	__get_cpu_var(next_check) = tmp_jiffs + CHECK_INTERVAL;
108 109 110 111

	/* if we just entered the thermal event */
	if (curr) {
		printk(KERN_CRIT "CPU%d: Temperature above threshold, "
112 113 114
		       "cpu clock throttled (total events = %lu)\n", cpu,
		       __get_cpu_var(thermal_throttle_count));

115 116 117 118 119 120 121
		add_taint(TAINT_MACHINE_CHECK);
	} else {
		printk(KERN_CRIT "CPU%d: Temperature/speed normal\n", cpu);
	}

	return 1;
}
122 123

#ifdef CONFIG_SYSFS
I
Ingo Molnar 已提交
124
/* Add/Remove thermal_throttle interface for CPU device: */
125
static __cpuinit int thermal_throttle_add_dev(struct sys_device *sys_dev)
126
{
I
Ingo Molnar 已提交
127 128
	return sysfs_create_group(&sys_dev->kobj,
				  &thermal_throttle_attr_group);
129 130
}

131
static __cpuinit void thermal_throttle_remove_dev(struct sys_device *sys_dev)
132
{
133
	sysfs_remove_group(&sys_dev->kobj, &thermal_throttle_attr_group);
134 135
}

I
Ingo Molnar 已提交
136
/* Mutex protecting device creation against CPU hotplug: */
137 138 139
static DEFINE_MUTEX(therm_cpu_lock);

/* Get notified when a cpu comes on/off. Be hotplug friendly. */
I
Ingo Molnar 已提交
140 141 142 143
static __cpuinit int
thermal_throttle_cpu_callback(struct notifier_block *nfb,
			      unsigned long action,
			      void *hcpu)
144 145 146
{
	unsigned int cpu = (unsigned long)hcpu;
	struct sys_device *sys_dev;
147
	int err = 0;
148 149

	sys_dev = get_cpu_sysdev(cpu);
I
Ingo Molnar 已提交
150

151
	switch (action) {
152 153
	case CPU_UP_PREPARE:
	case CPU_UP_PREPARE_FROZEN:
154
		mutex_lock(&therm_cpu_lock);
155
		err = thermal_throttle_add_dev(sys_dev);
156
		mutex_unlock(&therm_cpu_lock);
157
		WARN_ON(err);
158
		break;
159 160
	case CPU_UP_CANCELED:
	case CPU_UP_CANCELED_FROZEN:
161
	case CPU_DEAD:
162
	case CPU_DEAD_FROZEN:
163
		mutex_lock(&therm_cpu_lock);
164
		thermal_throttle_remove_dev(sys_dev);
165
		mutex_unlock(&therm_cpu_lock);
166 167
		break;
	}
168
	return err ? NOTIFY_BAD : NOTIFY_OK;
169 170
}

S
Satyam Sharma 已提交
171
static struct notifier_block thermal_throttle_cpu_notifier __cpuinitdata =
172 173 174 175 176 177 178
{
	.notifier_call = thermal_throttle_cpu_callback,
};

static __init int thermal_throttle_init_device(void)
{
	unsigned int cpu = 0;
179
	int err;
180 181 182 183 184 185 186 187 188 189

	if (!atomic_read(&therm_throt_en))
		return 0;

	register_hotcpu_notifier(&thermal_throttle_cpu_notifier);

#ifdef CONFIG_HOTPLUG_CPU
	mutex_lock(&therm_cpu_lock);
#endif
	/* connect live CPUs to sysfs */
190 191 192 193
	for_each_online_cpu(cpu) {
		err = thermal_throttle_add_dev(get_cpu_sysdev(cpu));
		WARN_ON(err);
	}
194 195 196 197 198 199 200
#ifdef CONFIG_HOTPLUG_CPU
	mutex_unlock(&therm_cpu_lock);
#endif

	return 0;
}
device_initcall(thermal_throttle_init_device);
201

202
#endif /* CONFIG_SYSFS */
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237

/* Thermal transition interrupt handler */
void intel_thermal_interrupt(void)
{
	__u64 msr_val;

	rdmsrl(MSR_IA32_THERM_STATUS, msr_val);
	if (therm_throt_process(msr_val & THERM_STATUS_PROCHOT))
		mce_log_therm_throt_event(msr_val);
}

static void unexpected_thermal_interrupt(void)
{
	printk(KERN_ERR "CPU%d: Unexpected LVT TMR interrupt!\n",
			smp_processor_id());
	add_taint(TAINT_MACHINE_CHECK);
}

static void (*smp_thermal_vector)(void) = unexpected_thermal_interrupt;

asmlinkage void smp_thermal_interrupt(struct pt_regs *regs)
{
	exit_idle();
	irq_enter();
	inc_irq_stat(irq_thermal_count);
	smp_thermal_vector();
	irq_exit();
	/* Ack only at the end to avoid potential reentry */
	ack_APIC_irq();
}

void intel_set_thermal_handler(void)
{
	smp_thermal_vector = intel_thermal_interrupt;
}
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

void intel_init_thermal(struct cpuinfo_x86 *c)
{
	unsigned int cpu = smp_processor_id();
	int tm2 = 0;
	u32 l, h;

	/* Thermal monitoring depends on ACPI and clock modulation*/
	if (!cpu_has(c, X86_FEATURE_ACPI) || !cpu_has(c, X86_FEATURE_ACC))
		return;

	/*
	 * First check if its enabled already, in which case there might
	 * be some SMM goo which handles it, so we can't even put a handler
	 * since it might be delivered via SMI already:
	 */
	rdmsr(MSR_IA32_MISC_ENABLE, l, h);
	h = apic_read(APIC_LVTTHMR);
	if ((l & MSR_IA32_MISC_ENABLE_TM1) && (h & APIC_DM_SMI)) {
		printk(KERN_DEBUG
		       "CPU%d: Thermal monitoring handled by SMI\n", cpu);
		return;
	}

	if (cpu_has(c, X86_FEATURE_TM2) && (l & MSR_IA32_MISC_ENABLE_TM2))
		tm2 = 1;

	/* Check whether a vector already exists */
	if (h & APIC_VECTOR_MASK) {
		printk(KERN_DEBUG
		       "CPU%d: Thermal LVT vector (%#x) already installed\n",
		       cpu, (h & APIC_VECTOR_MASK));
		return;
	}

	/* We'll mask the thermal vector in the lapic till we're ready: */
	h = THERMAL_APIC_VECTOR | APIC_DM_FIXED | APIC_LVT_MASKED;
	apic_write(APIC_LVTTHMR, h);

	rdmsr(MSR_IA32_THERM_INTERRUPT, l, h);
	wrmsr(MSR_IA32_THERM_INTERRUPT,
		l | (THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE), h);

	intel_set_thermal_handler();

	rdmsr(MSR_IA32_MISC_ENABLE, l, h);
	wrmsr(MSR_IA32_MISC_ENABLE, l | MSR_IA32_MISC_ENABLE_TM1, h);

	/* Unmask the thermal vector: */
	l = apic_read(APIC_LVTTHMR);
	apic_write(APIC_LVTTHMR, l & ~APIC_LVT_MASKED);

	printk(KERN_INFO "CPU%d: Thermal monitoring enabled (%s)\n",
	       cpu, tm2 ? "TM2" : "TM1");

	/* enable thermal throttle processing */
	atomic_set(&therm_throt_en, 1);
}