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(®s, 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(®s);
271*4882a593Smuzhiyun mutex_unlock(&tosh_mutex);
272*4882a593Smuzhiyun break;
273*4882a593Smuzhiyun }
274*4882a593Smuzhiyun }
275*4882a593Smuzhiyun err = tosh_smm(®s);
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, ®s, 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(®s);
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(®s);
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