1// SPDX-License-Identifier: GPL-2.0-or-later
2/* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops
3 *
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 of
11 *     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 WARNING
27 *
28 *   This code is covered by the GNU GPL and you are free to make any
29 *   changes you wish to it under the terms of the license. However the
30 *   code has the potential to render your computer and/or someone else's
31 *   unusable. Please proceed with care when modifying the code.
32 *
33 * Note: Unfortunately the laptop hardware can close the System Configuration
34 *       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 at
36 *       *any* time. It is up to any program to be aware of this eventuality
37 *       and take appropriate steps.
38 *
39 * The information used to write this driver has been obtained by reverse
40 * engineering the software supplied by Toshiba for their portable computers in
41 * strict accordance with the European Council Directive 92/250/EEC on the legal
42 * protection of computer programs, and it's implementation into English Law by
43 * 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 0
48
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_fops
93};
94
95/*
96 * Read the Fn key status
97 */
98#ifdef CONFIG_PROC_FS
99static 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#endif
116
117
118/*
119 * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function
120 */
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 Mode
205 */
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/toshiba
290 */
291#ifdef CONFIG_PROC_FS
292static int proc_toshiba_show(struct seq_file *m, void *v)
293{
294	int key;
295
296	key = tosh_fn_status();
297
298	/* Arguments
299	     0) Linux driver version (this will change if format changes)
300	     1) Machine ID
301	     2) SCI version
302	     3) BIOS version (major, minor)
303	     4) BIOS date (in SCI date format)
304	     5) Fn Key status
305	*/
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#endif
317
318
319/*
320 * Determine which port to use for the Fn key status
321 */
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 model
345 */
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 Windows
368		   the bx register holds 0xe6f5. However my code is producing
369		   a different value! For the time being I will just fudge the
370		   value. This has been verified on a Satellite Pro 430CDT,
371		   Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */
372#if TOSH_DEBUG
373		pr_debug("toshiba: debugging ID ebx=0x%04x\n", regs.ebx);
374#endif
375		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 laptop
397 *
398 *   returns and non-zero if unable to detect the presence of a Toshiba
399 *   laptop, otherwise zero and determines the Machine ID, BIOS version and
400 *   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 because
413	   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 the
462	   fn key detection (and the fan on the Portage 610/Tecra700), and
463	   then request them to stop other drivers using them. However as
464	   the keyboard driver grabs 0x60-0x6f and the pic driver grabs
465	   0xa0-0xbf we can't. We just have to live dangerously and use the
466	   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_FS
498	{
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#endif
508
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