i8k.c revision fe04f22fd2bc84dfcc0ef1c7acb863bd98b9ac93
1/*
2 * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
3 *	    See http://www.debian.org/~dz/i8k/ for more information
4 *	    and for latest version of this driver.
5 *
6 * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
11 * later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * General Public License for more details.
17 */
18
19#include <linux/module.h>
20#include <linux/types.h>
21#include <linux/init.h>
22#include <linux/proc_fs.h>
23#include <linux/seq_file.h>
24#include <linux/dmi.h>
25#include <linux/capability.h>
26#include <asm/uaccess.h>
27#include <asm/io.h>
28
29#include <linux/i8k.h>
30
31#define I8K_VERSION		"1.14 21/02/2005"
32
33#define I8K_SMM_FN_STATUS	0x0025
34#define I8K_SMM_POWER_STATUS	0x0069
35#define I8K_SMM_SET_FAN		0x01a3
36#define I8K_SMM_GET_FAN		0x00a3
37#define I8K_SMM_GET_SPEED	0x02a3
38#define I8K_SMM_GET_TEMP	0x10a3
39#define I8K_SMM_GET_DELL_SIG1	0xfea3
40#define I8K_SMM_GET_DELL_SIG2	0xffa3
41#define I8K_SMM_BIOS_VERSION	0x00a6
42
43#define I8K_FAN_MULT		30
44#define I8K_MAX_TEMP		127
45
46#define I8K_FN_NONE		0x00
47#define I8K_FN_UP		0x01
48#define I8K_FN_DOWN		0x02
49#define I8K_FN_MUTE		0x04
50#define I8K_FN_MASK		0x07
51#define I8K_FN_SHIFT		8
52
53#define I8K_POWER_AC		0x05
54#define I8K_POWER_BATTERY	0x01
55
56#define I8K_TEMPERATURE_BUG	1
57
58static char bios_version[4];
59
60MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
61MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
62MODULE_LICENSE("GPL");
63
64static int force;
65module_param(force, bool, 0);
66MODULE_PARM_DESC(force, "Force loading without checking for supported models");
67
68static int ignore_dmi;
69module_param(ignore_dmi, bool, 0);
70MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
71
72static int restricted;
73module_param(restricted, bool, 0);
74MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
75
76static int power_status;
77module_param(power_status, bool, 0600);
78MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
79
80static int i8k_open_fs(struct inode *inode, struct file *file);
81static int i8k_ioctl(struct inode *, struct file *, unsigned int,
82		     unsigned long);
83
84static const struct file_operations i8k_fops = {
85	.open		= i8k_open_fs,
86	.read		= seq_read,
87	.llseek		= seq_lseek,
88	.release	= single_release,
89	.ioctl		= i8k_ioctl,
90};
91
92struct smm_regs {
93	unsigned int eax;
94	unsigned int ebx __attribute__ ((packed));
95	unsigned int ecx __attribute__ ((packed));
96	unsigned int edx __attribute__ ((packed));
97	unsigned int esi __attribute__ ((packed));
98	unsigned int edi __attribute__ ((packed));
99};
100
101static inline const char *i8k_get_dmi_data(int field)
102{
103	const char *dmi_data = dmi_get_system_info(field);
104
105	return dmi_data && *dmi_data ? dmi_data : "?";
106}
107
108/*
109 * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
110 */
111static int i8k_smm(struct smm_regs *regs)
112{
113	int rc;
114	int eax = regs->eax;
115
116#if defined(CONFIG_X86_64)
117	asm("pushq %%rax\n\t"
118		"movl 0(%%rax),%%edx\n\t"
119		"pushq %%rdx\n\t"
120		"movl 4(%%rax),%%ebx\n\t"
121		"movl 8(%%rax),%%ecx\n\t"
122		"movl 12(%%rax),%%edx\n\t"
123		"movl 16(%%rax),%%esi\n\t"
124		"movl 20(%%rax),%%edi\n\t"
125		"popq %%rax\n\t"
126		"out %%al,$0xb2\n\t"
127		"out %%al,$0x84\n\t"
128		"xchgq %%rax,(%%rsp)\n\t"
129		"movl %%ebx,4(%%rax)\n\t"
130		"movl %%ecx,8(%%rax)\n\t"
131		"movl %%edx,12(%%rax)\n\t"
132		"movl %%esi,16(%%rax)\n\t"
133		"movl %%edi,20(%%rax)\n\t"
134		"popq %%rdx\n\t"
135		"movl %%edx,0(%%rax)\n\t"
136		"lahf\n\t"
137		"shrl $8,%%eax\n\t"
138		"andl $1,%%eax\n"
139		:"=a"(rc)
140		:    "a"(regs)
141		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
142#else
143	asm("pushl %%eax\n\t"
144	    "movl 0(%%eax),%%edx\n\t"
145	    "push %%edx\n\t"
146	    "movl 4(%%eax),%%ebx\n\t"
147	    "movl 8(%%eax),%%ecx\n\t"
148	    "movl 12(%%eax),%%edx\n\t"
149	    "movl 16(%%eax),%%esi\n\t"
150	    "movl 20(%%eax),%%edi\n\t"
151	    "popl %%eax\n\t"
152	    "out %%al,$0xb2\n\t"
153	    "out %%al,$0x84\n\t"
154	    "xchgl %%eax,(%%esp)\n\t"
155	    "movl %%ebx,4(%%eax)\n\t"
156	    "movl %%ecx,8(%%eax)\n\t"
157	    "movl %%edx,12(%%eax)\n\t"
158	    "movl %%esi,16(%%eax)\n\t"
159	    "movl %%edi,20(%%eax)\n\t"
160	    "popl %%edx\n\t"
161	    "movl %%edx,0(%%eax)\n\t"
162	    "lahf\n\t"
163	    "shrl $8,%%eax\n\t"
164	    "andl $1,%%eax\n":"=a"(rc)
165	    :    "a"(regs)
166	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
167#endif
168	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
169		return -EINVAL;
170
171	return 0;
172}
173
174/*
175 * Read the bios version. Return the version as an integer corresponding
176 * to the ascii value, for example "A17" is returned as 0x00413137.
177 */
178static int i8k_get_bios_version(void)
179{
180	struct smm_regs regs = { .eax = I8K_SMM_BIOS_VERSION, };
181
182	return i8k_smm(&regs) ? : regs.eax;
183}
184
185/*
186 * Read the Fn key status.
187 */
188static int i8k_get_fn_status(void)
189{
190	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
191	int rc;
192
193	if ((rc = i8k_smm(&regs)) < 0)
194		return rc;
195
196	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
197	case I8K_FN_UP:
198		return I8K_VOL_UP;
199	case I8K_FN_DOWN:
200		return I8K_VOL_DOWN;
201	case I8K_FN_MUTE:
202		return I8K_VOL_MUTE;
203	default:
204		return 0;
205	}
206}
207
208/*
209 * Read the power status.
210 */
211static int i8k_get_power_status(void)
212{
213	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
214	int rc;
215
216	if ((rc = i8k_smm(&regs)) < 0)
217		return rc;
218
219	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
220}
221
222/*
223 * Read the fan status.
224 */
225static int i8k_get_fan_status(int fan)
226{
227	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
228
229	regs.ebx = fan & 0xff;
230	return i8k_smm(&regs) ? : regs.eax & 0xff;
231}
232
233/*
234 * Read the fan speed in RPM.
235 */
236static int i8k_get_fan_speed(int fan)
237{
238	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
239
240	regs.ebx = fan & 0xff;
241	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * I8K_FAN_MULT;
242}
243
244/*
245 * Set the fan speed (off, low, high). Returns the new fan status.
246 */
247static int i8k_set_fan(int fan, int speed)
248{
249	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
250
251	speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed);
252	regs.ebx = (fan & 0xff) | (speed << 8);
253
254	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
255}
256
257/*
258 * Read the cpu temperature.
259 */
260static int i8k_get_temp(int sensor)
261{
262	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP, };
263	int rc;
264	int temp;
265
266#ifdef I8K_TEMPERATURE_BUG
267	static int prev;
268#endif
269	regs.ebx = sensor & 0xff;
270	if ((rc = i8k_smm(&regs)) < 0)
271		return rc;
272
273	temp = regs.eax & 0xff;
274
275#ifdef I8K_TEMPERATURE_BUG
276	/*
277	 * Sometimes the temperature sensor returns 0x99, which is out of range.
278	 * In this case we return (once) the previous cached value. For example:
279	 # 1003655137 00000058 00005a4b
280	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
281	 # 1003655139 00000054 00005c52
282	 */
283	if (temp > I8K_MAX_TEMP) {
284		temp = prev;
285		prev = I8K_MAX_TEMP;
286	} else {
287		prev = temp;
288	}
289#endif
290
291	return temp;
292}
293
294static int i8k_get_dell_signature(int req_fn)
295{
296	struct smm_regs regs = { .eax = req_fn, };
297	int rc;
298
299	if ((rc = i8k_smm(&regs)) < 0)
300		return rc;
301
302	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
303}
304
305static int i8k_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
306		     unsigned long arg)
307{
308	int val = 0;
309	int speed;
310	unsigned char buff[16];
311	int __user *argp = (int __user *)arg;
312
313	if (!argp)
314		return -EINVAL;
315
316	switch (cmd) {
317	case I8K_BIOS_VERSION:
318		val = i8k_get_bios_version();
319		break;
320
321	case I8K_MACHINE_ID:
322		memset(buff, 0, 16);
323		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), sizeof(buff));
324		break;
325
326	case I8K_FN_STATUS:
327		val = i8k_get_fn_status();
328		break;
329
330	case I8K_POWER_STATUS:
331		val = i8k_get_power_status();
332		break;
333
334	case I8K_GET_TEMP:
335		val = i8k_get_temp(0);
336		break;
337
338	case I8K_GET_SPEED:
339		if (copy_from_user(&val, argp, sizeof(int)))
340			return -EFAULT;
341
342		val = i8k_get_fan_speed(val);
343		break;
344
345	case I8K_GET_FAN:
346		if (copy_from_user(&val, argp, sizeof(int)))
347			return -EFAULT;
348
349		val = i8k_get_fan_status(val);
350		break;
351
352	case I8K_SET_FAN:
353		if (restricted && !capable(CAP_SYS_ADMIN))
354			return -EPERM;
355
356		if (copy_from_user(&val, argp, sizeof(int)))
357			return -EFAULT;
358
359		if (copy_from_user(&speed, argp + 1, sizeof(int)))
360			return -EFAULT;
361
362		val = i8k_set_fan(val, speed);
363		break;
364
365	default:
366		return -EINVAL;
367	}
368
369	if (val < 0)
370		return val;
371
372	switch (cmd) {
373	case I8K_BIOS_VERSION:
374		if (copy_to_user(argp, &val, 4))
375			return -EFAULT;
376
377		break;
378	case I8K_MACHINE_ID:
379		if (copy_to_user(argp, buff, 16))
380			return -EFAULT;
381
382		break;
383	default:
384		if (copy_to_user(argp, &val, sizeof(int)))
385			return -EFAULT;
386
387		break;
388	}
389
390	return 0;
391}
392
393/*
394 * Print the information for /proc/i8k.
395 */
396static int i8k_proc_show(struct seq_file *seq, void *offset)
397{
398	int fn_key, cpu_temp, ac_power;
399	int left_fan, right_fan, left_speed, right_speed;
400
401	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
402	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
403	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
404	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
405	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
406	fn_key		= i8k_get_fn_status();			/*   750 µs */
407	if (power_status)
408		ac_power = i8k_get_power_status();		/* 14700 µs */
409	else
410		ac_power = -1;
411
412	/*
413	 * Info:
414	 *
415	 * 1)  Format version (this will change if format changes)
416	 * 2)  BIOS version
417	 * 3)  BIOS machine ID
418	 * 4)  Cpu temperature
419	 * 5)  Left fan status
420	 * 6)  Right fan status
421	 * 7)  Left fan speed
422	 * 8)  Right fan speed
423	 * 9)  AC power
424	 * 10) Fn Key status
425	 */
426	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
427			  I8K_PROC_FMT,
428			  bios_version,
429			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
430			  cpu_temp,
431			  left_fan, right_fan, left_speed, right_speed,
432			  ac_power, fn_key);
433}
434
435static int i8k_open_fs(struct inode *inode, struct file *file)
436{
437	return single_open(file, i8k_proc_show, NULL);
438}
439
440static struct dmi_system_id __initdata i8k_dmi_table[] = {
441	{
442		.ident = "Dell Inspiron",
443		.matches = {
444			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
445			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
446		},
447	},
448	{
449		.ident = "Dell Latitude",
450		.matches = {
451			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
452			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
453		},
454	},
455	{
456		.ident = "Dell Inspiron 2",
457		.matches = {
458			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
459			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
460		},
461	},
462	{
463		.ident = "Dell Latitude 2",
464		.matches = {
465			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
466			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
467		},
468	},
469	{	/* UK Inspiron 6400  */
470		.ident = "Dell Inspiron 3",
471		.matches = {
472			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
473			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
474		},
475	},
476	{ }
477};
478
479/*
480 * Probe for the presence of a supported laptop.
481 */
482static int __init i8k_probe(void)
483{
484	char buff[4];
485	int version;
486
487	/*
488	 * Get DMI information
489	 */
490	if (!dmi_check_system(i8k_dmi_table)) {
491		if (!ignore_dmi && !force)
492			return -ENODEV;
493
494		printk(KERN_INFO "i8k: not running on a supported Dell system.\n");
495		printk(KERN_INFO "i8k: vendor=%s, model=%s, version=%s\n",
496			i8k_get_dmi_data(DMI_SYS_VENDOR),
497			i8k_get_dmi_data(DMI_PRODUCT_NAME),
498			i8k_get_dmi_data(DMI_BIOS_VERSION));
499	}
500
501	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), sizeof(bios_version));
502
503	/*
504	 * Get SMM Dell signature
505	 */
506	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
507	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
508		printk(KERN_ERR "i8k: unable to get SMM Dell signature\n");
509		if (!force)
510			return -ENODEV;
511	}
512
513	/*
514	 * Get SMM BIOS version.
515	 */
516	version = i8k_get_bios_version();
517	if (version <= 0) {
518		printk(KERN_WARNING "i8k: unable to get SMM BIOS version\n");
519	} else {
520		buff[0] = (version >> 16) & 0xff;
521		buff[1] = (version >> 8) & 0xff;
522		buff[2] = (version) & 0xff;
523		buff[3] = '\0';
524		/*
525		 * If DMI BIOS version is unknown use SMM BIOS version.
526		 */
527		if (!dmi_get_system_info(DMI_BIOS_VERSION))
528			strlcpy(bios_version, buff, sizeof(bios_version));
529
530		/*
531		 * Check if the two versions match.
532		 */
533		if (strncmp(buff, bios_version, sizeof(bios_version)) != 0)
534			printk(KERN_WARNING "i8k: BIOS version mismatch: %s != %s\n",
535				buff, bios_version);
536	}
537
538	return 0;
539}
540
541static int __init i8k_init(void)
542{
543	struct proc_dir_entry *proc_i8k;
544
545	/* Are we running on an supported laptop? */
546	if (i8k_probe())
547		return -ENODEV;
548
549	/* Register the proc entry */
550	proc_i8k = create_proc_entry("i8k", 0, NULL);
551	if (!proc_i8k)
552		return -ENOENT;
553
554	proc_i8k->proc_fops = &i8k_fops;
555	proc_i8k->owner = THIS_MODULE;
556
557	printk(KERN_INFO
558	       "Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
559	       I8K_VERSION);
560
561	return 0;
562}
563
564static void __exit i8k_exit(void)
565{
566	remove_proc_entry("i8k", NULL);
567}
568
569module_init(i8k_init);
570module_exit(i8k_exit);
571