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