Root/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 * 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/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#include <linux/seq_file.h>
71#include <linux/mutex.h>
72#include <linux/toshiba.h>
73
74#define TOSH_MINOR_DEV 181
75
76MODULE_LICENSE("GPL");
77MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>");
78MODULE_DESCRIPTION("Toshiba laptop SMM driver");
79MODULE_SUPPORTED_DEVICE("toshiba");
80
81static DEFINE_MUTEX(tosh_mutex);
82static int tosh_fn;
83module_param_named(fn, tosh_fn, int, 0);
84MODULE_PARM_DESC(fn, "User specified Fn key detection port");
85
86static int tosh_id;
87static int tosh_bios;
88static int tosh_date;
89static int tosh_sci;
90static int tosh_fan;
91
92static long tosh_ioctl(struct file *, unsigned int,
93    unsigned long);
94
95
96static const struct file_operations tosh_fops = {
97    .owner = THIS_MODULE,
98    .unlocked_ioctl = tosh_ioctl,
99    .llseek = noop_llseek,
100};
101
102static struct miscdevice tosh_device = {
103    TOSH_MINOR_DEV,
104    "toshiba",
105    &tosh_fops
106};
107
108/*
109 * Read the Fn key status
110 */
111#ifdef CONFIG_PROC_FS
112static int tosh_fn_status(void)
113{
114        unsigned char scan;
115    unsigned long flags;
116
117    if (tosh_fn!=0) {
118        scan = inb(tosh_fn);
119    } else {
120        local_irq_save(flags);
121        outb(0x8e, 0xe4);
122        scan = inb(0xe5);
123        local_irq_restore(flags);
124    }
125
126        return (int) scan;
127}
128#endif
129
130
131/*
132 * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function
133 */
134static int tosh_emulate_fan(SMMRegisters *regs)
135{
136    unsigned long eax,ecx,flags;
137    unsigned char al;
138
139    eax = regs->eax & 0xff00;
140    ecx = regs->ecx & 0xffff;
141
142    /* Portage 610CT */
143
144    if (tosh_id==0xfccb) {
145        if (eax==0xfe00) {
146            /* fan status */
147            local_irq_save(flags);
148            outb(0xbe, 0xe4);
149            al = inb(0xe5);
150            local_irq_restore(flags);
151            regs->eax = 0x00;
152            regs->ecx = (unsigned int) (al & 0x01);
153        }
154        if ((eax==0xff00) && (ecx==0x0000)) {
155            /* fan off */
156            local_irq_save(flags);
157            outb(0xbe, 0xe4);
158            al = inb(0xe5);
159            outb(0xbe, 0xe4);
160            outb (al | 0x01, 0xe5);
161            local_irq_restore(flags);
162            regs->eax = 0x00;
163            regs->ecx = 0x00;
164        }
165        if ((eax==0xff00) && (ecx==0x0001)) {
166            /* fan on */
167            local_irq_save(flags);
168            outb(0xbe, 0xe4);
169            al = inb(0xe5);
170            outb(0xbe, 0xe4);
171            outb(al & 0xfe, 0xe5);
172            local_irq_restore(flags);
173            regs->eax = 0x00;
174            regs->ecx = 0x01;
175        }
176    }
177
178    /* Tecra 700CS/CDT */
179
180    if (tosh_id==0xfccc) {
181        if (eax==0xfe00) {
182            /* fan status */
183            local_irq_save(flags);
184            outb(0xe0, 0xe4);
185            al = inb(0xe5);
186            local_irq_restore(flags);
187            regs->eax = 0x00;
188            regs->ecx = al & 0x01;
189        }
190        if ((eax==0xff00) && (ecx==0x0000)) {
191            /* fan off */
192            local_irq_save(flags);
193            outb(0xe0, 0xe4);
194            al = inb(0xe5);
195            outw(0xe0 | ((al & 0xfe) << 8), 0xe4);
196            local_irq_restore(flags);
197            regs->eax = 0x00;
198            regs->ecx = 0x00;
199        }
200        if ((eax==0xff00) && (ecx==0x0001)) {
201            /* fan on */
202            local_irq_save(flags);
203            outb(0xe0, 0xe4);
204            al = inb(0xe5);
205            outw(0xe0 | ((al | 0x01) << 8), 0xe4);
206            local_irq_restore(flags);
207            regs->eax = 0x00;
208            regs->ecx = 0x01;
209        }
210    }
211
212    return 0;
213}
214
215
216/*
217 * Put the laptop into System Management Mode
218 */
219int tosh_smm(SMMRegisters *regs)
220{
221    int eax;
222
223    asm ("# load the values into the registers\n\t" \
224        "pushl %%eax\n\t" \
225        "movl 0(%%eax),%%edx\n\t" \
226        "push %%edx\n\t" \
227        "movl 4(%%eax),%%ebx\n\t" \
228        "movl 8(%%eax),%%ecx\n\t" \
229        "movl 12(%%eax),%%edx\n\t" \
230        "movl 16(%%eax),%%esi\n\t" \
231        "movl 20(%%eax),%%edi\n\t" \
232        "popl %%eax\n\t" \
233        "# call the System Management mode\n\t" \
234        "inb $0xb2,%%al\n\t"
235        "# fill out the memory with the values in the registers\n\t" \
236        "xchgl %%eax,(%%esp)\n\t"
237        "movl %%ebx,4(%%eax)\n\t" \
238        "movl %%ecx,8(%%eax)\n\t" \
239        "movl %%edx,12(%%eax)\n\t" \
240        "movl %%esi,16(%%eax)\n\t" \
241        "movl %%edi,20(%%eax)\n\t" \
242        "popl %%edx\n\t" \
243        "movl %%edx,0(%%eax)\n\t" \
244        "# setup the return value to the carry flag\n\t" \
245        "lahf\n\t" \
246        "shrl $8,%%eax\n\t" \
247        "andl $1,%%eax\n" \
248        : "=a" (eax)
249        : "a" (regs)
250        : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
251
252    return eax;
253}
254EXPORT_SYMBOL(tosh_smm);
255
256
257static long tosh_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
258{
259    SMMRegisters regs;
260    SMMRegisters __user *argp = (SMMRegisters __user *)arg;
261    unsigned short ax,bx;
262    int err;
263
264    if (!argp)
265        return -EINVAL;
266
267    if (copy_from_user(&regs, argp, 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            mutex_lock(&tosh_mutex);
280            if (tosh_fan==1) {
281                if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) {
282                    err = tosh_emulate_fan(&regs);
283                    mutex_unlock(&tosh_mutex);
284                    break;
285                }
286            }
287            err = tosh_smm(&regs);
288            mutex_unlock(&tosh_mutex);
289            break;
290        default:
291            return -EINVAL;
292    }
293
294        if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))
295            return -EFAULT;
296
297    return (err==0) ? 0:-EINVAL;
298}
299
300
301/*
302 * Print the information for /proc/toshiba
303 */
304#ifdef CONFIG_PROC_FS
305static int proc_toshiba_show(struct seq_file *m, void *v)
306{
307    int key;
308
309    key = tosh_fn_status();
310
311    /* Arguments
312         0) Linux driver version (this will change if format changes)
313         1) Machine ID
314         2) SCI version
315         3) BIOS version (major, minor)
316         4) BIOS date (in SCI date format)
317         5) Fn Key status
318    */
319    seq_printf(m, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n",
320        tosh_id,
321        (tosh_sci & 0xff00)>>8,
322        tosh_sci & 0xff,
323        (tosh_bios & 0xff00)>>8,
324        tosh_bios & 0xff,
325        tosh_date,
326        key);
327    return 0;
328}
329
330static int proc_toshiba_open(struct inode *inode, struct file *file)
331{
332    return single_open(file, proc_toshiba_show, NULL);
333}
334
335static const struct file_operations proc_toshiba_fops = {
336    .owner = THIS_MODULE,
337    .open = proc_toshiba_open,
338    .read = seq_read,
339    .llseek = seq_lseek,
340    .release = single_release,
341};
342#endif
343
344
345/*
346 * Determine which port to use for the Fn key status
347 */
348static void tosh_set_fn_port(void)
349{
350    switch (tosh_id) {
351        case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10:
352        case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b:
353        case 0xfc5a:
354            tosh_fn = 0x62;
355            break;
356        case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0:
357        case 0xfce2:
358            tosh_fn = 0x68;
359            break;
360        default:
361            tosh_fn = 0x00;
362            break;
363    }
364
365    return;
366}
367
368
369/*
370 * Get the machine identification number of the current model
371 */
372static int tosh_get_machine_id(void __iomem *bios)
373{
374    int id;
375    SMMRegisters regs;
376    unsigned short bx,cx;
377    unsigned long address;
378
379    id = (0x100*(int) readb(bios+0xfffe))+((int) readb(bios+0xfffa));
380
381    /* do we have a SCTTable machine identication number on our hands */
382
383    if (id==0xfc2f) {
384
385        /* start by getting a pointer into the BIOS */
386
387        regs.eax = 0xc000;
388        regs.ebx = 0x0000;
389        regs.ecx = 0x0000;
390        tosh_smm(&regs);
391        bx = (unsigned short) (regs.ebx & 0xffff);
392
393        /* At this point in the Toshiba routines under MS Windows
394           the bx register holds 0xe6f5. However my code is producing
395           a different value! For the time being I will just fudge the
396           value. This has been verified on a Satellite Pro 430CDT,
397           Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */
398#if TOSH_DEBUG
399        printk("toshiba: debugging ID ebx=0x%04x\n", regs.ebx);
400#endif
401        bx = 0xe6f5;
402
403        /* now twiddle with our pointer a bit */
404
405        address = bx;
406        cx = readw(bios + address);
407        address = 9+bx+cx;
408        cx = readw(bios + address);
409        address = 0xa+cx;
410        cx = readw(bios + address);
411
412        /* now construct our machine identification number */
413
414        id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8);
415    }
416
417    return id;
418}
419
420
421/*
422 * Probe for the presence of a Toshiba laptop
423 *
424 * returns and non-zero if unable to detect the presence of a Toshiba
425 * laptop, otherwise zero and determines the Machine ID, BIOS version and
426 * date, and SCI version.
427 */
428static int tosh_probe(void)
429{
430    int i,major,minor,day,year,month,flag;
431    unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 };
432    SMMRegisters regs;
433    void __iomem *bios = ioremap_cache(0xf0000, 0x10000);
434
435    if (!bios)
436        return -ENOMEM;
437
438    /* extra sanity check for the string "TOSHIBA" in the BIOS because
439       some machines that are not Toshiba's pass the next test */
440
441    for (i=0;i<7;i++) {
442        if (readb(bios+0xe010+i)!=signature[i]) {
443            printk("toshiba: not a supported Toshiba laptop\n");
444            iounmap(bios);
445            return -ENODEV;
446        }
447    }
448
449    /* call the Toshiba SCI support check routine */
450
451    regs.eax = 0xf0f0;
452    regs.ebx = 0x0000;
453    regs.ecx = 0x0000;
454    flag = tosh_smm(&regs);
455
456    /* if this is not a Toshiba laptop carry flag is set and ah=0x86 */
457
458    if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) {
459        printk("toshiba: not a supported Toshiba laptop\n");
460        iounmap(bios);
461        return -ENODEV;
462    }
463
464    /* if we get this far then we are running on a Toshiba (probably)! */
465
466    tosh_sci = regs.edx & 0xffff;
467
468    /* next get the machine ID of the current laptop */
469
470    tosh_id = tosh_get_machine_id(bios);
471
472    /* get the BIOS version */
473
474    major = readb(bios+0xe009)-'0';
475    minor = ((readb(bios+0xe00b)-'0')*10)+(readb(bios+0xe00c)-'0');
476    tosh_bios = (major*0x100)+minor;
477
478    /* get the BIOS date */
479
480    day = ((readb(bios+0xfff5)-'0')*10)+(readb(bios+0xfff6)-'0');
481    month = ((readb(bios+0xfff8)-'0')*10)+(readb(bios+0xfff9)-'0');
482    year = ((readb(bios+0xfffb)-'0')*10)+(readb(bios+0xfffc)-'0');
483    tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6)
484        | ((day & 0x1f)<<1);
485
486
487    /* in theory we should check the ports we are going to use for the
488       fn key detection (and the fan on the Portage 610/Tecra700), and
489       then request them to stop other drivers using them. However as
490       the keyboard driver grabs 0x60-0x6f and the pic driver grabs
491       0xa0-0xbf we can't. We just have to live dangerously and use the
492       ports anyway, oh boy! */
493
494    /* do we need to emulate the fan? */
495
496    if ((tosh_id==0xfccb) || (tosh_id==0xfccc))
497        tosh_fan = 1;
498
499    iounmap(bios);
500
501    return 0;
502}
503
504static int __init toshiba_init(void)
505{
506    int retval;
507    /* are we running on a Toshiba laptop */
508
509    if (tosh_probe())
510        return -ENODEV;
511
512    printk(KERN_INFO "Toshiba System Management Mode driver v" TOSH_VERSION "\n");
513
514    /* set the port to use for Fn status if not specified as a parameter */
515    if (tosh_fn==0x00)
516        tosh_set_fn_port();
517
518    /* register the device file */
519    retval = misc_register(&tosh_device);
520    if (retval < 0)
521        return retval;
522
523#ifdef CONFIG_PROC_FS
524    {
525        struct proc_dir_entry *pde;
526
527        pde = proc_create("toshiba", 0, NULL, &proc_toshiba_fops);
528        if (!pde) {
529            misc_deregister(&tosh_device);
530            return -ENOMEM;
531        }
532    }
533#endif
534
535    return 0;
536}
537
538static void __exit toshiba_exit(void)
539{
540    remove_proc_entry("toshiba", NULL);
541    misc_deregister(&tosh_device);
542}
543
544module_init(toshiba_init);
545module_exit(toshiba_exit);
546
547

Archive Download this file



interactive