xref: /openbmc/linux/drivers/char/toshiba.c (revision 1da177e4)
1 /* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops
2  *
3  * Copyright (c) 1996-2001  Jonathan A. Buzzard (jonathan@buzzard.org.uk)
4  *
5  * Valuable assistance and patches from:
6  *     Tom May <tom@you-bastards.com>
7  *     Rob Napier <rnapier@employees.org>
8  *
9  * Fn status port numbers for machine ID's courtesy of
10  *     0xfc02: Scott Eisert <scott.e@sky-eye.com>
11  *     0xfc04: Steve VanDevender <stevev@efn.org>
12  *     0xfc08: Garth Berry <garth@itsbruce.net>
13  *     0xfc0a: Egbert Eich <eich@xfree86.org>
14  *     0xfc10: Andrew Lofthouse <Andrew.Lofthouse@robins.af.mil>
15  *     0xfc11: Spencer Olson <solson@novell.com>
16  *     0xfc13: Claudius Frankewitz <kryp@gmx.de>
17  *     0xfc15: Tom May <tom@you-bastards.com>
18  *     0xfc17: Dave Konrad <konrad@xenia.it>
19  *     0xfc1a: George Betzos <betzos@engr.colostate.edu>
20  *     0xfc1b: Munemasa Wada <munemasa@jnovel.co.jp>
21  *     0xfc1d: Arthur Liu <armie@slap.mine.nu>
22  *     0xfc5a: Jacques L'helgoualc'h <lhh@free.fr>
23  *     0xfcd1: Mr. Dave Konrad <konrad@xenia.it>
24  *
25  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
26  *
27  *   This code is covered by the GNU GPL and you are free to make any
28  *   changes you wish to it under the terms of the license. However the
29  *   code has the potential to render your computer and/or someone else's
30  *   unusable. Please proceed with care when modifying the code.
31  *
32  * Note: Unfortunately the laptop hardware can close the System Configuration
33  *       Interface on it's own accord. It is therefore necessary for *all*
34  *       programs using this driver to be aware that *any* SCI call can fail at
35  *       *any* time. It is up to any program to be aware of this eventuality
36  *       and take appropriate steps.
37  *
38  * This program is free software; you can redistribute it and/or modify it
39  * under the terms of the GNU General Public License as published by the
40  * Free Software Foundation; either version 2, or (at your option) any
41  * later version.
42  *
43  * This program is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
46  * General Public License for more details.
47  *
48  * The information used to write this driver has been obtained by reverse
49  * engineering the software supplied by Toshiba for their portable computers in
50  * strict accordance with the European Council Directive 92/250/EEC on the legal
51  * protection of computer programs, and it's implementation into English Law by
52  * the Copyright (Computer Programs) Regulations 1992 (S.I. 1992 No.3233).
53  *
54  */
55 
56 #define TOSH_VERSION "1.11 26/9/2001"
57 #define TOSH_DEBUG 0
58 
59 #include <linux/module.h>
60 #include <linux/kernel.h>
61 #include <linux/sched.h>
62 #include <linux/types.h>
63 #include <linux/fcntl.h>
64 #include <linux/miscdevice.h>
65 #include <linux/ioport.h>
66 #include <asm/io.h>
67 #include <asm/uaccess.h>
68 #include <linux/init.h>
69 #include <linux/stat.h>
70 #include <linux/proc_fs.h>
71 
72 #include <linux/toshiba.h>
73 
74 #define TOSH_MINOR_DEV 181
75 
76 static int tosh_id = 0x0000;
77 static int tosh_bios = 0x0000;
78 static int tosh_date = 0x0000;
79 static int tosh_sci = 0x0000;
80 static int tosh_fan = 0;
81 
82 static int tosh_fn = 0;
83 
84 module_param(tosh_fn, int, 0);
85 
86 
87 static int tosh_ioctl(struct inode *, struct file *, unsigned int,
88 	unsigned long);
89 
90 
91 static struct file_operations tosh_fops = {
92 	.owner		= THIS_MODULE,
93 	.ioctl		= tosh_ioctl,
94 };
95 
96 static struct miscdevice tosh_device = {
97 	TOSH_MINOR_DEV,
98 	"toshiba",
99 	&tosh_fops
100 };
101 
102 /*
103  * Read the Fn key status
104  */
105 #ifdef CONFIG_PROC_FS
106 static int tosh_fn_status(void)
107 {
108         unsigned char scan;
109 	unsigned long flags;
110 
111 	if (tosh_fn!=0) {
112 		scan = inb(tosh_fn);
113 	} else {
114 		local_irq_save(flags);
115 		outb(0x8e, 0xe4);
116 		scan = inb(0xe5);
117 		local_irq_restore(flags);
118 	}
119 
120         return (int) scan;
121 }
122 #endif
123 
124 
125 /*
126  * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function
127  */
128 static int tosh_emulate_fan(SMMRegisters *regs)
129 {
130 	unsigned long eax,ecx,flags;
131 	unsigned char al;
132 
133 	eax = regs->eax & 0xff00;
134 	ecx = regs->ecx & 0xffff;
135 
136 	/* Portage 610CT */
137 
138 	if (tosh_id==0xfccb) {
139 		if (eax==0xfe00) {
140 			/* fan status */
141 			local_irq_save(flags);
142 			outb(0xbe, 0xe4);
143 			al = inb(0xe5);
144 			local_irq_restore(flags);
145 			regs->eax = 0x00;
146 			regs->ecx = (unsigned int) (al & 0x01);
147 		}
148 		if ((eax==0xff00) && (ecx==0x0000)) {
149 			/* fan off */
150 			local_irq_save(flags);
151 			outb(0xbe, 0xe4);
152 			al = inb(0xe5);
153 			outb(0xbe, 0xe4);
154 			outb (al | 0x01, 0xe5);
155 			local_irq_restore(flags);
156 			regs->eax = 0x00;
157 			regs->ecx = 0x00;
158 		}
159 		if ((eax==0xff00) && (ecx==0x0001)) {
160 			/* fan on */
161 			local_irq_save(flags);
162 			outb(0xbe, 0xe4);
163 			al = inb(0xe5);
164 			outb(0xbe, 0xe4);
165 			outb(al & 0xfe, 0xe5);
166 			local_irq_restore(flags);
167 			regs->eax = 0x00;
168 			regs->ecx = 0x01;
169 		}
170 	}
171 
172 	/* Tecra 700CS/CDT */
173 
174 	if (tosh_id==0xfccc) {
175 		if (eax==0xfe00) {
176 			/* fan status */
177 			local_irq_save(flags);
178 			outb(0xe0, 0xe4);
179 			al = inb(0xe5);
180 			local_irq_restore(flags);
181 			regs->eax = 0x00;
182 			regs->ecx = al & 0x01;
183 		}
184 		if ((eax==0xff00) && (ecx==0x0000)) {
185 			/* fan off */
186 			local_irq_save(flags);
187 			outb(0xe0, 0xe4);
188 			al = inb(0xe5);
189 			outw(0xe0 | ((al & 0xfe) << 8), 0xe4);
190 			local_irq_restore(flags);
191 			regs->eax = 0x00;
192 			regs->ecx = 0x00;
193 		}
194 		if ((eax==0xff00) && (ecx==0x0001)) {
195 			/* fan on */
196 			local_irq_save(flags);
197 			outb(0xe0, 0xe4);
198 			al = inb(0xe5);
199 			outw(0xe0 | ((al | 0x01) << 8), 0xe4);
200 			local_irq_restore(flags);
201 			regs->eax = 0x00;
202 			regs->ecx = 0x01;
203 		}
204 	}
205 
206 	return 0;
207 }
208 
209 
210 /*
211  * Put the laptop into System Management Mode
212  */
213 int tosh_smm(SMMRegisters *regs)
214 {
215 	int eax;
216 
217 	asm ("# load the values into the registers\n\t" \
218 		"pushl %%eax\n\t" \
219 		"movl 0(%%eax),%%edx\n\t" \
220 		"push %%edx\n\t" \
221 		"movl 4(%%eax),%%ebx\n\t" \
222 		"movl 8(%%eax),%%ecx\n\t" \
223 		"movl 12(%%eax),%%edx\n\t" \
224 		"movl 16(%%eax),%%esi\n\t" \
225 		"movl 20(%%eax),%%edi\n\t" \
226 		"popl %%eax\n\t" \
227 		"# call the System Management mode\n\t" \
228 		"inb $0xb2,%%al\n\t"
229 		"# fill out the memory with the values in the registers\n\t" \
230 		"xchgl %%eax,(%%esp)\n\t"
231 		"movl %%ebx,4(%%eax)\n\t" \
232 		"movl %%ecx,8(%%eax)\n\t" \
233 		"movl %%edx,12(%%eax)\n\t" \
234 		"movl %%esi,16(%%eax)\n\t" \
235 		"movl %%edi,20(%%eax)\n\t" \
236 		"popl %%edx\n\t" \
237 		"movl %%edx,0(%%eax)\n\t" \
238 		"# setup the return value to the carry flag\n\t" \
239 		"lahf\n\t" \
240 		"shrl $8,%%eax\n\t" \
241 		"andl $1,%%eax\n" \
242 		: "=a" (eax)
243 		: "a" (regs)
244 		: "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
245 
246 	return eax;
247 }
248 
249 
250 static int tosh_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
251 	unsigned long arg)
252 {
253 	SMMRegisters regs;
254 	SMMRegisters __user *argp = (SMMRegisters __user *)arg;
255 	unsigned short ax,bx;
256 	int err;
257 
258 	if (!argp)
259 		return -EINVAL;
260 
261 	if (copy_from_user(&regs, argp, sizeof(SMMRegisters)))
262 		return -EFAULT;
263 
264 	switch (cmd) {
265 		case TOSH_SMM:
266 			ax = regs.eax & 0xff00;
267 			bx = regs.ebx & 0xffff;
268 			/* block HCI calls to read/write memory & PCI devices */
269 			if (((ax==0xff00) || (ax==0xfe00)) && (bx>0x0069))
270 				return -EINVAL;
271 
272 			/* do we need to emulate the fan ? */
273 			if (tosh_fan==1) {
274 				if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) {
275 					err = tosh_emulate_fan(&regs);
276 					break;
277 				}
278 			}
279 			err = tosh_smm(&regs);
280 			break;
281 		default:
282 			return -EINVAL;
283 	}
284 
285         if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))
286         	return -EFAULT;
287 
288 	return (err==0) ? 0:-EINVAL;
289 }
290 
291 
292 /*
293  * Print the information for /proc/toshiba
294  */
295 #ifdef CONFIG_PROC_FS
296 static int tosh_get_info(char *buffer, char **start, off_t fpos, int length)
297 {
298 	char *temp;
299 	int key;
300 
301 	temp = buffer;
302 	key = tosh_fn_status();
303 
304 	/* Arguments
305 	     0) Linux driver version (this will change if format changes)
306 	     1) Machine ID
307 	     2) SCI version
308 	     3) BIOS version (major, minor)
309 	     4) BIOS date (in SCI date format)
310 	     5) Fn Key status
311 	*/
312 
313 	temp += sprintf(temp, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n",
314 		tosh_id,
315 		(tosh_sci & 0xff00)>>8,
316 		tosh_sci & 0xff,
317 		(tosh_bios & 0xff00)>>8,
318 		tosh_bios & 0xff,
319 		tosh_date,
320 		key);
321 
322 	return temp-buffer;
323 }
324 #endif
325 
326 
327 /*
328  * Determine which port to use for the Fn key status
329  */
330 static void tosh_set_fn_port(void)
331 {
332 	switch (tosh_id) {
333 		case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10:
334 		case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b:
335 		case 0xfc5a:
336 			tosh_fn = 0x62;
337 			break;
338 		case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0:
339 		case 0xfce2:
340 			tosh_fn = 0x68;
341 			break;
342 		default:
343 			tosh_fn = 0x00;
344 			break;
345 	}
346 
347 	return;
348 }
349 
350 
351 /*
352  * Get the machine identification number of the current model
353  */
354 static int tosh_get_machine_id(void)
355 {
356 	int id;
357 	SMMRegisters regs;
358 	unsigned short bx,cx;
359 	unsigned long address;
360 
361 	id = (0x100*(int) isa_readb(0xffffe))+((int) isa_readb(0xffffa));
362 
363 	/* do we have a SCTTable machine identication number on our hands */
364 
365 	if (id==0xfc2f) {
366 
367 		/* start by getting a pointer into the BIOS */
368 
369 		regs.eax = 0xc000;
370 		regs.ebx = 0x0000;
371 		regs.ecx = 0x0000;
372 		tosh_smm(&regs);
373 		bx = (unsigned short) (regs.ebx & 0xffff);
374 
375 		/* At this point in the Toshiba routines under MS Windows
376 		   the bx register holds 0xe6f5. However my code is producing
377 		   a different value! For the time being I will just fudge the
378 		   value. This has been verified on a Satellite Pro 430CDT,
379 		   Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */
380 #if TOSH_DEBUG
381 		printk("toshiba: debugging ID ebx=0x%04x\n", regs.ebx);
382 #endif
383 		bx = 0xe6f5;
384 
385 		/* now twiddle with our pointer a bit */
386 
387 		address = 0x000f0000+bx;
388 		cx = isa_readw(address);
389 		address = 0x000f0009+bx+cx;
390 		cx = isa_readw(address);
391 		address = 0x000f000a+cx;
392 		cx = isa_readw(address);
393 
394 		/* now construct our machine identification number */
395 
396 		id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8);
397 	}
398 
399 	return id;
400 }
401 
402 
403 /*
404  * Probe for the presence of a Toshiba laptop
405  *
406  *   returns and non-zero if unable to detect the presence of a Toshiba
407  *   laptop, otherwise zero and determines the Machine ID, BIOS version and
408  *   date, and SCI version.
409  */
410 static int tosh_probe(void)
411 {
412 	int i,major,minor,day,year,month,flag;
413 	unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 };
414 	SMMRegisters regs;
415 
416 	/* extra sanity check for the string "TOSHIBA" in the BIOS because
417 	   some machines that are not Toshiba's pass the next test */
418 
419 	for (i=0;i<7;i++) {
420 		if (isa_readb(0xfe010+i)!=signature[i]) {
421 			printk("toshiba: not a supported Toshiba laptop\n");
422 			return -ENODEV;
423 		}
424 	}
425 
426 	/* call the Toshiba SCI support check routine */
427 
428 	regs.eax = 0xf0f0;
429 	regs.ebx = 0x0000;
430 	regs.ecx = 0x0000;
431 	flag = tosh_smm(&regs);
432 
433 	/* if this is not a Toshiba laptop carry flag is set and ah=0x86 */
434 
435 	if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) {
436 		printk("toshiba: not a supported Toshiba laptop\n");
437 		return -ENODEV;
438 	}
439 
440 	/* if we get this far then we are running on a Toshiba (probably)! */
441 
442 	tosh_sci = regs.edx & 0xffff;
443 
444 	/* next get the machine ID of the current laptop */
445 
446 	tosh_id = tosh_get_machine_id();
447 
448 	/* get the BIOS version */
449 
450 	major = isa_readb(0xfe009)-'0';
451 	minor = ((isa_readb(0xfe00b)-'0')*10)+(isa_readb(0xfe00c)-'0');
452 	tosh_bios = (major*0x100)+minor;
453 
454 	/* get the BIOS date */
455 
456 	day = ((isa_readb(0xffff5)-'0')*10)+(isa_readb(0xffff6)-'0');
457 	month = ((isa_readb(0xffff8)-'0')*10)+(isa_readb(0xffff9)-'0');
458 	year = ((isa_readb(0xffffb)-'0')*10)+(isa_readb(0xffffc)-'0');
459 	tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6)
460 		| ((day & 0x1f)<<1);
461 
462 
463 	/* in theory we should check the ports we are going to use for the
464 	   fn key detection (and the fan on the Portage 610/Tecra700), and
465 	   then request them to stop other drivers using them. However as
466 	   the keyboard driver grabs 0x60-0x6f and the pic driver grabs
467 	   0xa0-0xbf we can't. We just have to live dangerously and use the
468 	   ports anyway, oh boy! */
469 
470 	/* do we need to emulate the fan? */
471 
472 	if ((tosh_id==0xfccb) || (tosh_id==0xfccc))
473 		tosh_fan = 1;
474 
475 	return 0;
476 }
477 
478 int __init tosh_init(void)
479 {
480 	int retval;
481 	/* are we running on a Toshiba laptop */
482 
483 	if (tosh_probe()!=0)
484 		return -EIO;
485 
486 	printk(KERN_INFO "Toshiba System Managment Mode driver v"
487 		TOSH_VERSION"\n");
488 
489 	/* set the port to use for Fn status if not specified as a parameter */
490 	if (tosh_fn==0x00)
491 		tosh_set_fn_port();
492 
493 	/* register the device file */
494 	retval = misc_register(&tosh_device);
495 	if(retval < 0)
496 		return retval;
497 
498 #ifdef CONFIG_PROC_FS
499 	/* register the proc entry */
500 	if(create_proc_info_entry("toshiba", 0, NULL, tosh_get_info) == NULL){
501 		misc_deregister(&tosh_device);
502 		return -ENOMEM;
503 	}
504 #endif
505 
506 	return 0;
507 }
508 
509 #ifdef MODULE
510 int init_module(void)
511 {
512 	return tosh_init();
513 }
514 
515 void cleanup_module(void)
516 {
517 	/* remove the proc entry */
518 
519 	remove_proc_entry("toshiba", NULL);
520 
521 	/* unregister the device file */
522 
523 	misc_deregister(&tosh_device);
524 }
525 #endif
526 
527 MODULE_LICENSE("GPL");
528 MODULE_PARM_DESC(tosh_fn, "User specified Fn key detection port");
529 MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>");
530 MODULE_DESCRIPTION("Toshiba laptop SMM driver");
531 MODULE_SUPPORTED_DEVICE("toshiba");
532 
533