brintos

brintos / linux-shallow public Read only

0
0
Text · 12.4 KiB · 776abbf Raw
521 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops3 *4 * Copyright (c) 1996-2001  Jonathan A. Buzzard (jonathan@buzzard.org.uk)5 *6 * Valuable assistance and patches from:7 *     Tom May <tom@you-bastards.com>8 *     Rob Napier <rnapier@employees.org>9 *10 * Fn status port numbers for machine ID's courtesy of11 *     0xfc02: Scott Eisert <scott.e@sky-eye.com>12 *     0xfc04: Steve VanDevender <stevev@efn.org>13 *     0xfc08: Garth Berry <garth@itsbruce.net>14 *     0xfc0a: Egbert Eich <eich@xfree86.org>15 *     0xfc10: Andrew Lofthouse <Andrew.Lofthouse@robins.af.mil>16 *     0xfc11: Spencer Olson <solson@novell.com>17 *     0xfc13: Claudius Frankewitz <kryp@gmx.de>18 *     0xfc15: Tom May <tom@you-bastards.com>19 *     0xfc17: Dave Konrad <konrad@xenia.it>20 *     0xfc1a: George Betzos <betzos@engr.colostate.edu>21 *     0xfc1b: Munemasa Wada <munemasa@jnovel.co.jp>22 *     0xfc1d: Arthur Liu <armie@slap.mine.nu>23 *     0xfc5a: Jacques L'helgoualc'h <lhh@free.fr>24 *     0xfcd1: Mr. Dave Konrad <konrad@xenia.it>25 *26 * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING27 *28 *   This code is covered by the GNU GPL and you are free to make any29 *   changes you wish to it under the terms of the license. However the30 *   code has the potential to render your computer and/or someone else's31 *   unusable. Please proceed with care when modifying the code.32 *33 * Note: Unfortunately the laptop hardware can close the System Configuration34 *       Interface on it's own accord. It is therefore necessary for *all*35 *       programs using this driver to be aware that *any* SCI call can fail at36 *       *any* time. It is up to any program to be aware of this eventuality37 *       and take appropriate steps.38 *39 * The information used to write this driver has been obtained by reverse40 * engineering the software supplied by Toshiba for their portable computers in41 * strict accordance with the European Council Directive 92/250/EEC on the legal42 * protection of computer programs, and it's implementation into English Law by43 * the Copyright (Computer Programs) Regulations 1992 (S.I. 1992 No.3233).44 */45 46#define TOSH_VERSION "1.11 26/9/2001"47#define TOSH_DEBUG 048 49#include <linux/module.h>50#include <linux/kernel.h>51#include <linux/types.h>52#include <linux/fcntl.h>53#include <linux/miscdevice.h>54#include <linux/ioport.h>55#include <asm/io.h>56#include <linux/uaccess.h>57#include <linux/init.h>58#include <linux/stat.h>59#include <linux/proc_fs.h>60#include <linux/seq_file.h>61#include <linux/mutex.h>62#include <linux/toshiba.h>63 64MODULE_LICENSE("GPL");65MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>");66MODULE_DESCRIPTION("Toshiba laptop SMM driver");67 68static DEFINE_MUTEX(tosh_mutex);69static int tosh_fn;70module_param_named(fn, tosh_fn, int, 0);71MODULE_PARM_DESC(fn, "User specified Fn key detection port");72 73static int tosh_id;74static int tosh_bios;75static int tosh_date;76static int tosh_sci;77static int tosh_fan;78 79static long tosh_ioctl(struct file *, unsigned int,80	unsigned long);81 82 83static const struct file_operations tosh_fops = {84	.owner		= THIS_MODULE,85	.unlocked_ioctl	= tosh_ioctl,86	.llseek		= noop_llseek,87};88 89static struct miscdevice tosh_device = {90	TOSH_MINOR_DEV,91	"toshiba",92	&tosh_fops93};94 95/*96 * Read the Fn key status97 */98#ifdef CONFIG_PROC_FS99static int tosh_fn_status(void)100{101        unsigned char scan;102	unsigned long flags;103 104	if (tosh_fn!=0) {105		scan = inb(tosh_fn);106	} else {107		local_irq_save(flags);108		outb(0x8e, 0xe4);109		scan = inb(0xe5);110		local_irq_restore(flags);111	}112 113        return (int) scan;114}115#endif116 117 118/*119 * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function120 */121static int tosh_emulate_fan(SMMRegisters *regs)122{123	unsigned long eax,ecx,flags;124	unsigned char al;125 126	eax = regs->eax & 0xff00;127	ecx = regs->ecx & 0xffff;128 129	/* Portage 610CT */130 131	if (tosh_id==0xfccb) {132		if (eax==0xfe00) {133			/* fan status */134			local_irq_save(flags);135			outb(0xbe, 0xe4);136			al = inb(0xe5);137			local_irq_restore(flags);138			regs->eax = 0x00;139			regs->ecx = (unsigned int) (al & 0x01);140		}141		if ((eax==0xff00) && (ecx==0x0000)) {142			/* fan off */143			local_irq_save(flags);144			outb(0xbe, 0xe4);145			al = inb(0xe5);146			outb(0xbe, 0xe4);147			outb (al | 0x01, 0xe5);148			local_irq_restore(flags);149			regs->eax = 0x00;150			regs->ecx = 0x00;151		}152		if ((eax==0xff00) && (ecx==0x0001)) {153			/* fan on */154			local_irq_save(flags);155			outb(0xbe, 0xe4);156			al = inb(0xe5);157			outb(0xbe, 0xe4);158			outb(al & 0xfe, 0xe5);159			local_irq_restore(flags);160			regs->eax = 0x00;161			regs->ecx = 0x01;162		}163	}164 165	/* Tecra 700CS/CDT */166 167	if (tosh_id==0xfccc) {168		if (eax==0xfe00) {169			/* fan status */170			local_irq_save(flags);171			outb(0xe0, 0xe4);172			al = inb(0xe5);173			local_irq_restore(flags);174			regs->eax = 0x00;175			regs->ecx = al & 0x01;176		}177		if ((eax==0xff00) && (ecx==0x0000)) {178			/* fan off */179			local_irq_save(flags);180			outb(0xe0, 0xe4);181			al = inb(0xe5);182			outw(0xe0 | ((al & 0xfe) << 8), 0xe4);183			local_irq_restore(flags);184			regs->eax = 0x00;185			regs->ecx = 0x00;186		}187		if ((eax==0xff00) && (ecx==0x0001)) {188			/* fan on */189			local_irq_save(flags);190			outb(0xe0, 0xe4);191			al = inb(0xe5);192			outw(0xe0 | ((al | 0x01) << 8), 0xe4);193			local_irq_restore(flags);194			regs->eax = 0x00;195			regs->ecx = 0x01;196		}197	}198 199	return 0;200}201 202 203/*204 * Put the laptop into System Management Mode205 */206int tosh_smm(SMMRegisters *regs)207{208	int eax;209 210	asm ("# load the values into the registers\n\t" \211		"pushl %%eax\n\t" \212		"movl 0(%%eax),%%edx\n\t" \213		"push %%edx\n\t" \214		"movl 4(%%eax),%%ebx\n\t" \215		"movl 8(%%eax),%%ecx\n\t" \216		"movl 12(%%eax),%%edx\n\t" \217		"movl 16(%%eax),%%esi\n\t" \218		"movl 20(%%eax),%%edi\n\t" \219		"popl %%eax\n\t" \220		"# call the System Management mode\n\t" \221		"inb $0xb2,%%al\n\t"222		"# fill out the memory with the values in the registers\n\t" \223		"xchgl %%eax,(%%esp)\n\t"224		"movl %%ebx,4(%%eax)\n\t" \225		"movl %%ecx,8(%%eax)\n\t" \226		"movl %%edx,12(%%eax)\n\t" \227		"movl %%esi,16(%%eax)\n\t" \228		"movl %%edi,20(%%eax)\n\t" \229		"popl %%edx\n\t" \230		"movl %%edx,0(%%eax)\n\t" \231		"# setup the return value to the carry flag\n\t" \232		"lahf\n\t" \233		"shrl $8,%%eax\n\t" \234		"andl $1,%%eax\n" \235		: "=a" (eax)236		: "a" (regs)237		: "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");238 239	return eax;240}241EXPORT_SYMBOL(tosh_smm);242 243 244static long tosh_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)245{246	SMMRegisters regs;247	SMMRegisters __user *argp = (SMMRegisters __user *)arg;248	unsigned short ax,bx;249	int err;250 251	if (!argp)252		return -EINVAL;253 254	if (copy_from_user(&regs, argp, sizeof(SMMRegisters)))255		return -EFAULT;256 257	switch (cmd) {258		case TOSH_SMM:259			ax = regs.eax & 0xff00;260			bx = regs.ebx & 0xffff;261			/* block HCI calls to read/write memory & PCI devices */262			if (((ax==0xff00) || (ax==0xfe00)) && (bx>0x0069))263				return -EINVAL;264 265			/* do we need to emulate the fan ? */266			mutex_lock(&tosh_mutex);267			if (tosh_fan==1) {268				if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) {269					err = tosh_emulate_fan(&regs);270					mutex_unlock(&tosh_mutex);271					break;272				}273			}274			err = tosh_smm(&regs);275			mutex_unlock(&tosh_mutex);276			break;277		default:278			return -EINVAL;279	}280 281        if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))282        	return -EFAULT;283 284	return (err==0) ? 0:-EINVAL;285}286 287 288/*289 * Print the information for /proc/toshiba290 */291#ifdef CONFIG_PROC_FS292static int proc_toshiba_show(struct seq_file *m, void *v)293{294	int key;295 296	key = tosh_fn_status();297 298	/* Arguments299	     0) Linux driver version (this will change if format changes)300	     1) Machine ID301	     2) SCI version302	     3) BIOS version (major, minor)303	     4) BIOS date (in SCI date format)304	     5) Fn Key status305	*/306	seq_printf(m, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n",307		tosh_id,308		(tosh_sci & 0xff00)>>8,309		tosh_sci & 0xff,310		(tosh_bios & 0xff00)>>8,311		tosh_bios & 0xff,312		tosh_date,313		key);314	return 0;315}316#endif317 318 319/*320 * Determine which port to use for the Fn key status321 */322static void tosh_set_fn_port(void)323{324	switch (tosh_id) {325		case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10:326		case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b:327		case 0xfc5a:328			tosh_fn = 0x62;329			break;330		case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0:331		case 0xfce2:332			tosh_fn = 0x68;333			break;334		default:335			tosh_fn = 0x00;336			break;337	}338 339	return;340}341 342 343/*344 * Get the machine identification number of the current model345 */346static int tosh_get_machine_id(void __iomem *bios)347{348	int id;349	SMMRegisters regs;350	unsigned short bx,cx;351	unsigned long address;352 353	id = (0x100*(int) readb(bios+0xfffe))+((int) readb(bios+0xfffa));354 355	/* do we have a SCTTable machine identication number on our hands */356 357	if (id==0xfc2f) {358 359		/* start by getting a pointer into the BIOS */360 361		regs.eax = 0xc000;362		regs.ebx = 0x0000;363		regs.ecx = 0x0000;364		tosh_smm(&regs);365		bx = (unsigned short) (regs.ebx & 0xffff);366 367		/* At this point in the Toshiba routines under MS Windows368		   the bx register holds 0xe6f5. However my code is producing369		   a different value! For the time being I will just fudge the370		   value. This has been verified on a Satellite Pro 430CDT,371		   Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */372#if TOSH_DEBUG373		pr_debug("toshiba: debugging ID ebx=0x%04x\n", regs.ebx);374#endif375		bx = 0xe6f5;376 377		/* now twiddle with our pointer a bit */378 379		address = bx;380		cx = readw(bios + address);381		address = 9+bx+cx;382		cx = readw(bios + address);383		address = 0xa+cx;384		cx = readw(bios + address);385 386		/* now construct our machine identification number */387 388		id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8);389	}390 391	return id;392}393 394 395/*396 * Probe for the presence of a Toshiba laptop397 *398 *   returns and non-zero if unable to detect the presence of a Toshiba399 *   laptop, otherwise zero and determines the Machine ID, BIOS version and400 *   date, and SCI version.401 */402static int tosh_probe(void)403{404	int i,major,minor,day,year,month,flag;405	unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 };406	SMMRegisters regs;407	void __iomem *bios = ioremap(0xf0000, 0x10000);408 409	if (!bios)410		return -ENOMEM;411 412	/* extra sanity check for the string "TOSHIBA" in the BIOS because413	   some machines that are not Toshiba's pass the next test */414 415	for (i=0;i<7;i++) {416		if (readb(bios+0xe010+i)!=signature[i]) {417			pr_err("toshiba: not a supported Toshiba laptop\n");418			iounmap(bios);419			return -ENODEV;420		}421	}422 423	/* call the Toshiba SCI support check routine */424 425	regs.eax = 0xf0f0;426	regs.ebx = 0x0000;427	regs.ecx = 0x0000;428	flag = tosh_smm(&regs);429 430	/* if this is not a Toshiba laptop carry flag is set and ah=0x86 */431 432	if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) {433		pr_err("toshiba: not a supported Toshiba laptop\n");434		iounmap(bios);435		return -ENODEV;436	}437 438	/* if we get this far then we are running on a Toshiba (probably)! */439 440	tosh_sci = regs.edx & 0xffff;441 442	/* next get the machine ID of the current laptop */443 444	tosh_id = tosh_get_machine_id(bios);445 446	/* get the BIOS version */447 448	major = readb(bios+0xe009)-'0';449	minor = ((readb(bios+0xe00b)-'0')*10)+(readb(bios+0xe00c)-'0');450	tosh_bios = (major*0x100)+minor;451 452	/* get the BIOS date */453 454	day = ((readb(bios+0xfff5)-'0')*10)+(readb(bios+0xfff6)-'0');455	month = ((readb(bios+0xfff8)-'0')*10)+(readb(bios+0xfff9)-'0');456	year = ((readb(bios+0xfffb)-'0')*10)+(readb(bios+0xfffc)-'0');457	tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6)458		| ((day & 0x1f)<<1);459 460 461	/* in theory we should check the ports we are going to use for the462	   fn key detection (and the fan on the Portage 610/Tecra700), and463	   then request them to stop other drivers using them. However as464	   the keyboard driver grabs 0x60-0x6f and the pic driver grabs465	   0xa0-0xbf we can't. We just have to live dangerously and use the466	   ports anyway, oh boy! */467 468	/* do we need to emulate the fan? */469 470	if ((tosh_id==0xfccb) || (tosh_id==0xfccc))471		tosh_fan = 1;472 473	iounmap(bios);474 475	return 0;476}477 478static int __init toshiba_init(void)479{480	int retval;481	/* are we running on a Toshiba laptop */482 483	if (tosh_probe())484		return -ENODEV;485 486	pr_info("Toshiba System Management Mode driver v" TOSH_VERSION "\n");487 488	/* set the port to use for Fn status if not specified as a parameter */489	if (tosh_fn==0x00)490		tosh_set_fn_port();491 492	/* register the device file */493	retval = misc_register(&tosh_device);494	if (retval < 0)495		return retval;496 497#ifdef CONFIG_PROC_FS498	{499		struct proc_dir_entry *pde;500 501		pde = proc_create_single("toshiba", 0, NULL, proc_toshiba_show);502		if (!pde) {503			misc_deregister(&tosh_device);504			return -ENOMEM;505		}506	}507#endif508 509	return 0;510}511 512static void __exit toshiba_exit(void)513{514	remove_proc_entry("toshiba", NULL);515	misc_deregister(&tosh_device);516}517 518module_init(toshiba_init);519module_exit(toshiba_exit);520 521