i8k.c 17.8 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7
/*
 * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
 *	    See http://www.debian.org/~dz/i8k/ for more information
 *	    and for latest version of this driver.
 *
 * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
 *
8 9 10
 * Hwmon integration:
 * Copyright (C) 2011  Jean Delvare <khali@linux-fr.org>
 *
L
Linus Torvalds 已提交
11 12 13 14 15 16 17 18 19 20 21
 * 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, 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.
 */

22 23
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

L
Linus Torvalds 已提交
24 25 26 27
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
28
#include <linux/seq_file.h>
29
#include <linux/dmi.h>
30
#include <linux/capability.h>
31
#include <linux/mutex.h>
32 33
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
L
Linus Torvalds 已提交
34 35 36 37 38
#include <asm/uaccess.h>
#include <asm/io.h>

#include <linux/i8k.h>

39
#define I8K_VERSION		"1.14 21/02/2005"
L
Linus Torvalds 已提交
40 41 42 43 44 45 46

#define I8K_SMM_FN_STATUS	0x0025
#define I8K_SMM_POWER_STATUS	0x0069
#define I8K_SMM_SET_FAN		0x01a3
#define I8K_SMM_GET_FAN		0x00a3
#define I8K_SMM_GET_SPEED	0x02a3
#define I8K_SMM_GET_TEMP	0x10a3
47 48
#define I8K_SMM_GET_DELL_SIG1	0xfea3
#define I8K_SMM_GET_DELL_SIG2	0xffa3
L
Linus Torvalds 已提交
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
#define I8K_SMM_BIOS_VERSION	0x00a6

#define I8K_FAN_MULT		30
#define I8K_MAX_TEMP		127

#define I8K_FN_NONE		0x00
#define I8K_FN_UP		0x01
#define I8K_FN_DOWN		0x02
#define I8K_FN_MUTE		0x04
#define I8K_FN_MASK		0x07
#define I8K_FN_SHIFT		8

#define I8K_POWER_AC		0x05
#define I8K_POWER_BATTERY	0x01

#define I8K_TEMPERATURE_BUG	1

66
static DEFINE_MUTEX(i8k_mutex);
67
static char bios_version[4];
68
static struct device *i8k_hwmon_dev;
L
Linus Torvalds 已提交
69 70 71 72 73

MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
MODULE_LICENSE("GPL");

74
static bool force;
L
Linus Torvalds 已提交
75 76 77
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported models");

78
static bool ignore_dmi;
79 80 81
module_param(ignore_dmi, bool, 0);
MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");

82
static bool restricted;
L
Linus Torvalds 已提交
83 84 85
module_param(restricted, bool, 0);
MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");

86
static bool power_status;
L
Linus Torvalds 已提交
87 88 89
module_param(power_status, bool, 0600);
MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");

90 91 92 93
static int fan_mult = I8K_FAN_MULT;
module_param(fan_mult, int, 0);
MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with");

94
static int i8k_open_fs(struct inode *inode, struct file *file);
95
static long i8k_ioctl(struct file *, unsigned int, unsigned long);
L
Linus Torvalds 已提交
96

97
static const struct file_operations i8k_fops = {
98
	.owner		= THIS_MODULE,
99 100 101 102
	.open		= i8k_open_fs,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
103
	.unlocked_ioctl	= i8k_ioctl,
L
Linus Torvalds 已提交
104 105
};

106
struct smm_regs {
107 108 109 110 111 112
	unsigned int eax;
	unsigned int ebx __attribute__ ((packed));
	unsigned int ecx __attribute__ ((packed));
	unsigned int edx __attribute__ ((packed));
	unsigned int esi __attribute__ ((packed));
	unsigned int edi __attribute__ ((packed));
113
};
L
Linus Torvalds 已提交
114

115
static inline const char *i8k_get_dmi_data(int field)
116
{
117
	const char *dmi_data = dmi_get_system_info(field);
118 119

	return dmi_data && *dmi_data ? dmi_data : "?";
120
}
L
Linus Torvalds 已提交
121 122 123 124

/*
 * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
 */
125
static int i8k_smm(struct smm_regs *regs)
L
Linus Torvalds 已提交
126
{
127 128 129
	int rc;
	int eax = regs->eax;

130
#if defined(CONFIG_X86_64)
131
	asm volatile("pushq %%rax\n\t"
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
		"movl 0(%%rax),%%edx\n\t"
		"pushq %%rdx\n\t"
		"movl 4(%%rax),%%ebx\n\t"
		"movl 8(%%rax),%%ecx\n\t"
		"movl 12(%%rax),%%edx\n\t"
		"movl 16(%%rax),%%esi\n\t"
		"movl 20(%%rax),%%edi\n\t"
		"popq %%rax\n\t"
		"out %%al,$0xb2\n\t"
		"out %%al,$0x84\n\t"
		"xchgq %%rax,(%%rsp)\n\t"
		"movl %%ebx,4(%%rax)\n\t"
		"movl %%ecx,8(%%rax)\n\t"
		"movl %%edx,12(%%rax)\n\t"
		"movl %%esi,16(%%rax)\n\t"
		"movl %%edi,20(%%rax)\n\t"
		"popq %%rdx\n\t"
		"movl %%edx,0(%%rax)\n\t"
L
Luca Tettamanti 已提交
150 151
		"pushfq\n\t"
		"popq %%rax\n\t"
152
		"andl $1,%%eax\n"
153
		:"=a"(rc)
154 155 156
		:    "a"(regs)
		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
#else
157
	asm volatile("pushl %%eax\n\t"
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	    "movl 0(%%eax),%%edx\n\t"
	    "push %%edx\n\t"
	    "movl 4(%%eax),%%ebx\n\t"
	    "movl 8(%%eax),%%ecx\n\t"
	    "movl 12(%%eax),%%edx\n\t"
	    "movl 16(%%eax),%%esi\n\t"
	    "movl 20(%%eax),%%edi\n\t"
	    "popl %%eax\n\t"
	    "out %%al,$0xb2\n\t"
	    "out %%al,$0x84\n\t"
	    "xchgl %%eax,(%%esp)\n\t"
	    "movl %%ebx,4(%%eax)\n\t"
	    "movl %%ecx,8(%%eax)\n\t"
	    "movl %%edx,12(%%eax)\n\t"
	    "movl %%esi,16(%%eax)\n\t"
	    "movl %%edi,20(%%eax)\n\t"
	    "popl %%edx\n\t"
	    "movl %%edx,0(%%eax)\n\t"
	    "lahf\n\t"
	    "shrl $8,%%eax\n\t"
J
Jim Bos 已提交
178
	    "andl $1,%%eax\n"
179
	    :"=a"(rc)
180 181
	    :    "a"(regs)
	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
182
#endif
183
	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
184 185 186
		return -EINVAL;

	return 0;
L
Linus Torvalds 已提交
187 188 189 190 191 192 193 194
}

/*
 * Read the bios version. Return the version as an integer corresponding
 * to the ascii value, for example "A17" is returned as 0x00413137.
 */
static int i8k_get_bios_version(void)
{
195
	struct smm_regs regs = { .eax = I8K_SMM_BIOS_VERSION, };
L
Linus Torvalds 已提交
196

197
	return i8k_smm(&regs) ? : regs.eax;
L
Linus Torvalds 已提交
198 199 200 201 202 203 204
}

/*
 * Read the Fn key status.
 */
static int i8k_get_fn_status(void)
{
205
	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
206 207
	int rc;

208
	if ((rc = i8k_smm(&regs)) < 0)
209 210 211 212 213 214 215 216 217 218 219 220
		return rc;

	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
	case I8K_FN_UP:
		return I8K_VOL_UP;
	case I8K_FN_DOWN:
		return I8K_VOL_DOWN;
	case I8K_FN_MUTE:
		return I8K_VOL_MUTE;
	default:
		return 0;
	}
L
Linus Torvalds 已提交
221 222 223 224 225 226 227
}

/*
 * Read the power status.
 */
static int i8k_get_power_status(void)
{
228
	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
229 230
	int rc;

231
	if ((rc = i8k_smm(&regs)) < 0)
232 233
		return rc;

234
	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
L
Linus Torvalds 已提交
235 236 237 238 239 240 241
}

/*
 * Read the fan status.
 */
static int i8k_get_fan_status(int fan)
{
242
	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
L
Linus Torvalds 已提交
243

244
	regs.ebx = fan & 0xff;
245
	return i8k_smm(&regs) ? : regs.eax & 0xff;
L
Linus Torvalds 已提交
246 247 248 249 250 251 252
}

/*
 * Read the fan speed in RPM.
 */
static int i8k_get_fan_speed(int fan)
{
253
	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
L
Linus Torvalds 已提交
254

255
	regs.ebx = fan & 0xff;
256
	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * fan_mult;
L
Linus Torvalds 已提交
257 258 259 260 261 262 263
}

/*
 * Set the fan speed (off, low, high). Returns the new fan status.
 */
static int i8k_set_fan(int fan, int speed)
{
264
	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
L
Linus Torvalds 已提交
265

266 267
	speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed);
	regs.ebx = (fan & 0xff) | (speed << 8);
L
Linus Torvalds 已提交
268

269
	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
L
Linus Torvalds 已提交
270 271 272 273 274
}

/*
 * Read the cpu temperature.
 */
275
static int i8k_get_temp(int sensor)
L
Linus Torvalds 已提交
276
{
277
	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP, };
278 279
	int rc;
	int temp;
L
Linus Torvalds 已提交
280 281

#ifdef I8K_TEMPERATURE_BUG
282
	static int prev;
L
Linus Torvalds 已提交
283
#endif
284
	regs.ebx = sensor & 0xff;
285
	if ((rc = i8k_smm(&regs)) < 0)
286
		return rc;
287

288
	temp = regs.eax & 0xff;
L
Linus Torvalds 已提交
289 290

#ifdef I8K_TEMPERATURE_BUG
291 292 293 294 295 296 297 298 299 300 301 302 303
	/*
	 * Sometimes the temperature sensor returns 0x99, which is out of range.
	 * In this case we return (once) the previous cached value. For example:
	 # 1003655137 00000058 00005a4b
	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
	 # 1003655139 00000054 00005c52
	 */
	if (temp > I8K_MAX_TEMP) {
		temp = prev;
		prev = I8K_MAX_TEMP;
	} else {
		prev = temp;
	}
L
Linus Torvalds 已提交
304 305
#endif

306
	return temp;
L
Linus Torvalds 已提交
307 308
}

309
static int i8k_get_dell_signature(int req_fn)
L
Linus Torvalds 已提交
310
{
311
	struct smm_regs regs = { .eax = req_fn, };
312
	int rc;
L
Linus Torvalds 已提交
313

314
	if ((rc = i8k_smm(&regs)) < 0)
315
		return rc;
L
Linus Torvalds 已提交
316

317
	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
L
Linus Torvalds 已提交
318 319
}

320 321
static int
i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
322
{
323
	int val = 0;
324 325 326 327 328 329 330 331 332 333 334 335 336 337
	int speed;
	unsigned char buff[16];
	int __user *argp = (int __user *)arg;

	if (!argp)
		return -EINVAL;

	switch (cmd) {
	case I8K_BIOS_VERSION:
		val = i8k_get_bios_version();
		break;

	case I8K_MACHINE_ID:
		memset(buff, 0, 16);
338
		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), sizeof(buff));
339 340 341 342 343 344 345 346 347 348 349
		break;

	case I8K_FN_STATUS:
		val = i8k_get_fn_status();
		break;

	case I8K_POWER_STATUS:
		val = i8k_get_power_status();
		break;

	case I8K_GET_TEMP:
350
		val = i8k_get_temp(0);
351 352 353
		break;

	case I8K_GET_SPEED:
354
		if (copy_from_user(&val, argp, sizeof(int)))
355
			return -EFAULT;
356

357 358
		val = i8k_get_fan_speed(val);
		break;
L
Linus Torvalds 已提交
359

360
	case I8K_GET_FAN:
361
		if (copy_from_user(&val, argp, sizeof(int)))
362
			return -EFAULT;
363

364 365
		val = i8k_get_fan_status(val);
		break;
L
Linus Torvalds 已提交
366

367
	case I8K_SET_FAN:
368
		if (restricted && !capable(CAP_SYS_ADMIN))
369
			return -EPERM;
370 371

		if (copy_from_user(&val, argp, sizeof(int)))
372
			return -EFAULT;
373 374

		if (copy_from_user(&speed, argp + 1, sizeof(int)))
375
			return -EFAULT;
376

377 378
		val = i8k_set_fan(val, speed);
		break;
L
Linus Torvalds 已提交
379

380 381
	default:
		return -EINVAL;
L
Linus Torvalds 已提交
382 383
	}

384
	if (val < 0)
385
		return val;
L
Linus Torvalds 已提交
386

387 388
	switch (cmd) {
	case I8K_BIOS_VERSION:
389
		if (copy_to_user(argp, &val, 4))
390
			return -EFAULT;
391

392 393
		break;
	case I8K_MACHINE_ID:
394
		if (copy_to_user(argp, buff, 16))
395
			return -EFAULT;
396

397 398
		break;
	default:
399
		if (copy_to_user(argp, &val, sizeof(int)))
400
			return -EFAULT;
401

402
		break;
L
Linus Torvalds 已提交
403 404
	}

405
	return 0;
L
Linus Torvalds 已提交
406 407
}

408 409 410 411
static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
	long ret;

412
	mutex_lock(&i8k_mutex);
413
	ret = i8k_ioctl_unlocked(fp, cmd, arg);
414
	mutex_unlock(&i8k_mutex);
415 416 417 418

	return ret;
}

L
Linus Torvalds 已提交
419 420 421
/*
 * Print the information for /proc/i8k.
 */
422
static int i8k_proc_show(struct seq_file *seq, void *offset)
L
Linus Torvalds 已提交
423
{
424
	int fn_key, cpu_temp, ac_power;
425 426
	int left_fan, right_fan, left_speed, right_speed;

427 428 429 430 431 432
	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
	fn_key		= i8k_get_fn_status();			/*   750 µs */
433
	if (power_status)
434
		ac_power = i8k_get_power_status();		/* 14700 µs */
435
	else
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
		ac_power = -1;

	/*
	 * Info:
	 *
	 * 1)  Format version (this will change if format changes)
	 * 2)  BIOS version
	 * 3)  BIOS machine ID
	 * 4)  Cpu temperature
	 * 5)  Left fan status
	 * 6)  Right fan status
	 * 7)  Left fan speed
	 * 8)  Right fan speed
	 * 9)  AC power
	 * 10) Fn Key status
	 */
452 453 454
	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
			  I8K_PROC_FMT,
			  bios_version,
455
			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
456 457 458
			  cpu_temp,
			  left_fan, right_fan, left_speed, right_speed,
			  ac_power, fn_key);
L
Linus Torvalds 已提交
459 460
}

461
static int i8k_open_fs(struct inode *inode, struct file *file)
L
Linus Torvalds 已提交
462
{
463
	return single_open(file, i8k_proc_show, NULL);
L
Linus Torvalds 已提交
464 465
}

466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539

/*
 * Hwmon interface
 */

static ssize_t i8k_hwmon_show_temp(struct device *dev,
				   struct device_attribute *devattr,
				   char *buf)
{
	int cpu_temp;

	cpu_temp = i8k_get_temp(0);
	if (cpu_temp < 0)
		return cpu_temp;
	return sprintf(buf, "%d\n", cpu_temp * 1000);
}

static ssize_t i8k_hwmon_show_fan(struct device *dev,
				  struct device_attribute *devattr,
				  char *buf)
{
	int index = to_sensor_dev_attr(devattr)->index;
	int fan_speed;

	fan_speed = i8k_get_fan_speed(index);
	if (fan_speed < 0)
		return fan_speed;
	return sprintf(buf, "%d\n", fan_speed);
}

static ssize_t i8k_hwmon_show_label(struct device *dev,
				    struct device_attribute *devattr,
				    char *buf)
{
	static const char *labels[4] = {
		"i8k",
		"CPU",
		"Left Fan",
		"Right Fan",
	};
	int index = to_sensor_dev_attr(devattr)->index;

	return sprintf(buf, "%s\n", labels[index]);
}

static DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL);
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
			  I8K_FAN_LEFT);
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
			  I8K_FAN_RIGHT);
static SENSOR_DEVICE_ATTR(name, S_IRUGO, i8k_hwmon_show_label, NULL, 0);
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 1);
static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 2);
static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_label, NULL, 3);

static void i8k_hwmon_remove_files(struct device *dev)
{
	device_remove_file(dev, &dev_attr_temp1_input);
	device_remove_file(dev, &sensor_dev_attr_fan1_input.dev_attr);
	device_remove_file(dev, &sensor_dev_attr_fan2_input.dev_attr);
	device_remove_file(dev, &sensor_dev_attr_temp1_label.dev_attr);
	device_remove_file(dev, &sensor_dev_attr_fan1_label.dev_attr);
	device_remove_file(dev, &sensor_dev_attr_fan2_label.dev_attr);
	device_remove_file(dev, &sensor_dev_attr_name.dev_attr);
}

static int __init i8k_init_hwmon(void)
{
	int err;

	i8k_hwmon_dev = hwmon_device_register(NULL);
	if (IS_ERR(i8k_hwmon_dev)) {
		err = PTR_ERR(i8k_hwmon_dev);
		i8k_hwmon_dev = NULL;
540
		pr_err("hwmon registration failed (%d)\n", err);
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
		return err;
	}

	/* Required name attribute */
	err = device_create_file(i8k_hwmon_dev,
				 &sensor_dev_attr_name.dev_attr);
	if (err)
		goto exit_unregister;

	/* CPU temperature attributes, if temperature reading is OK */
	err = i8k_get_temp(0);
	if (err < 0) {
		dev_dbg(i8k_hwmon_dev,
			"Not creating temperature attributes (%d)\n", err);
	} else {
		err = device_create_file(i8k_hwmon_dev, &dev_attr_temp1_input);
		if (err)
			goto exit_remove_files;
		err = device_create_file(i8k_hwmon_dev,
					 &sensor_dev_attr_temp1_label.dev_attr);
		if (err)
			goto exit_remove_files;
	}

	/* Left fan attributes, if left fan is present */
	err = i8k_get_fan_status(I8K_FAN_LEFT);
	if (err < 0) {
		dev_dbg(i8k_hwmon_dev,
			"Not creating %s fan attributes (%d)\n", "left", err);
	} else {
		err = device_create_file(i8k_hwmon_dev,
					 &sensor_dev_attr_fan1_input.dev_attr);
		if (err)
			goto exit_remove_files;
		err = device_create_file(i8k_hwmon_dev,
					 &sensor_dev_attr_fan1_label.dev_attr);
		if (err)
			goto exit_remove_files;
	}

	/* Right fan attributes, if right fan is present */
	err = i8k_get_fan_status(I8K_FAN_RIGHT);
	if (err < 0) {
		dev_dbg(i8k_hwmon_dev,
			"Not creating %s fan attributes (%d)\n", "right", err);
	} else {
		err = device_create_file(i8k_hwmon_dev,
					 &sensor_dev_attr_fan2_input.dev_attr);
		if (err)
			goto exit_remove_files;
		err = device_create_file(i8k_hwmon_dev,
					 &sensor_dev_attr_fan2_label.dev_attr);
		if (err)
			goto exit_remove_files;
	}

	return 0;

 exit_remove_files:
	i8k_hwmon_remove_files(i8k_hwmon_dev);
 exit_unregister:
	hwmon_device_unregister(i8k_hwmon_dev);
	return err;
}

static void __exit i8k_exit_hwmon(void)
{
	i8k_hwmon_remove_files(i8k_hwmon_dev);
	hwmon_device_unregister(i8k_hwmon_dev);
}

612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
static struct dmi_system_id __initdata i8k_dmi_table[] = {
	{
		.ident = "Dell Inspiron",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
		},
	},
	{
		.ident = "Dell Latitude",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
		},
	},
627 628 629 630 631 632 633 634 635 636 637 638 639 640
	{
		.ident = "Dell Inspiron 2",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
		},
	},
	{
		.ident = "Dell Latitude 2",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
		},
	},
641 642 643 644 645 646 647
	{	/* UK Inspiron 6400  */
		.ident = "Dell Inspiron 3",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
		},
	},
F
Frank Sorenson 已提交
648 649 650 651 652 653 654
	{
		.ident = "Dell Inspiron 3",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
		},
	},
655 656 657 658 659 660 661
	{
		.ident = "Dell Precision",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
		},
	},
F
Federico Heinz 已提交
662 663 664 665 666 667 668
	{
		.ident = "Dell Vostro",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
		},
	},
A
Alan Cox 已提交
669 670 671 672 673 674 675
	{
		.ident = "Dell XPS421",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
		},
	},
F
Federico Heinz 已提交
676
        { }
677
};
L
Linus Torvalds 已提交
678 679 680 681 682 683

/*
 * Probe for the presence of a supported laptop.
 */
static int __init i8k_probe(void)
{
684 685 686
	char buff[4];
	int version;

L
Linus Torvalds 已提交
687
	/*
688
	 * Get DMI information
L
Linus Torvalds 已提交
689
	 */
690 691 692 693
	if (!dmi_check_system(i8k_dmi_table)) {
		if (!ignore_dmi && !force)
			return -ENODEV;

694 695
		pr_info("not running on a supported Dell system.\n");
		pr_info("vendor=%s, model=%s, version=%s\n",
696 697 698
			i8k_get_dmi_data(DMI_SYS_VENDOR),
			i8k_get_dmi_data(DMI_PRODUCT_NAME),
			i8k_get_dmi_data(DMI_BIOS_VERSION));
L
Linus Torvalds 已提交
699
	}
700

701 702
	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), sizeof(bios_version));

L
Linus Torvalds 已提交
703
	/*
704
	 * Get SMM Dell signature
L
Linus Torvalds 已提交
705
	 */
706 707
	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
708
		pr_err("unable to get SMM Dell signature\n");
709 710
		if (!force)
			return -ENODEV;
L
Linus Torvalds 已提交
711 712
	}

713 714 715 716 717
	/*
	 * Get SMM BIOS version.
	 */
	version = i8k_get_bios_version();
	if (version <= 0) {
718
		pr_warn("unable to get SMM BIOS version\n");
719 720 721 722 723 724 725 726
	} else {
		buff[0] = (version >> 16) & 0xff;
		buff[1] = (version >> 8) & 0xff;
		buff[2] = (version) & 0xff;
		buff[3] = '\0';
		/*
		 * If DMI BIOS version is unknown use SMM BIOS version.
		 */
727 728 729
		if (!dmi_get_system_info(DMI_BIOS_VERSION))
			strlcpy(bios_version, buff, sizeof(bios_version));

730 731 732
		/*
		 * Check if the two versions match.
		 */
733
		if (strncmp(buff, bios_version, sizeof(bios_version)) != 0)
734
			pr_warn("BIOS version mismatch: %s != %s\n",
735
				buff, bios_version);
736
	}
L
Linus Torvalds 已提交
737

738
	return 0;
L
Linus Torvalds 已提交
739 740
}

741
static int __init i8k_init(void)
L
Linus Torvalds 已提交
742
{
743
	struct proc_dir_entry *proc_i8k;
744
	int err;
745 746

	/* Are we running on an supported laptop? */
747
	if (i8k_probe())
748 749 750
		return -ENODEV;

	/* Register the proc entry */
751
	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
752
	if (!proc_i8k)
753
		return -ENOENT;
754

755 756 757 758
	err = i8k_init_hwmon();
	if (err)
		goto exit_remove_proc;

759 760
	pr_info("Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
		I8K_VERSION);
761 762

	return 0;
763 764 765 766

 exit_remove_proc:
	remove_proc_entry("i8k", NULL);
	return err;
L
Linus Torvalds 已提交
767 768
}

769
static void __exit i8k_exit(void)
L
Linus Torvalds 已提交
770
{
771
	i8k_exit_hwmon();
772
	remove_proc_entry("i8k", NULL);
L
Linus Torvalds 已提交
773 774
}

775 776
module_init(i8k_init);
module_exit(i8k_exit);