18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (c) 1996-2001 Jonathan A. Buzzard (jonathan@buzzard.org.uk) 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Valuable assistance and patches from: 78c2ecf20Sopenharmony_ci * Tom May <tom@you-bastards.com> 88c2ecf20Sopenharmony_ci * Rob Napier <rnapier@employees.org> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Fn status port numbers for machine ID's courtesy of 118c2ecf20Sopenharmony_ci * 0xfc02: Scott Eisert <scott.e@sky-eye.com> 128c2ecf20Sopenharmony_ci * 0xfc04: Steve VanDevender <stevev@efn.org> 138c2ecf20Sopenharmony_ci * 0xfc08: Garth Berry <garth@itsbruce.net> 148c2ecf20Sopenharmony_ci * 0xfc0a: Egbert Eich <eich@xfree86.org> 158c2ecf20Sopenharmony_ci * 0xfc10: Andrew Lofthouse <Andrew.Lofthouse@robins.af.mil> 168c2ecf20Sopenharmony_ci * 0xfc11: Spencer Olson <solson@novell.com> 178c2ecf20Sopenharmony_ci * 0xfc13: Claudius Frankewitz <kryp@gmx.de> 188c2ecf20Sopenharmony_ci * 0xfc15: Tom May <tom@you-bastards.com> 198c2ecf20Sopenharmony_ci * 0xfc17: Dave Konrad <konrad@xenia.it> 208c2ecf20Sopenharmony_ci * 0xfc1a: George Betzos <betzos@engr.colostate.edu> 218c2ecf20Sopenharmony_ci * 0xfc1b: Munemasa Wada <munemasa@jnovel.co.jp> 228c2ecf20Sopenharmony_ci * 0xfc1d: Arthur Liu <armie@slap.mine.nu> 238c2ecf20Sopenharmony_ci * 0xfc5a: Jacques L'helgoualc'h <lhh@free.fr> 248c2ecf20Sopenharmony_ci * 0xfcd1: Mr. Dave Konrad <konrad@xenia.it> 258c2ecf20Sopenharmony_ci * 268c2ecf20Sopenharmony_ci * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 278c2ecf20Sopenharmony_ci * 288c2ecf20Sopenharmony_ci * This code is covered by the GNU GPL and you are free to make any 298c2ecf20Sopenharmony_ci * changes you wish to it under the terms of the license. However the 308c2ecf20Sopenharmony_ci * code has the potential to render your computer and/or someone else's 318c2ecf20Sopenharmony_ci * unusable. Please proceed with care when modifying the code. 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * Note: Unfortunately the laptop hardware can close the System Configuration 348c2ecf20Sopenharmony_ci * Interface on it's own accord. It is therefore necessary for *all* 358c2ecf20Sopenharmony_ci * programs using this driver to be aware that *any* SCI call can fail at 368c2ecf20Sopenharmony_ci * *any* time. It is up to any program to be aware of this eventuality 378c2ecf20Sopenharmony_ci * and take appropriate steps. 388c2ecf20Sopenharmony_ci * 398c2ecf20Sopenharmony_ci * The information used to write this driver has been obtained by reverse 408c2ecf20Sopenharmony_ci * engineering the software supplied by Toshiba for their portable computers in 418c2ecf20Sopenharmony_ci * strict accordance with the European Council Directive 92/250/EEC on the legal 428c2ecf20Sopenharmony_ci * protection of computer programs, and it's implementation into English Law by 438c2ecf20Sopenharmony_ci * the Copyright (Computer Programs) Regulations 1992 (S.I. 1992 No.3233). 448c2ecf20Sopenharmony_ci */ 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define TOSH_VERSION "1.11 26/9/2001" 478c2ecf20Sopenharmony_ci#define TOSH_DEBUG 0 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#include <linux/module.h> 508c2ecf20Sopenharmony_ci#include <linux/kernel.h> 518c2ecf20Sopenharmony_ci#include <linux/types.h> 528c2ecf20Sopenharmony_ci#include <linux/fcntl.h> 538c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 548c2ecf20Sopenharmony_ci#include <linux/ioport.h> 558c2ecf20Sopenharmony_ci#include <asm/io.h> 568c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 578c2ecf20Sopenharmony_ci#include <linux/init.h> 588c2ecf20Sopenharmony_ci#include <linux/stat.h> 598c2ecf20Sopenharmony_ci#include <linux/proc_fs.h> 608c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 618c2ecf20Sopenharmony_ci#include <linux/mutex.h> 628c2ecf20Sopenharmony_ci#include <linux/toshiba.h> 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 658c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>"); 668c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Toshiba laptop SMM driver"); 678c2ecf20Sopenharmony_ciMODULE_SUPPORTED_DEVICE("toshiba"); 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(tosh_mutex); 708c2ecf20Sopenharmony_cistatic int tosh_fn; 718c2ecf20Sopenharmony_cimodule_param_named(fn, tosh_fn, int, 0); 728c2ecf20Sopenharmony_ciMODULE_PARM_DESC(fn, "User specified Fn key detection port"); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic int tosh_id; 758c2ecf20Sopenharmony_cistatic int tosh_bios; 768c2ecf20Sopenharmony_cistatic int tosh_date; 778c2ecf20Sopenharmony_cistatic int tosh_sci; 788c2ecf20Sopenharmony_cistatic int tosh_fan; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic long tosh_ioctl(struct file *, unsigned int, 818c2ecf20Sopenharmony_ci unsigned long); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic const struct file_operations tosh_fops = { 858c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 868c2ecf20Sopenharmony_ci .unlocked_ioctl = tosh_ioctl, 878c2ecf20Sopenharmony_ci .llseek = noop_llseek, 888c2ecf20Sopenharmony_ci}; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic struct miscdevice tosh_device = { 918c2ecf20Sopenharmony_ci TOSH_MINOR_DEV, 928c2ecf20Sopenharmony_ci "toshiba", 938c2ecf20Sopenharmony_ci &tosh_fops 948c2ecf20Sopenharmony_ci}; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci/* 978c2ecf20Sopenharmony_ci * Read the Fn key status 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci#ifdef CONFIG_PROC_FS 1008c2ecf20Sopenharmony_cistatic int tosh_fn_status(void) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci unsigned char scan; 1038c2ecf20Sopenharmony_ci unsigned long flags; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (tosh_fn!=0) { 1068c2ecf20Sopenharmony_ci scan = inb(tosh_fn); 1078c2ecf20Sopenharmony_ci } else { 1088c2ecf20Sopenharmony_ci local_irq_save(flags); 1098c2ecf20Sopenharmony_ci outb(0x8e, 0xe4); 1108c2ecf20Sopenharmony_ci scan = inb(0xe5); 1118c2ecf20Sopenharmony_ci local_irq_restore(flags); 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return (int) scan; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci#endif 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci/* 1208c2ecf20Sopenharmony_ci * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_cistatic int tosh_emulate_fan(SMMRegisters *regs) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci unsigned long eax,ecx,flags; 1258c2ecf20Sopenharmony_ci unsigned char al; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci eax = regs->eax & 0xff00; 1288c2ecf20Sopenharmony_ci ecx = regs->ecx & 0xffff; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* Portage 610CT */ 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (tosh_id==0xfccb) { 1338c2ecf20Sopenharmony_ci if (eax==0xfe00) { 1348c2ecf20Sopenharmony_ci /* fan status */ 1358c2ecf20Sopenharmony_ci local_irq_save(flags); 1368c2ecf20Sopenharmony_ci outb(0xbe, 0xe4); 1378c2ecf20Sopenharmony_ci al = inb(0xe5); 1388c2ecf20Sopenharmony_ci local_irq_restore(flags); 1398c2ecf20Sopenharmony_ci regs->eax = 0x00; 1408c2ecf20Sopenharmony_ci regs->ecx = (unsigned int) (al & 0x01); 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci if ((eax==0xff00) && (ecx==0x0000)) { 1438c2ecf20Sopenharmony_ci /* fan off */ 1448c2ecf20Sopenharmony_ci local_irq_save(flags); 1458c2ecf20Sopenharmony_ci outb(0xbe, 0xe4); 1468c2ecf20Sopenharmony_ci al = inb(0xe5); 1478c2ecf20Sopenharmony_ci outb(0xbe, 0xe4); 1488c2ecf20Sopenharmony_ci outb (al | 0x01, 0xe5); 1498c2ecf20Sopenharmony_ci local_irq_restore(flags); 1508c2ecf20Sopenharmony_ci regs->eax = 0x00; 1518c2ecf20Sopenharmony_ci regs->ecx = 0x00; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci if ((eax==0xff00) && (ecx==0x0001)) { 1548c2ecf20Sopenharmony_ci /* fan on */ 1558c2ecf20Sopenharmony_ci local_irq_save(flags); 1568c2ecf20Sopenharmony_ci outb(0xbe, 0xe4); 1578c2ecf20Sopenharmony_ci al = inb(0xe5); 1588c2ecf20Sopenharmony_ci outb(0xbe, 0xe4); 1598c2ecf20Sopenharmony_ci outb(al & 0xfe, 0xe5); 1608c2ecf20Sopenharmony_ci local_irq_restore(flags); 1618c2ecf20Sopenharmony_ci regs->eax = 0x00; 1628c2ecf20Sopenharmony_ci regs->ecx = 0x01; 1638c2ecf20Sopenharmony_ci } 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* Tecra 700CS/CDT */ 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (tosh_id==0xfccc) { 1698c2ecf20Sopenharmony_ci if (eax==0xfe00) { 1708c2ecf20Sopenharmony_ci /* fan status */ 1718c2ecf20Sopenharmony_ci local_irq_save(flags); 1728c2ecf20Sopenharmony_ci outb(0xe0, 0xe4); 1738c2ecf20Sopenharmony_ci al = inb(0xe5); 1748c2ecf20Sopenharmony_ci local_irq_restore(flags); 1758c2ecf20Sopenharmony_ci regs->eax = 0x00; 1768c2ecf20Sopenharmony_ci regs->ecx = al & 0x01; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci if ((eax==0xff00) && (ecx==0x0000)) { 1798c2ecf20Sopenharmony_ci /* fan off */ 1808c2ecf20Sopenharmony_ci local_irq_save(flags); 1818c2ecf20Sopenharmony_ci outb(0xe0, 0xe4); 1828c2ecf20Sopenharmony_ci al = inb(0xe5); 1838c2ecf20Sopenharmony_ci outw(0xe0 | ((al & 0xfe) << 8), 0xe4); 1848c2ecf20Sopenharmony_ci local_irq_restore(flags); 1858c2ecf20Sopenharmony_ci regs->eax = 0x00; 1868c2ecf20Sopenharmony_ci regs->ecx = 0x00; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci if ((eax==0xff00) && (ecx==0x0001)) { 1898c2ecf20Sopenharmony_ci /* fan on */ 1908c2ecf20Sopenharmony_ci local_irq_save(flags); 1918c2ecf20Sopenharmony_ci outb(0xe0, 0xe4); 1928c2ecf20Sopenharmony_ci al = inb(0xe5); 1938c2ecf20Sopenharmony_ci outw(0xe0 | ((al | 0x01) << 8), 0xe4); 1948c2ecf20Sopenharmony_ci local_irq_restore(flags); 1958c2ecf20Sopenharmony_ci regs->eax = 0x00; 1968c2ecf20Sopenharmony_ci regs->ecx = 0x01; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return 0; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci/* 2058c2ecf20Sopenharmony_ci * Put the laptop into System Management Mode 2068c2ecf20Sopenharmony_ci */ 2078c2ecf20Sopenharmony_ciint tosh_smm(SMMRegisters *regs) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci int eax; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci asm ("# load the values into the registers\n\t" \ 2128c2ecf20Sopenharmony_ci "pushl %%eax\n\t" \ 2138c2ecf20Sopenharmony_ci "movl 0(%%eax),%%edx\n\t" \ 2148c2ecf20Sopenharmony_ci "push %%edx\n\t" \ 2158c2ecf20Sopenharmony_ci "movl 4(%%eax),%%ebx\n\t" \ 2168c2ecf20Sopenharmony_ci "movl 8(%%eax),%%ecx\n\t" \ 2178c2ecf20Sopenharmony_ci "movl 12(%%eax),%%edx\n\t" \ 2188c2ecf20Sopenharmony_ci "movl 16(%%eax),%%esi\n\t" \ 2198c2ecf20Sopenharmony_ci "movl 20(%%eax),%%edi\n\t" \ 2208c2ecf20Sopenharmony_ci "popl %%eax\n\t" \ 2218c2ecf20Sopenharmony_ci "# call the System Management mode\n\t" \ 2228c2ecf20Sopenharmony_ci "inb $0xb2,%%al\n\t" 2238c2ecf20Sopenharmony_ci "# fill out the memory with the values in the registers\n\t" \ 2248c2ecf20Sopenharmony_ci "xchgl %%eax,(%%esp)\n\t" 2258c2ecf20Sopenharmony_ci "movl %%ebx,4(%%eax)\n\t" \ 2268c2ecf20Sopenharmony_ci "movl %%ecx,8(%%eax)\n\t" \ 2278c2ecf20Sopenharmony_ci "movl %%edx,12(%%eax)\n\t" \ 2288c2ecf20Sopenharmony_ci "movl %%esi,16(%%eax)\n\t" \ 2298c2ecf20Sopenharmony_ci "movl %%edi,20(%%eax)\n\t" \ 2308c2ecf20Sopenharmony_ci "popl %%edx\n\t" \ 2318c2ecf20Sopenharmony_ci "movl %%edx,0(%%eax)\n\t" \ 2328c2ecf20Sopenharmony_ci "# setup the return value to the carry flag\n\t" \ 2338c2ecf20Sopenharmony_ci "lahf\n\t" \ 2348c2ecf20Sopenharmony_ci "shrl $8,%%eax\n\t" \ 2358c2ecf20Sopenharmony_ci "andl $1,%%eax\n" \ 2368c2ecf20Sopenharmony_ci : "=a" (eax) 2378c2ecf20Sopenharmony_ci : "a" (regs) 2388c2ecf20Sopenharmony_ci : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci return eax; 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ciEXPORT_SYMBOL(tosh_smm); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_cistatic long tosh_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) 2468c2ecf20Sopenharmony_ci{ 2478c2ecf20Sopenharmony_ci SMMRegisters regs; 2488c2ecf20Sopenharmony_ci SMMRegisters __user *argp = (SMMRegisters __user *)arg; 2498c2ecf20Sopenharmony_ci unsigned short ax,bx; 2508c2ecf20Sopenharmony_ci int err; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (!argp) 2538c2ecf20Sopenharmony_ci return -EINVAL; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci if (copy_from_user(®s, argp, sizeof(SMMRegisters))) 2568c2ecf20Sopenharmony_ci return -EFAULT; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci switch (cmd) { 2598c2ecf20Sopenharmony_ci case TOSH_SMM: 2608c2ecf20Sopenharmony_ci ax = regs.eax & 0xff00; 2618c2ecf20Sopenharmony_ci bx = regs.ebx & 0xffff; 2628c2ecf20Sopenharmony_ci /* block HCI calls to read/write memory & PCI devices */ 2638c2ecf20Sopenharmony_ci if (((ax==0xff00) || (ax==0xfe00)) && (bx>0x0069)) 2648c2ecf20Sopenharmony_ci return -EINVAL; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci /* do we need to emulate the fan ? */ 2678c2ecf20Sopenharmony_ci mutex_lock(&tosh_mutex); 2688c2ecf20Sopenharmony_ci if (tosh_fan==1) { 2698c2ecf20Sopenharmony_ci if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) { 2708c2ecf20Sopenharmony_ci err = tosh_emulate_fan(®s); 2718c2ecf20Sopenharmony_ci mutex_unlock(&tosh_mutex); 2728c2ecf20Sopenharmony_ci break; 2738c2ecf20Sopenharmony_ci } 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci err = tosh_smm(®s); 2768c2ecf20Sopenharmony_ci mutex_unlock(&tosh_mutex); 2778c2ecf20Sopenharmony_ci break; 2788c2ecf20Sopenharmony_ci default: 2798c2ecf20Sopenharmony_ci return -EINVAL; 2808c2ecf20Sopenharmony_ci } 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci if (copy_to_user(argp, ®s, sizeof(SMMRegisters))) 2838c2ecf20Sopenharmony_ci return -EFAULT; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci return (err==0) ? 0:-EINVAL; 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci/* 2908c2ecf20Sopenharmony_ci * Print the information for /proc/toshiba 2918c2ecf20Sopenharmony_ci */ 2928c2ecf20Sopenharmony_ci#ifdef CONFIG_PROC_FS 2938c2ecf20Sopenharmony_cistatic int proc_toshiba_show(struct seq_file *m, void *v) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci int key; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci key = tosh_fn_status(); 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci /* Arguments 3008c2ecf20Sopenharmony_ci 0) Linux driver version (this will change if format changes) 3018c2ecf20Sopenharmony_ci 1) Machine ID 3028c2ecf20Sopenharmony_ci 2) SCI version 3038c2ecf20Sopenharmony_ci 3) BIOS version (major, minor) 3048c2ecf20Sopenharmony_ci 4) BIOS date (in SCI date format) 3058c2ecf20Sopenharmony_ci 5) Fn Key status 3068c2ecf20Sopenharmony_ci */ 3078c2ecf20Sopenharmony_ci seq_printf(m, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n", 3088c2ecf20Sopenharmony_ci tosh_id, 3098c2ecf20Sopenharmony_ci (tosh_sci & 0xff00)>>8, 3108c2ecf20Sopenharmony_ci tosh_sci & 0xff, 3118c2ecf20Sopenharmony_ci (tosh_bios & 0xff00)>>8, 3128c2ecf20Sopenharmony_ci tosh_bios & 0xff, 3138c2ecf20Sopenharmony_ci tosh_date, 3148c2ecf20Sopenharmony_ci key); 3158c2ecf20Sopenharmony_ci return 0; 3168c2ecf20Sopenharmony_ci} 3178c2ecf20Sopenharmony_ci#endif 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci/* 3218c2ecf20Sopenharmony_ci * Determine which port to use for the Fn key status 3228c2ecf20Sopenharmony_ci */ 3238c2ecf20Sopenharmony_cistatic void tosh_set_fn_port(void) 3248c2ecf20Sopenharmony_ci{ 3258c2ecf20Sopenharmony_ci switch (tosh_id) { 3268c2ecf20Sopenharmony_ci case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10: 3278c2ecf20Sopenharmony_ci case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b: 3288c2ecf20Sopenharmony_ci case 0xfc5a: 3298c2ecf20Sopenharmony_ci tosh_fn = 0x62; 3308c2ecf20Sopenharmony_ci break; 3318c2ecf20Sopenharmony_ci case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0: 3328c2ecf20Sopenharmony_ci case 0xfce2: 3338c2ecf20Sopenharmony_ci tosh_fn = 0x68; 3348c2ecf20Sopenharmony_ci break; 3358c2ecf20Sopenharmony_ci default: 3368c2ecf20Sopenharmony_ci tosh_fn = 0x00; 3378c2ecf20Sopenharmony_ci break; 3388c2ecf20Sopenharmony_ci } 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci return; 3418c2ecf20Sopenharmony_ci} 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci/* 3458c2ecf20Sopenharmony_ci * Get the machine identification number of the current model 3468c2ecf20Sopenharmony_ci */ 3478c2ecf20Sopenharmony_cistatic int tosh_get_machine_id(void __iomem *bios) 3488c2ecf20Sopenharmony_ci{ 3498c2ecf20Sopenharmony_ci int id; 3508c2ecf20Sopenharmony_ci SMMRegisters regs; 3518c2ecf20Sopenharmony_ci unsigned short bx,cx; 3528c2ecf20Sopenharmony_ci unsigned long address; 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci id = (0x100*(int) readb(bios+0xfffe))+((int) readb(bios+0xfffa)); 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci /* do we have a SCTTable machine identication number on our hands */ 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci if (id==0xfc2f) { 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci /* start by getting a pointer into the BIOS */ 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci regs.eax = 0xc000; 3638c2ecf20Sopenharmony_ci regs.ebx = 0x0000; 3648c2ecf20Sopenharmony_ci regs.ecx = 0x0000; 3658c2ecf20Sopenharmony_ci tosh_smm(®s); 3668c2ecf20Sopenharmony_ci bx = (unsigned short) (regs.ebx & 0xffff); 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci /* At this point in the Toshiba routines under MS Windows 3698c2ecf20Sopenharmony_ci the bx register holds 0xe6f5. However my code is producing 3708c2ecf20Sopenharmony_ci a different value! For the time being I will just fudge the 3718c2ecf20Sopenharmony_ci value. This has been verified on a Satellite Pro 430CDT, 3728c2ecf20Sopenharmony_ci Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */ 3738c2ecf20Sopenharmony_ci#if TOSH_DEBUG 3748c2ecf20Sopenharmony_ci pr_debug("toshiba: debugging ID ebx=0x%04x\n", regs.ebx); 3758c2ecf20Sopenharmony_ci#endif 3768c2ecf20Sopenharmony_ci bx = 0xe6f5; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci /* now twiddle with our pointer a bit */ 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci address = bx; 3818c2ecf20Sopenharmony_ci cx = readw(bios + address); 3828c2ecf20Sopenharmony_ci address = 9+bx+cx; 3838c2ecf20Sopenharmony_ci cx = readw(bios + address); 3848c2ecf20Sopenharmony_ci address = 0xa+cx; 3858c2ecf20Sopenharmony_ci cx = readw(bios + address); 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci /* now construct our machine identification number */ 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8); 3908c2ecf20Sopenharmony_ci } 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci return id; 3938c2ecf20Sopenharmony_ci} 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci/* 3978c2ecf20Sopenharmony_ci * Probe for the presence of a Toshiba laptop 3988c2ecf20Sopenharmony_ci * 3998c2ecf20Sopenharmony_ci * returns and non-zero if unable to detect the presence of a Toshiba 4008c2ecf20Sopenharmony_ci * laptop, otherwise zero and determines the Machine ID, BIOS version and 4018c2ecf20Sopenharmony_ci * date, and SCI version. 4028c2ecf20Sopenharmony_ci */ 4038c2ecf20Sopenharmony_cistatic int tosh_probe(void) 4048c2ecf20Sopenharmony_ci{ 4058c2ecf20Sopenharmony_ci int i,major,minor,day,year,month,flag; 4068c2ecf20Sopenharmony_ci unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 }; 4078c2ecf20Sopenharmony_ci SMMRegisters regs; 4088c2ecf20Sopenharmony_ci void __iomem *bios = ioremap(0xf0000, 0x10000); 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci if (!bios) 4118c2ecf20Sopenharmony_ci return -ENOMEM; 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci /* extra sanity check for the string "TOSHIBA" in the BIOS because 4148c2ecf20Sopenharmony_ci some machines that are not Toshiba's pass the next test */ 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci for (i=0;i<7;i++) { 4178c2ecf20Sopenharmony_ci if (readb(bios+0xe010+i)!=signature[i]) { 4188c2ecf20Sopenharmony_ci pr_err("toshiba: not a supported Toshiba laptop\n"); 4198c2ecf20Sopenharmony_ci iounmap(bios); 4208c2ecf20Sopenharmony_ci return -ENODEV; 4218c2ecf20Sopenharmony_ci } 4228c2ecf20Sopenharmony_ci } 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci /* call the Toshiba SCI support check routine */ 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci regs.eax = 0xf0f0; 4278c2ecf20Sopenharmony_ci regs.ebx = 0x0000; 4288c2ecf20Sopenharmony_ci regs.ecx = 0x0000; 4298c2ecf20Sopenharmony_ci flag = tosh_smm(®s); 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci /* if this is not a Toshiba laptop carry flag is set and ah=0x86 */ 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) { 4348c2ecf20Sopenharmony_ci pr_err("toshiba: not a supported Toshiba laptop\n"); 4358c2ecf20Sopenharmony_ci iounmap(bios); 4368c2ecf20Sopenharmony_ci return -ENODEV; 4378c2ecf20Sopenharmony_ci } 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci /* if we get this far then we are running on a Toshiba (probably)! */ 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci tosh_sci = regs.edx & 0xffff; 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci /* next get the machine ID of the current laptop */ 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci tosh_id = tosh_get_machine_id(bios); 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci /* get the BIOS version */ 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci major = readb(bios+0xe009)-'0'; 4508c2ecf20Sopenharmony_ci minor = ((readb(bios+0xe00b)-'0')*10)+(readb(bios+0xe00c)-'0'); 4518c2ecf20Sopenharmony_ci tosh_bios = (major*0x100)+minor; 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci /* get the BIOS date */ 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci day = ((readb(bios+0xfff5)-'0')*10)+(readb(bios+0xfff6)-'0'); 4568c2ecf20Sopenharmony_ci month = ((readb(bios+0xfff8)-'0')*10)+(readb(bios+0xfff9)-'0'); 4578c2ecf20Sopenharmony_ci year = ((readb(bios+0xfffb)-'0')*10)+(readb(bios+0xfffc)-'0'); 4588c2ecf20Sopenharmony_ci tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6) 4598c2ecf20Sopenharmony_ci | ((day & 0x1f)<<1); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci /* in theory we should check the ports we are going to use for the 4638c2ecf20Sopenharmony_ci fn key detection (and the fan on the Portage 610/Tecra700), and 4648c2ecf20Sopenharmony_ci then request them to stop other drivers using them. However as 4658c2ecf20Sopenharmony_ci the keyboard driver grabs 0x60-0x6f and the pic driver grabs 4668c2ecf20Sopenharmony_ci 0xa0-0xbf we can't. We just have to live dangerously and use the 4678c2ecf20Sopenharmony_ci ports anyway, oh boy! */ 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci /* do we need to emulate the fan? */ 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci if ((tosh_id==0xfccb) || (tosh_id==0xfccc)) 4728c2ecf20Sopenharmony_ci tosh_fan = 1; 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci iounmap(bios); 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_ci return 0; 4778c2ecf20Sopenharmony_ci} 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_cistatic int __init toshiba_init(void) 4808c2ecf20Sopenharmony_ci{ 4818c2ecf20Sopenharmony_ci int retval; 4828c2ecf20Sopenharmony_ci /* are we running on a Toshiba laptop */ 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci if (tosh_probe()) 4858c2ecf20Sopenharmony_ci return -ENODEV; 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci pr_info("Toshiba System Management Mode driver v" TOSH_VERSION "\n"); 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci /* set the port to use for Fn status if not specified as a parameter */ 4908c2ecf20Sopenharmony_ci if (tosh_fn==0x00) 4918c2ecf20Sopenharmony_ci tosh_set_fn_port(); 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci /* register the device file */ 4948c2ecf20Sopenharmony_ci retval = misc_register(&tosh_device); 4958c2ecf20Sopenharmony_ci if (retval < 0) 4968c2ecf20Sopenharmony_ci return retval; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci#ifdef CONFIG_PROC_FS 4998c2ecf20Sopenharmony_ci { 5008c2ecf20Sopenharmony_ci struct proc_dir_entry *pde; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci pde = proc_create_single("toshiba", 0, NULL, proc_toshiba_show); 5038c2ecf20Sopenharmony_ci if (!pde) { 5048c2ecf20Sopenharmony_ci misc_deregister(&tosh_device); 5058c2ecf20Sopenharmony_ci return -ENOMEM; 5068c2ecf20Sopenharmony_ci } 5078c2ecf20Sopenharmony_ci } 5088c2ecf20Sopenharmony_ci#endif 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci return 0; 5118c2ecf20Sopenharmony_ci} 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_cistatic void __exit toshiba_exit(void) 5148c2ecf20Sopenharmony_ci{ 5158c2ecf20Sopenharmony_ci remove_proc_entry("toshiba", NULL); 5168c2ecf20Sopenharmony_ci misc_deregister(&tosh_device); 5178c2ecf20Sopenharmony_ci} 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_cimodule_init(toshiba_init); 5208c2ecf20Sopenharmony_cimodule_exit(toshiba_exit); 5218c2ecf20Sopenharmony_ci 522