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