2005-04-17 02:20:36 +04:00
/* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops
*
* Copyright ( c ) 1996 - 2001 Jonathan A . Buzzard ( jonathan @ buzzard . org . uk )
*
* Valuable assistance and patches from :
* Tom May < tom @ you - bastards . com >
* Rob Napier < rnapier @ employees . org >
*
* Fn status port numbers for machine ID ' s courtesy of
* 0xfc02 : Scott Eisert < scott . e @ sky - eye . com >
* 0xfc04 : Steve VanDevender < stevev @ efn . org >
* 0xfc08 : Garth Berry < garth @ itsbruce . net >
* 0xfc0a : Egbert Eich < eich @ xfree86 . org >
* 0xfc10 : Andrew Lofthouse < Andrew . Lofthouse @ robins . af . mil >
* 0xfc11 : Spencer Olson < solson @ novell . com >
* 0xfc13 : Claudius Frankewitz < kryp @ gmx . de >
* 0xfc15 : Tom May < tom @ you - bastards . com >
* 0xfc17 : Dave Konrad < konrad @ xenia . it >
* 0xfc1a : George Betzos < betzos @ engr . colostate . edu >
* 0xfc1b : Munemasa Wada < munemasa @ jnovel . co . jp >
* 0xfc1d : Arthur Liu < armie @ slap . mine . nu >
* 0xfc5a : Jacques L ' helgoualc ' h < lhh @ free . fr >
* 0xfcd1 : Mr . Dave Konrad < konrad @ xenia . it >
*
* WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
*
* This code is covered by the GNU GPL and you are free to make any
* changes you wish to it under the terms of the license . However the
* code has the potential to render your computer and / or someone else ' s
* unusable . Please proceed with care when modifying the code .
*
* Note : Unfortunately the laptop hardware can close the System Configuration
* Interface on it ' s own accord . It is therefore necessary for * all *
* programs using this driver to be aware that * any * SCI call can fail at
* * any * time . It is up to any program to be aware of this eventuality
* and take appropriate steps .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 , or ( at your option ) any
* later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* The information used to write this driver has been obtained by reverse
* engineering the software supplied by Toshiba for their portable computers in
* strict accordance with the European Council Directive 92 / 250 / EEC on the legal
* protection of computer programs , and it ' s implementation into English Law by
* the Copyright ( Computer Programs ) Regulations 1992 ( S . I . 1992 No .3233 ) .
*
*/
# define TOSH_VERSION "1.11 26 / 9 / 2001"
# define TOSH_DEBUG 0
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/types.h>
# include <linux/fcntl.h>
# include <linux/miscdevice.h>
# include <linux/ioport.h>
# include <asm/io.h>
# include <asm/uaccess.h>
# include <linux/init.h>
# include <linux/stat.h>
# include <linux/proc_fs.h>
# include <linux/toshiba.h>
# define TOSH_MINOR_DEV 181
2005-06-26 01:54:22 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Jonathan Buzzard <jonathan@buzzard.org.uk> " ) ;
MODULE_DESCRIPTION ( " Toshiba laptop SMM driver " ) ;
MODULE_SUPPORTED_DEVICE ( " toshiba " ) ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:22 +04:00
static int tosh_fn ;
module_param_named ( fn , tosh_fn , int , 0 ) ;
MODULE_PARM_DESC ( fn , " User specified Fn key detection port " ) ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:22 +04:00
static int tosh_id ;
static int tosh_bios ;
static int tosh_date ;
static int tosh_sci ;
static int tosh_fan ;
2005-04-17 02:20:36 +04:00
static int tosh_ioctl ( struct inode * , struct file * , unsigned int ,
unsigned long ) ;
static struct file_operations tosh_fops = {
. owner = THIS_MODULE ,
. ioctl = tosh_ioctl ,
} ;
static struct miscdevice tosh_device = {
TOSH_MINOR_DEV ,
" toshiba " ,
& tosh_fops
} ;
/*
* Read the Fn key status
*/
# ifdef CONFIG_PROC_FS
static int tosh_fn_status ( void )
{
unsigned char scan ;
unsigned long flags ;
if ( tosh_fn ! = 0 ) {
scan = inb ( tosh_fn ) ;
} else {
local_irq_save ( flags ) ;
outb ( 0x8e , 0xe4 ) ;
scan = inb ( 0xe5 ) ;
local_irq_restore ( flags ) ;
}
return ( int ) scan ;
}
# endif
/*
* For the Portage 610 CT and the Tecra 700 CS / 700 CDT emulate the HCI fan function
*/
static int tosh_emulate_fan ( SMMRegisters * regs )
{
unsigned long eax , ecx , flags ;
unsigned char al ;
eax = regs - > eax & 0xff00 ;
ecx = regs - > ecx & 0xffff ;
/* Portage 610CT */
if ( tosh_id = = 0xfccb ) {
if ( eax = = 0xfe00 ) {
/* fan status */
local_irq_save ( flags ) ;
outb ( 0xbe , 0xe4 ) ;
al = inb ( 0xe5 ) ;
local_irq_restore ( flags ) ;
regs - > eax = 0x00 ;
regs - > ecx = ( unsigned int ) ( al & 0x01 ) ;
}
if ( ( eax = = 0xff00 ) & & ( ecx = = 0x0000 ) ) {
/* fan off */
local_irq_save ( flags ) ;
outb ( 0xbe , 0xe4 ) ;
al = inb ( 0xe5 ) ;
outb ( 0xbe , 0xe4 ) ;
outb ( al | 0x01 , 0xe5 ) ;
local_irq_restore ( flags ) ;
regs - > eax = 0x00 ;
regs - > ecx = 0x00 ;
}
if ( ( eax = = 0xff00 ) & & ( ecx = = 0x0001 ) ) {
/* fan on */
local_irq_save ( flags ) ;
outb ( 0xbe , 0xe4 ) ;
al = inb ( 0xe5 ) ;
outb ( 0xbe , 0xe4 ) ;
outb ( al & 0xfe , 0xe5 ) ;
local_irq_restore ( flags ) ;
regs - > eax = 0x00 ;
regs - > ecx = 0x01 ;
}
}
/* Tecra 700CS/CDT */
if ( tosh_id = = 0xfccc ) {
if ( eax = = 0xfe00 ) {
/* fan status */
local_irq_save ( flags ) ;
outb ( 0xe0 , 0xe4 ) ;
al = inb ( 0xe5 ) ;
local_irq_restore ( flags ) ;
regs - > eax = 0x00 ;
regs - > ecx = al & 0x01 ;
}
if ( ( eax = = 0xff00 ) & & ( ecx = = 0x0000 ) ) {
/* fan off */
local_irq_save ( flags ) ;
outb ( 0xe0 , 0xe4 ) ;
al = inb ( 0xe5 ) ;
outw ( 0xe0 | ( ( al & 0xfe ) < < 8 ) , 0xe4 ) ;
local_irq_restore ( flags ) ;
regs - > eax = 0x00 ;
regs - > ecx = 0x00 ;
}
if ( ( eax = = 0xff00 ) & & ( ecx = = 0x0001 ) ) {
/* fan on */
local_irq_save ( flags ) ;
outb ( 0xe0 , 0xe4 ) ;
al = inb ( 0xe5 ) ;
outw ( 0xe0 | ( ( al | 0x01 ) < < 8 ) , 0xe4 ) ;
local_irq_restore ( flags ) ;
regs - > eax = 0x00 ;
regs - > ecx = 0x01 ;
}
}
return 0 ;
}
/*
* Put the laptop into System Management Mode
*/
int tosh_smm ( SMMRegisters * regs )
{
int eax ;
asm ( " # load the values into the registers \n \t " \
" pushl %%eax \n \t " \
" movl 0(%%eax),%%edx \n \t " \
" push %%edx \n \t " \
" movl 4(%%eax),%%ebx \n \t " \
" movl 8(%%eax),%%ecx \n \t " \
" movl 12(%%eax),%%edx \n \t " \
" movl 16(%%eax),%%esi \n \t " \
" movl 20(%%eax),%%edi \n \t " \
" popl %%eax \n \t " \
" # call the System Management mode \n \t " \
" inb $0xb2,%%al \n \t "
" # fill out the memory with the values in the registers \n \t " \
" xchgl %%eax,(%%esp) \n \t "
" movl %%ebx,4(%%eax) \n \t " \
" movl %%ecx,8(%%eax) \n \t " \
" movl %%edx,12(%%eax) \n \t " \
" movl %%esi,16(%%eax) \n \t " \
" movl %%edi,20(%%eax) \n \t " \
" popl %%edx \n \t " \
" movl %%edx,0(%%eax) \n \t " \
" # setup the return value to the carry flag \n \t " \
" lahf \n \t " \
" shrl $8,%%eax \n \t " \
" andl $1,%%eax \n " \
: " =a " ( eax )
: " a " ( regs )
: " %ebx " , " %ecx " , " %edx " , " %esi " , " %edi " , " memory " ) ;
return eax ;
}
static int tosh_ioctl ( struct inode * ip , struct file * fp , unsigned int cmd ,
unsigned long arg )
{
SMMRegisters regs ;
SMMRegisters __user * argp = ( SMMRegisters __user * ) arg ;
unsigned short ax , bx ;
int err ;
if ( ! argp )
return - EINVAL ;
if ( copy_from_user ( & regs , argp , sizeof ( SMMRegisters ) ) )
return - EFAULT ;
switch ( cmd ) {
case TOSH_SMM :
ax = regs . eax & 0xff00 ;
bx = regs . ebx & 0xffff ;
/* block HCI calls to read/write memory & PCI devices */
if ( ( ( ax = = 0xff00 ) | | ( ax = = 0xfe00 ) ) & & ( bx > 0x0069 ) )
return - EINVAL ;
/* do we need to emulate the fan ? */
if ( tosh_fan = = 1 ) {
if ( ( ( ax = = 0xf300 ) | | ( ax = = 0xf400 ) ) & & ( bx = = 0x0004 ) ) {
err = tosh_emulate_fan ( & regs ) ;
break ;
}
}
err = tosh_smm ( & regs ) ;
break ;
default :
return - EINVAL ;
}
if ( copy_to_user ( argp , & regs , sizeof ( SMMRegisters ) ) )
return - EFAULT ;
return ( err = = 0 ) ? 0 : - EINVAL ;
}
/*
* Print the information for / proc / toshiba
*/
# ifdef CONFIG_PROC_FS
static int tosh_get_info ( char * buffer , char * * start , off_t fpos , int length )
{
char * temp ;
int key ;
temp = buffer ;
key = tosh_fn_status ( ) ;
/* Arguments
0 ) Linux driver version ( this will change if format changes )
1 ) Machine ID
2 ) SCI version
3 ) BIOS version ( major , minor )
4 ) BIOS date ( in SCI date format )
5 ) Fn Key status
*/
temp + = sprintf ( temp , " 1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x \n " ,
tosh_id ,
( tosh_sci & 0xff00 ) > > 8 ,
tosh_sci & 0xff ,
( tosh_bios & 0xff00 ) > > 8 ,
tosh_bios & 0xff ,
tosh_date ,
key ) ;
return temp - buffer ;
}
# endif
/*
* Determine which port to use for the Fn key status
*/
static void tosh_set_fn_port ( void )
{
switch ( tosh_id ) {
case 0xfc02 : case 0xfc04 : case 0xfc09 : case 0xfc0a : case 0xfc10 :
case 0xfc11 : case 0xfc13 : case 0xfc15 : case 0xfc1a : case 0xfc1b :
case 0xfc5a :
tosh_fn = 0x62 ;
break ;
case 0xfc08 : case 0xfc17 : case 0xfc1d : case 0xfcd1 : case 0xfce0 :
case 0xfce2 :
tosh_fn = 0x68 ;
break ;
default :
tosh_fn = 0x00 ;
break ;
}
return ;
}
/*
* Get the machine identification number of the current model
*/
2006-03-24 14:15:36 +03:00
static int tosh_get_machine_id ( void __iomem * bios )
2005-04-17 02:20:36 +04:00
{
int id ;
SMMRegisters regs ;
unsigned short bx , cx ;
unsigned long address ;
2006-03-24 14:15:36 +03:00
id = ( 0x100 * ( int ) readb ( bios + 0xfffe ) ) + ( ( int ) readb ( bios + 0xfffa ) ) ;
2005-06-26 01:54:22 +04:00
2005-04-17 02:20:36 +04:00
/* do we have a SCTTable machine identication number on our hands */
if ( id = = 0xfc2f ) {
/* start by getting a pointer into the BIOS */
regs . eax = 0xc000 ;
regs . ebx = 0x0000 ;
regs . ecx = 0x0000 ;
tosh_smm ( & regs ) ;
bx = ( unsigned short ) ( regs . ebx & 0xffff ) ;
/* At this point in the Toshiba routines under MS Windows
the bx register holds 0xe6f5 . However my code is producing
a different value ! For the time being I will just fudge the
value . This has been verified on a Satellite Pro 430 CDT ,
Tecra 750 CDT , Tecra 780 DVD and Satellite 310 CDT . */
# if TOSH_DEBUG
printk ( " toshiba: debugging ID ebx=0x%04x \n " , regs . ebx ) ;
# endif
bx = 0xe6f5 ;
/* now twiddle with our pointer a bit */
2006-03-24 14:15:36 +03:00
address = bx ;
cx = readw ( bios + address ) ;
address = 9 + bx + cx ;
cx = readw ( bios + address ) ;
address = 0xa + cx ;
cx = readw ( bios + address ) ;
2005-04-17 02:20:36 +04:00
/* now construct our machine identification number */
id = ( ( cx & 0xff ) < < 8 ) + ( ( cx & 0xff00 ) > > 8 ) ;
}
return id ;
}
/*
* Probe for the presence of a Toshiba laptop
*
* returns and non - zero if unable to detect the presence of a Toshiba
* laptop , otherwise zero and determines the Machine ID , BIOS version and
* date , and SCI version .
*/
static int tosh_probe ( void )
{
int i , major , minor , day , year , month , flag ;
unsigned char signature [ 7 ] = { 0x54 , 0x4f , 0x53 , 0x48 , 0x49 , 0x42 , 0x41 } ;
SMMRegisters regs ;
2006-03-24 14:15:36 +03:00
void __iomem * bios = ioremap ( 0xf0000 , 0x10000 ) ;
if ( ! bios )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
/* extra sanity check for the string "TOSHIBA" in the BIOS because
some machines that are not Toshiba ' s pass the next test */
for ( i = 0 ; i < 7 ; i + + ) {
2006-03-24 14:15:36 +03:00
if ( readb ( bios + 0xe010 + i ) ! = signature [ i ] ) {
2005-04-17 02:20:36 +04:00
printk ( " toshiba: not a supported Toshiba laptop \n " ) ;
2006-03-24 14:15:36 +03:00
iounmap ( bios ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
}
/* call the Toshiba SCI support check routine */
2005-06-26 01:54:22 +04:00
2005-04-17 02:20:36 +04:00
regs . eax = 0xf0f0 ;
regs . ebx = 0x0000 ;
regs . ecx = 0x0000 ;
flag = tosh_smm ( & regs ) ;
/* if this is not a Toshiba laptop carry flag is set and ah=0x86 */
if ( ( flag = = 1 ) | | ( ( regs . eax & 0xff00 ) = = 0x8600 ) ) {
printk ( " toshiba: not a supported Toshiba laptop \n " ) ;
2006-03-24 14:15:36 +03:00
iounmap ( bios ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
/* if we get this far then we are running on a Toshiba (probably)! */
tosh_sci = regs . edx & 0xffff ;
2005-06-26 01:54:22 +04:00
2005-04-17 02:20:36 +04:00
/* next get the machine ID of the current laptop */
2006-03-24 14:15:36 +03:00
tosh_id = tosh_get_machine_id ( bios ) ;
2005-04-17 02:20:36 +04:00
/* get the BIOS version */
2006-03-24 14:15:36 +03:00
major = readb ( bios + 0xe009 ) - ' 0 ' ;
minor = ( ( readb ( bios + 0xe00b ) - ' 0 ' ) * 10 ) + ( readb ( bios + 0xe00c ) - ' 0 ' ) ;
2005-04-17 02:20:36 +04:00
tosh_bios = ( major * 0x100 ) + minor ;
/* get the BIOS date */
2006-03-24 14:15:36 +03:00
day = ( ( readb ( bios + 0xfff5 ) - ' 0 ' ) * 10 ) + ( readb ( bios + 0xfff6 ) - ' 0 ' ) ;
month = ( ( readb ( bios + 0xfff8 ) - ' 0 ' ) * 10 ) + ( readb ( bios + 0xfff9 ) - ' 0 ' ) ;
year = ( ( readb ( bios + 0xfffb ) - ' 0 ' ) * 10 ) + ( readb ( bios + 0xfffc ) - ' 0 ' ) ;
2005-04-17 02:20:36 +04:00
tosh_date = ( ( ( year - 90 ) & 0x1f ) < < 10 ) | ( ( month & 0xf ) < < 6 )
| ( ( day & 0x1f ) < < 1 ) ;
/* in theory we should check the ports we are going to use for the
fn key detection ( and the fan on the Portage 610 / Tecra700 ) , and
then request them to stop other drivers using them . However as
the keyboard driver grabs 0x60 - 0x6f and the pic driver grabs
0xa0 - 0xbf we can ' t . We just have to live dangerously and use the
ports anyway , oh boy ! */
/* do we need to emulate the fan? */
if ( ( tosh_id = = 0xfccb ) | | ( tosh_id = = 0xfccc ) )
tosh_fan = 1 ;
2006-03-24 14:15:36 +03:00
iounmap ( bios ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-06-26 01:54:22 +04:00
static int __init toshiba_init ( void )
2005-04-17 02:20:36 +04:00
{
int retval ;
/* are we running on a Toshiba laptop */
2005-06-26 01:54:22 +04:00
if ( tosh_probe ( ) )
return - ENODEV ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:22 +04:00
printk ( KERN_INFO " Toshiba System Managment Mode driver v " TOSH_VERSION " \n " ) ;
2005-04-17 02:20:36 +04:00
/* set the port to use for Fn status if not specified as a parameter */
if ( tosh_fn = = 0x00 )
tosh_set_fn_port ( ) ;
/* register the device file */
retval = misc_register ( & tosh_device ) ;
2005-06-26 01:54:22 +04:00
if ( retval < 0 )
2005-04-17 02:20:36 +04:00
return retval ;
# ifdef CONFIG_PROC_FS
/* register the proc entry */
2005-06-26 01:54:22 +04:00
if ( create_proc_info_entry ( " toshiba " , 0 , NULL , tosh_get_info ) = = NULL ) {
2005-04-17 02:20:36 +04:00
misc_deregister ( & tosh_device ) ;
return - ENOMEM ;
}
# endif
return 0 ;
}
2005-06-26 01:54:22 +04:00
static void __exit toshiba_exit ( void )
2005-04-17 02:20:36 +04:00
{
remove_proc_entry ( " toshiba " , NULL ) ;
misc_deregister ( & tosh_device ) ;
}
2005-06-26 01:54:22 +04:00
module_init ( toshiba_init ) ;
module_exit ( toshiba_exit ) ;
2005-04-17 02:20:36 +04:00