Contents of /alx-src/tags/kernel26-2.6.12-alx-r9/drivers/char/i8k.c
Parent Directory | Revision Log
Revision 630 -
(show annotations)
(download)
Wed Mar 4 11:03:09 2009 UTC (15 years, 3 months ago) by niro
File MIME type: text/plain
File size: 16523 byte(s)
Wed Mar 4 11:03:09 2009 UTC (15 years, 3 months ago) by niro
File MIME type: text/plain
File size: 16523 byte(s)
Tag kernel26-2.6.12-alx-r9
1 | /* |
2 | * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops. |
3 | * See http://www.debian.org/~dz/i8k/ for more information |
4 | * and for latest version of this driver. |
5 | * |
6 | * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org> |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify it |
9 | * under the terms of the GNU General Public License as published by the |
10 | * Free Software Foundation; either version 2, or (at your option) any |
11 | * later version. |
12 | * |
13 | * This program is distributed in the hope that it will be useful, but |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * General Public License for more details. |
17 | */ |
18 | |
19 | #include <linux/module.h> |
20 | #include <linux/types.h> |
21 | #include <linux/init.h> |
22 | #include <linux/proc_fs.h> |
23 | #include <linux/apm_bios.h> |
24 | #include <asm/uaccess.h> |
25 | #include <asm/io.h> |
26 | |
27 | #include <linux/i8k.h> |
28 | |
29 | #define I8K_VERSION "1.13 14/05/2002" |
30 | |
31 | #define I8K_SMM_FN_STATUS 0x0025 |
32 | #define I8K_SMM_POWER_STATUS 0x0069 |
33 | #define I8K_SMM_SET_FAN 0x01a3 |
34 | #define I8K_SMM_GET_FAN 0x00a3 |
35 | #define I8K_SMM_GET_SPEED 0x02a3 |
36 | #define I8K_SMM_GET_TEMP 0x10a3 |
37 | #define I8K_SMM_GET_DELL_SIG 0xffa3 |
38 | #define I8K_SMM_BIOS_VERSION 0x00a6 |
39 | |
40 | #define I8K_FAN_MULT 30 |
41 | #define I8K_MAX_TEMP 127 |
42 | |
43 | #define I8K_FN_NONE 0x00 |
44 | #define I8K_FN_UP 0x01 |
45 | #define I8K_FN_DOWN 0x02 |
46 | #define I8K_FN_MUTE 0x04 |
47 | #define I8K_FN_MASK 0x07 |
48 | #define I8K_FN_SHIFT 8 |
49 | |
50 | #define I8K_POWER_AC 0x05 |
51 | #define I8K_POWER_BATTERY 0x01 |
52 | |
53 | #define I8K_TEMPERATURE_BUG 1 |
54 | |
55 | #define DELL_SIGNATURE "Dell Computer" |
56 | |
57 | static char *supported_models[] = { |
58 | "Inspiron", |
59 | "Latitude", |
60 | NULL |
61 | }; |
62 | |
63 | static char system_vendor[48] = "?"; |
64 | static char product_name [48] = "?"; |
65 | static char bios_version [4] = "?"; |
66 | static char serial_number[16] = "?"; |
67 | |
68 | MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); |
69 | MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops"); |
70 | MODULE_LICENSE("GPL"); |
71 | |
72 | static int force; |
73 | module_param(force, bool, 0); |
74 | MODULE_PARM_DESC(force, "Force loading without checking for supported models"); |
75 | |
76 | static int restricted; |
77 | module_param(restricted, bool, 0); |
78 | MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set"); |
79 | |
80 | static int power_status; |
81 | module_param(power_status, bool, 0600); |
82 | MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k"); |
83 | |
84 | static ssize_t i8k_read(struct file *, char __user *, size_t, loff_t *); |
85 | static int i8k_ioctl(struct inode *, struct file *, unsigned int, |
86 | unsigned long); |
87 | |
88 | static struct file_operations i8k_fops = { |
89 | .read = i8k_read, |
90 | .ioctl = i8k_ioctl, |
91 | }; |
92 | |
93 | typedef struct { |
94 | unsigned int eax; |
95 | unsigned int ebx __attribute__ ((packed)); |
96 | unsigned int ecx __attribute__ ((packed)); |
97 | unsigned int edx __attribute__ ((packed)); |
98 | unsigned int esi __attribute__ ((packed)); |
99 | unsigned int edi __attribute__ ((packed)); |
100 | } SMMRegisters; |
101 | |
102 | typedef struct { |
103 | u8 type; |
104 | u8 length; |
105 | u16 handle; |
106 | } DMIHeader; |
107 | |
108 | /* |
109 | * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard. |
110 | */ |
111 | static int i8k_smm(SMMRegisters *regs) |
112 | { |
113 | int rc; |
114 | int eax = regs->eax; |
115 | |
116 | asm("pushl %%eax\n\t" \ |
117 | "movl 0(%%eax),%%edx\n\t" \ |
118 | "push %%edx\n\t" \ |
119 | "movl 4(%%eax),%%ebx\n\t" \ |
120 | "movl 8(%%eax),%%ecx\n\t" \ |
121 | "movl 12(%%eax),%%edx\n\t" \ |
122 | "movl 16(%%eax),%%esi\n\t" \ |
123 | "movl 20(%%eax),%%edi\n\t" \ |
124 | "popl %%eax\n\t" \ |
125 | "out %%al,$0xb2\n\t" \ |
126 | "out %%al,$0x84\n\t" \ |
127 | "xchgl %%eax,(%%esp)\n\t" |
128 | "movl %%ebx,4(%%eax)\n\t" \ |
129 | "movl %%ecx,8(%%eax)\n\t" \ |
130 | "movl %%edx,12(%%eax)\n\t" \ |
131 | "movl %%esi,16(%%eax)\n\t" \ |
132 | "movl %%edi,20(%%eax)\n\t" \ |
133 | "popl %%edx\n\t" \ |
134 | "movl %%edx,0(%%eax)\n\t" \ |
135 | "lahf\n\t" \ |
136 | "shrl $8,%%eax\n\t" \ |
137 | "andl $1,%%eax\n" \ |
138 | : "=a" (rc) |
139 | : "a" (regs) |
140 | : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); |
141 | |
142 | if ((rc != 0) || ((regs->eax & 0xffff) == 0xffff) || (regs->eax == eax)) { |
143 | return -EINVAL; |
144 | } |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | /* |
150 | * Read the bios version. Return the version as an integer corresponding |
151 | * to the ascii value, for example "A17" is returned as 0x00413137. |
152 | */ |
153 | static int i8k_get_bios_version(void) |
154 | { |
155 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
156 | int rc; |
157 | |
158 | regs.eax = I8K_SMM_BIOS_VERSION; |
159 | if ((rc=i8k_smm(®s)) < 0) { |
160 | return rc; |
161 | } |
162 | |
163 | return regs.eax; |
164 | } |
165 | |
166 | /* |
167 | * Read the machine id. |
168 | */ |
169 | static int i8k_get_serial_number(unsigned char *buff) |
170 | { |
171 | strlcpy(buff, serial_number, sizeof(serial_number)); |
172 | return 0; |
173 | } |
174 | |
175 | /* |
176 | * Read the Fn key status. |
177 | */ |
178 | static int i8k_get_fn_status(void) |
179 | { |
180 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
181 | int rc; |
182 | |
183 | regs.eax = I8K_SMM_FN_STATUS; |
184 | if ((rc=i8k_smm(®s)) < 0) { |
185 | return rc; |
186 | } |
187 | |
188 | switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) { |
189 | case I8K_FN_UP: |
190 | return I8K_VOL_UP; |
191 | case I8K_FN_DOWN: |
192 | return I8K_VOL_DOWN; |
193 | case I8K_FN_MUTE: |
194 | return I8K_VOL_MUTE; |
195 | default: |
196 | return 0; |
197 | } |
198 | } |
199 | |
200 | /* |
201 | * Read the power status. |
202 | */ |
203 | static int i8k_get_power_status(void) |
204 | { |
205 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
206 | int rc; |
207 | |
208 | regs.eax = I8K_SMM_POWER_STATUS; |
209 | if ((rc=i8k_smm(®s)) < 0) { |
210 | return rc; |
211 | } |
212 | |
213 | switch (regs.eax & 0xff) { |
214 | case I8K_POWER_AC: |
215 | return I8K_AC; |
216 | default: |
217 | return I8K_BATTERY; |
218 | } |
219 | } |
220 | |
221 | /* |
222 | * Read the fan status. |
223 | */ |
224 | static int i8k_get_fan_status(int fan) |
225 | { |
226 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
227 | int rc; |
228 | |
229 | regs.eax = I8K_SMM_GET_FAN; |
230 | regs.ebx = fan & 0xff; |
231 | if ((rc=i8k_smm(®s)) < 0) { |
232 | return rc; |
233 | } |
234 | |
235 | return (regs.eax & 0xff); |
236 | } |
237 | |
238 | /* |
239 | * Read the fan speed in RPM. |
240 | */ |
241 | static int i8k_get_fan_speed(int fan) |
242 | { |
243 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
244 | int rc; |
245 | |
246 | regs.eax = I8K_SMM_GET_SPEED; |
247 | regs.ebx = fan & 0xff; |
248 | if ((rc=i8k_smm(®s)) < 0) { |
249 | return rc; |
250 | } |
251 | |
252 | return (regs.eax & 0xffff) * I8K_FAN_MULT; |
253 | } |
254 | |
255 | /* |
256 | * Set the fan speed (off, low, high). Returns the new fan status. |
257 | */ |
258 | static int i8k_set_fan(int fan, int speed) |
259 | { |
260 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
261 | int rc; |
262 | |
263 | speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed); |
264 | |
265 | regs.eax = I8K_SMM_SET_FAN; |
266 | regs.ebx = (fan & 0xff) | (speed << 8); |
267 | if ((rc=i8k_smm(®s)) < 0) { |
268 | return rc; |
269 | } |
270 | |
271 | return (i8k_get_fan_status(fan)); |
272 | } |
273 | |
274 | /* |
275 | * Read the cpu temperature. |
276 | */ |
277 | static int i8k_get_cpu_temp(void) |
278 | { |
279 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
280 | int rc; |
281 | int temp; |
282 | |
283 | #ifdef I8K_TEMPERATURE_BUG |
284 | static int prev = 0; |
285 | #endif |
286 | |
287 | regs.eax = I8K_SMM_GET_TEMP; |
288 | if ((rc=i8k_smm(®s)) < 0) { |
289 | return rc; |
290 | } |
291 | temp = regs.eax & 0xff; |
292 | |
293 | #ifdef I8K_TEMPERATURE_BUG |
294 | /* |
295 | * Sometimes the temperature sensor returns 0x99, which is out of range. |
296 | * In this case we return (once) the previous cached value. For example: |
297 | # 1003655137 00000058 00005a4b |
298 | # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees |
299 | # 1003655139 00000054 00005c52 |
300 | */ |
301 | if (temp > I8K_MAX_TEMP) { |
302 | temp = prev; |
303 | prev = I8K_MAX_TEMP; |
304 | } else { |
305 | prev = temp; |
306 | } |
307 | #endif |
308 | |
309 | return temp; |
310 | } |
311 | |
312 | static int i8k_get_dell_signature(void) |
313 | { |
314 | SMMRegisters regs = { 0, 0, 0, 0, 0, 0 }; |
315 | int rc; |
316 | |
317 | regs.eax = I8K_SMM_GET_DELL_SIG; |
318 | if ((rc=i8k_smm(®s)) < 0) { |
319 | return rc; |
320 | } |
321 | |
322 | if ((regs.eax == 1145651527) && (regs.edx == 1145392204)) { |
323 | return 0; |
324 | } else { |
325 | return -1; |
326 | } |
327 | } |
328 | |
329 | static int i8k_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, |
330 | unsigned long arg) |
331 | { |
332 | int val; |
333 | int speed; |
334 | unsigned char buff[16]; |
335 | int __user *argp = (int __user *)arg; |
336 | |
337 | if (!argp) |
338 | return -EINVAL; |
339 | |
340 | switch (cmd) { |
341 | case I8K_BIOS_VERSION: |
342 | val = i8k_get_bios_version(); |
343 | break; |
344 | |
345 | case I8K_MACHINE_ID: |
346 | memset(buff, 0, 16); |
347 | val = i8k_get_serial_number(buff); |
348 | break; |
349 | |
350 | case I8K_FN_STATUS: |
351 | val = i8k_get_fn_status(); |
352 | break; |
353 | |
354 | case I8K_POWER_STATUS: |
355 | val = i8k_get_power_status(); |
356 | break; |
357 | |
358 | case I8K_GET_TEMP: |
359 | val = i8k_get_cpu_temp(); |
360 | break; |
361 | |
362 | case I8K_GET_SPEED: |
363 | if (copy_from_user(&val, argp, sizeof(int))) { |
364 | return -EFAULT; |
365 | } |
366 | val = i8k_get_fan_speed(val); |
367 | break; |
368 | |
369 | case I8K_GET_FAN: |
370 | if (copy_from_user(&val, argp, sizeof(int))) { |
371 | return -EFAULT; |
372 | } |
373 | val = i8k_get_fan_status(val); |
374 | break; |
375 | |
376 | case I8K_SET_FAN: |
377 | if (restricted && !capable(CAP_SYS_ADMIN)) { |
378 | return -EPERM; |
379 | } |
380 | if (copy_from_user(&val, argp, sizeof(int))) { |
381 | return -EFAULT; |
382 | } |
383 | if (copy_from_user(&speed, argp+1, sizeof(int))) { |
384 | return -EFAULT; |
385 | } |
386 | val = i8k_set_fan(val, speed); |
387 | break; |
388 | |
389 | default: |
390 | return -EINVAL; |
391 | } |
392 | |
393 | if (val < 0) { |
394 | return val; |
395 | } |
396 | |
397 | switch (cmd) { |
398 | case I8K_BIOS_VERSION: |
399 | if (copy_to_user(argp, &val, 4)) { |
400 | return -EFAULT; |
401 | } |
402 | break; |
403 | case I8K_MACHINE_ID: |
404 | if (copy_to_user(argp, buff, 16)) { |
405 | return -EFAULT; |
406 | } |
407 | break; |
408 | default: |
409 | if (copy_to_user(argp, &val, sizeof(int))) { |
410 | return -EFAULT; |
411 | } |
412 | break; |
413 | } |
414 | |
415 | return 0; |
416 | } |
417 | |
418 | /* |
419 | * Print the information for /proc/i8k. |
420 | */ |
421 | static int i8k_get_info(char *buffer, char **start, off_t fpos, int length) |
422 | { |
423 | int n, fn_key, cpu_temp, ac_power; |
424 | int left_fan, right_fan, left_speed, right_speed; |
425 | |
426 | cpu_temp = i8k_get_cpu_temp(); /* 11100 µs */ |
427 | left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */ |
428 | right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */ |
429 | left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */ |
430 | right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */ |
431 | fn_key = i8k_get_fn_status(); /* 750 µs */ |
432 | if (power_status) { |
433 | ac_power = i8k_get_power_status(); /* 14700 µs */ |
434 | } else { |
435 | ac_power = -1; |
436 | } |
437 | |
438 | /* |
439 | * Info: |
440 | * |
441 | * 1) Format version (this will change if format changes) |
442 | * 2) BIOS version |
443 | * 3) BIOS machine ID |
444 | * 4) Cpu temperature |
445 | * 5) Left fan status |
446 | * 6) Right fan status |
447 | * 7) Left fan speed |
448 | * 8) Right fan speed |
449 | * 9) AC power |
450 | * 10) Fn Key status |
451 | */ |
452 | n = sprintf(buffer, "%s %s %s %d %d %d %d %d %d %d\n", |
453 | I8K_PROC_FMT, |
454 | bios_version, |
455 | serial_number, |
456 | cpu_temp, |
457 | left_fan, |
458 | right_fan, |
459 | left_speed, |
460 | right_speed, |
461 | ac_power, |
462 | fn_key); |
463 | |
464 | return n; |
465 | } |
466 | |
467 | static ssize_t i8k_read(struct file *f, char __user *buffer, size_t len, loff_t *fpos) |
468 | { |
469 | int n; |
470 | char info[128]; |
471 | |
472 | n = i8k_get_info(info, NULL, 0, 128); |
473 | if (n <= 0) { |
474 | return n; |
475 | } |
476 | |
477 | if (*fpos >= n) { |
478 | return 0; |
479 | } |
480 | |
481 | if ((*fpos + len) >= n) { |
482 | len = n - *fpos; |
483 | } |
484 | |
485 | if (copy_to_user(buffer, info, len) != 0) { |
486 | return -EFAULT; |
487 | } |
488 | |
489 | *fpos += len; |
490 | return len; |
491 | } |
492 | |
493 | static char* __init string_trim(char *s, int size) |
494 | { |
495 | int len; |
496 | char *p; |
497 | |
498 | if ((len = strlen(s)) > size) { |
499 | len = size; |
500 | } |
501 | |
502 | for (p=s+len-1; len && (*p==' '); len--,p--) { |
503 | *p = '\0'; |
504 | } |
505 | |
506 | return s; |
507 | } |
508 | |
509 | /* DMI code, stolen from arch/i386/kernel/dmi_scan.c */ |
510 | |
511 | /* |
512 | * |<-- dmi->length -->| |
513 | * | | |
514 | * |dmi header s=N | string1,\0, ..., stringN,\0, ..., \0 |
515 | * | | |
516 | * +-----------------------+ |
517 | */ |
518 | static char* __init dmi_string(DMIHeader *dmi, u8 s) |
519 | { |
520 | u8 *p; |
521 | |
522 | if (!s) { |
523 | return ""; |
524 | } |
525 | s--; |
526 | |
527 | p = (u8 *)dmi + dmi->length; |
528 | while (s > 0) { |
529 | p += strlen(p); |
530 | p++; |
531 | s--; |
532 | } |
533 | |
534 | return p; |
535 | } |
536 | |
537 | static void __init dmi_decode(DMIHeader *dmi) |
538 | { |
539 | u8 *data = (u8 *) dmi; |
540 | char *p; |
541 | |
542 | #ifdef I8K_DEBUG |
543 | int i; |
544 | printk("%08x ", (int)data); |
545 | for (i=0; i<data[1] && i<64; i++) { |
546 | printk("%02x ", data[i]); |
547 | } |
548 | printk("\n"); |
549 | #endif |
550 | |
551 | switch (dmi->type) { |
552 | case 0: /* BIOS Information */ |
553 | p = dmi_string(dmi,data[5]); |
554 | if (*p) { |
555 | strlcpy(bios_version, p, sizeof(bios_version)); |
556 | string_trim(bios_version, sizeof(bios_version)); |
557 | } |
558 | break; |
559 | case 1: /* System Information */ |
560 | p = dmi_string(dmi,data[4]); |
561 | if (*p) { |
562 | strlcpy(system_vendor, p, sizeof(system_vendor)); |
563 | string_trim(system_vendor, sizeof(system_vendor)); |
564 | } |
565 | p = dmi_string(dmi,data[5]); |
566 | if (*p) { |
567 | strlcpy(product_name, p, sizeof(product_name)); |
568 | string_trim(product_name, sizeof(product_name)); |
569 | } |
570 | p = dmi_string(dmi,data[7]); |
571 | if (*p) { |
572 | strlcpy(serial_number, p, sizeof(serial_number)); |
573 | string_trim(serial_number, sizeof(serial_number)); |
574 | } |
575 | break; |
576 | } |
577 | } |
578 | |
579 | static int __init dmi_table(u32 base, int len, int num, void (*fn)(DMIHeader*)) |
580 | { |
581 | u8 *buf; |
582 | u8 *data; |
583 | DMIHeader *dmi; |
584 | int i = 1; |
585 | |
586 | buf = ioremap(base, len); |
587 | if (buf == NULL) { |
588 | return -1; |
589 | } |
590 | data = buf; |
591 | |
592 | /* |
593 | * Stop when we see al the items the table claimed to have |
594 | * or we run off the end of the table (also happens) |
595 | */ |
596 | while ((i<num) && ((data-buf) < len)) { |
597 | dmi = (DMIHeader *)data; |
598 | /* |
599 | * Avoid misparsing crud if the length of the last |
600 | * record is crap |
601 | */ |
602 | if ((data-buf+dmi->length) >= len) { |
603 | break; |
604 | } |
605 | fn(dmi); |
606 | data += dmi->length; |
607 | /* |
608 | * Don't go off the end of the data if there is |
609 | * stuff looking like string fill past the end |
610 | */ |
611 | while (((data-buf) < len) && (*data || data[1])) { |
612 | data++; |
613 | } |
614 | data += 2; |
615 | i++; |
616 | } |
617 | iounmap(buf); |
618 | |
619 | return 0; |
620 | } |
621 | |
622 | static int __init dmi_iterate(void (*decode)(DMIHeader *)) |
623 | { |
624 | unsigned char buf[20]; |
625 | void __iomem *p = ioremap(0xe0000, 0x20000), *q; |
626 | |
627 | if (!p) |
628 | return -1; |
629 | |
630 | for (q = p; q < p + 0x20000; q += 16) { |
631 | memcpy_fromio(buf, q, 20); |
632 | if (memcmp(buf, "_DMI_", 5)==0) { |
633 | u16 num = buf[13]<<8 | buf[12]; |
634 | u16 len = buf [7]<<8 | buf [6]; |
635 | u32 base = buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8]; |
636 | #ifdef I8K_DEBUG |
637 | printk(KERN_INFO "DMI %d.%d present.\n", |
638 | buf[14]>>4, buf[14]&0x0F); |
639 | printk(KERN_INFO "%d structures occupying %d bytes.\n", |
640 | buf[13]<<8 | buf[12], |
641 | buf [7]<<8 | buf[6]); |
642 | printk(KERN_INFO "DMI table at 0x%08X.\n", |
643 | buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8]); |
644 | #endif |
645 | if (dmi_table(base, len, num, decode)==0) { |
646 | iounmap(p); |
647 | return 0; |
648 | } |
649 | } |
650 | } |
651 | iounmap(p); |
652 | return -1; |
653 | } |
654 | /* end of DMI code */ |
655 | |
656 | /* |
657 | * Get DMI information. |
658 | */ |
659 | static int __init i8k_dmi_probe(void) |
660 | { |
661 | char **p; |
662 | |
663 | if (dmi_iterate(dmi_decode) != 0) { |
664 | printk(KERN_INFO "i8k: unable to get DMI information\n"); |
665 | return -ENODEV; |
666 | } |
667 | |
668 | if (strncmp(system_vendor,DELL_SIGNATURE,strlen(DELL_SIGNATURE)) != 0) { |
669 | printk(KERN_INFO "i8k: not running on a Dell system\n"); |
670 | return -ENODEV; |
671 | } |
672 | |
673 | for (p=supported_models; ; p++) { |
674 | if (!*p) { |
675 | printk(KERN_INFO "i8k: unsupported model: %s\n", product_name); |
676 | return -ENODEV; |
677 | } |
678 | if (strncmp(product_name,*p,strlen(*p)) == 0) { |
679 | break; |
680 | } |
681 | } |
682 | |
683 | return 0; |
684 | } |
685 | |
686 | /* |
687 | * Probe for the presence of a supported laptop. |
688 | */ |
689 | static int __init i8k_probe(void) |
690 | { |
691 | char buff[4]; |
692 | int version; |
693 | int smm_found = 0; |
694 | |
695 | /* |
696 | * Get DMI information |
697 | */ |
698 | if (i8k_dmi_probe() != 0) { |
699 | printk(KERN_INFO "i8k: vendor=%s, model=%s, version=%s\n", |
700 | system_vendor, product_name, bios_version); |
701 | } |
702 | |
703 | /* |
704 | * Get SMM Dell signature |
705 | */ |
706 | if (i8k_get_dell_signature() != 0) { |
707 | printk(KERN_INFO "i8k: unable to get SMM Dell signature\n"); |
708 | } else { |
709 | smm_found = 1; |
710 | } |
711 | |
712 | /* |
713 | * Get SMM BIOS version. |
714 | */ |
715 | version = i8k_get_bios_version(); |
716 | if (version <= 0) { |
717 | printk(KERN_INFO "i8k: unable to get SMM BIOS version\n"); |
718 | } else { |
719 | smm_found = 1; |
720 | buff[0] = (version >> 16) & 0xff; |
721 | buff[1] = (version >> 8) & 0xff; |
722 | buff[2] = (version) & 0xff; |
723 | buff[3] = '\0'; |
724 | /* |
725 | * If DMI BIOS version is unknown use SMM BIOS version. |
726 | */ |
727 | if (bios_version[0] == '?') { |
728 | strcpy(bios_version, buff); |
729 | } |
730 | /* |
731 | * Check if the two versions match. |
732 | */ |
733 | if (strncmp(buff,bios_version,sizeof(bios_version)) != 0) { |
734 | printk(KERN_INFO "i8k: BIOS version mismatch: %s != %s\n", |
735 | buff, bios_version); |
736 | } |
737 | } |
738 | |
739 | if (!smm_found && !force) { |
740 | return -ENODEV; |
741 | } |
742 | |
743 | return 0; |
744 | } |
745 | |
746 | #ifdef MODULE |
747 | static |
748 | #endif |
749 | int __init i8k_init(void) |
750 | { |
751 | struct proc_dir_entry *proc_i8k; |
752 | |
753 | /* Are we running on an supported laptop? */ |
754 | if (i8k_probe() != 0) { |
755 | return -ENODEV; |
756 | } |
757 | |
758 | /* Register the proc entry */ |
759 | proc_i8k = create_proc_info_entry("i8k", 0, NULL, i8k_get_info); |
760 | if (!proc_i8k) { |
761 | return -ENOENT; |
762 | } |
763 | proc_i8k->proc_fops = &i8k_fops; |
764 | proc_i8k->owner = THIS_MODULE; |
765 | |
766 | printk(KERN_INFO |
767 | "Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n", |
768 | I8K_VERSION); |
769 | |
770 | return 0; |
771 | } |
772 | |
773 | #ifdef MODULE |
774 | int init_module(void) |
775 | { |
776 | return i8k_init(); |
777 | } |
778 | |
779 | void cleanup_module(void) |
780 | { |
781 | /* Remove the proc entry */ |
782 | remove_proc_entry("i8k", NULL); |
783 | |
784 | printk(KERN_INFO "i8k: module unloaded\n"); |
785 | } |
786 | #endif |
787 | |
788 | /* end of file */ |