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