File: /usr/src/linux/drivers/char/toshiba.c

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