i8k.c
上传用户:lgb322
上传日期:2013-02-24
资源大小:30529k
文件大小:16k
源码类别:

嵌入式Linux

开发平台:

Unix_Linux

  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. #include <linux/module.h>
  19. #include <linux/version.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. #include <linux/i8k.h>
  27. #define I8K_VERSION "1.7 21/11/2001"
  28. #define I8K_SMM_FN_STATUS 0x0025
  29. #define I8K_SMM_POWER_STATUS 0x0069
  30. #define I8K_SMM_SET_FAN 0x01a3
  31. #define I8K_SMM_GET_FAN 0x00a3
  32. #define I8K_SMM_GET_SPEED 0x02a3
  33. #define I8K_SMM_GET_TEMP 0x10a3
  34. #define I8K_SMM_GET_DELL_SIG 0xffa3
  35. #define I8K_SMM_BIOS_VERSION 0x00a6
  36. #define I8K_FAN_MULT 30
  37. #define I8K_MAX_TEMP 127
  38. #define I8K_FN_NONE 0x00
  39. #define I8K_FN_UP 0x01
  40. #define I8K_FN_DOWN 0x02
  41. #define I8K_FN_MUTE 0x04
  42. #define I8K_FN_MASK 0x07
  43. #define I8K_FN_SHIFT 8
  44. #define I8K_POWER_AC 0x05
  45. #define I8K_POWER_BATTERY 0x01
  46. #define I8K_TEMPERATURE_BUG 1
  47. #define DELL_SIGNATURE "Dell Computer"
  48. static char *supported_models[] = {
  49.     "Inspiron",
  50.     "Latitude",
  51.     NULL
  52. };
  53. static char system_vendor[48] = "?";
  54. static char product_name [48] = "?";
  55. static char bios_version [4]  = "?";
  56. static char serial_number[16] = "?";
  57. int force = 0;
  58. int power_status = 0;
  59. MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
  60. MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
  61. MODULE_LICENSE("GPL");
  62. MODULE_PARM(force, "i");
  63. MODULE_PARM(power_status, "i");
  64. MODULE_PARM_DESC(force, "Force loading without checking for supported models");
  65. MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
  66. static ssize_t i8k_read(struct file *, char *, size_t, loff_t *);
  67. static int i8k_ioctl(struct inode *, struct file *, unsigned int,
  68.      unsigned long);
  69. static struct file_operations i8k_fops = {
  70.     read: i8k_read,
  71.     ioctl: i8k_ioctl,
  72. };
  73. typedef struct {
  74.     unsigned int eax;
  75.     unsigned int ebx __attribute__ ((packed));
  76.     unsigned int ecx __attribute__ ((packed));
  77.     unsigned int edx __attribute__ ((packed));
  78.     unsigned int esi __attribute__ ((packed));
  79.     unsigned int edi __attribute__ ((packed));
  80. } SMMRegisters;
  81. typedef struct {
  82.     u8 type;
  83.     u8 length;
  84.     u16 handle;
  85. } DMIHeader;
  86. /*
  87.  * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
  88.  */
  89. static int i8k_smm(SMMRegisters *regs)
  90. {
  91.     int rc;
  92.     int eax = regs->eax;
  93.     asm("pushl %%eaxnt" 
  94. "movl 0(%%eax),%%edxnt" 
  95. "push %%edxnt" 
  96. "movl 4(%%eax),%%ebxnt" 
  97. "movl 8(%%eax),%%ecxnt" 
  98. "movl 12(%%eax),%%edxnt" 
  99. "movl 16(%%eax),%%esint" 
  100. "movl 20(%%eax),%%edint" 
  101. "popl %%eaxnt" 
  102. "out %%al,$0xb2nt" 
  103. "out %%al,$0x84nt" 
  104. "xchgl %%eax,(%%esp)nt"
  105. "movl %%ebx,4(%%eax)nt" 
  106. "movl %%ecx,8(%%eax)nt" 
  107. "movl %%edx,12(%%eax)nt" 
  108. "movl %%esi,16(%%eax)nt" 
  109. "movl %%edi,20(%%eax)nt" 
  110. "popl %%edxnt" 
  111. "movl %%edx,0(%%eax)nt" 
  112. "lahfnt" 
  113. "shrl $8,%%eaxnt" 
  114. "andl $1,%%eaxn" 
  115. : "=a" (rc)
  116. : "a" (regs)
  117. : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
  118.     if ((rc != 0) || ((regs->eax & 0xffff) == 0xffff) || (regs->eax == eax)) {
  119. return -EINVAL;
  120.     }
  121.     return 0;
  122. }
  123. /*
  124.  * Read the bios version. Return the version as an integer corresponding
  125.  * to the ascii value, for example "A17" is returned as 0x00413137.
  126.  */
  127. static int i8k_get_bios_version(void)
  128. {
  129.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  130.     int rc;
  131.     regs.eax = I8K_SMM_BIOS_VERSION;
  132.     if ((rc=i8k_smm(&regs)) < 0) {
  133. return rc;
  134.     }
  135.     return regs.eax;
  136. }
  137. /*
  138.  * Read the machine id.
  139.  */
  140. static int i8k_get_serial_number(unsigned char *buff)
  141. {
  142.     strncpy(buff, serial_number, 16);
  143.     return 0;
  144. }
  145. /*
  146.  * Read the Fn key status.
  147.  */
  148. static int i8k_get_fn_status(void)
  149. {
  150.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  151.     int rc;
  152.     regs.eax = I8K_SMM_FN_STATUS;
  153.     if ((rc=i8k_smm(&regs)) < 0) {
  154. return rc;
  155.     }
  156.     switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
  157.     case I8K_FN_UP:
  158. return I8K_VOL_UP;
  159.     case I8K_FN_DOWN:
  160. return I8K_VOL_DOWN;
  161.     case I8K_FN_MUTE:
  162. return I8K_VOL_MUTE;
  163.     default:
  164. return 0;
  165.     }
  166. }
  167. /*
  168.  * Read the power status.
  169.  */
  170. static int i8k_get_power_status(void)
  171. {
  172.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  173.     int rc;
  174.     regs.eax = I8K_SMM_POWER_STATUS;
  175.     if ((rc=i8k_smm(&regs)) < 0) {
  176. return rc;
  177.     }
  178.     switch (regs.eax & 0xff) {
  179.     case I8K_POWER_AC:
  180. return I8K_AC;
  181.     default:
  182. return I8K_BATTERY;
  183.     }
  184. }
  185. /*
  186.  * Read the fan status.
  187.  */
  188. static int i8k_get_fan_status(int fan)
  189. {
  190.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  191.     int rc;
  192.     regs.eax = I8K_SMM_GET_FAN;
  193.     regs.ebx = fan & 0xff;
  194.     if ((rc=i8k_smm(&regs)) < 0) {
  195. return rc;
  196.     }
  197.     return (regs.eax & 0xff);
  198. }
  199. /*
  200.  * Read the fan speed in RPM.
  201.  */
  202. static int i8k_get_fan_speed(int fan)
  203. {
  204.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  205.     int rc;
  206.     regs.eax = I8K_SMM_GET_SPEED;
  207.     regs.ebx = fan & 0xff;
  208.     if ((rc=i8k_smm(&regs)) < 0) {
  209. return rc;
  210.     }
  211.     return (regs.eax & 0xffff) * I8K_FAN_MULT;
  212. }
  213. /*
  214.  * Set the fan speed (off, low, high). Returns the new fan status.
  215.  */
  216. static int i8k_set_fan(int fan, int speed)
  217. {
  218.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  219.     int rc;
  220.     speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed);
  221.     regs.eax = I8K_SMM_SET_FAN;
  222.     regs.ebx = (fan & 0xff) | (speed << 8);
  223.     if ((rc=i8k_smm(&regs)) < 0) {
  224. return rc;
  225.     }
  226.     return (i8k_get_fan_status(fan));
  227. }
  228. /*
  229.  * Read the cpu temperature.
  230.  */
  231. static int i8k_get_cpu_temp(void)
  232. {
  233.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  234.     int rc;
  235.     int temp;
  236. #ifdef I8K_TEMPERATURE_BUG
  237.     static int prev = 0;
  238. #endif
  239.     regs.eax = I8K_SMM_GET_TEMP;
  240.     if ((rc=i8k_smm(&regs)) < 0) {
  241. return rc;
  242.     }
  243.     temp = regs.eax & 0xff;
  244. #ifdef I8K_TEMPERATURE_BUG
  245.     /*
  246.      * Sometimes the temperature sensor returns 0x99, which is out of range.
  247.      * In this case we return (once) the previous cached value. For example:
  248.      # 1003655137 00000058 00005a4b
  249.      # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
  250.      # 1003655139 00000054 00005c52
  251.      */
  252.     if (temp > I8K_MAX_TEMP) {
  253. temp = prev;
  254. prev = I8K_MAX_TEMP;
  255.     } else {
  256. prev = temp;
  257.     }
  258. #endif
  259.     return temp;
  260. }
  261. static int i8k_get_dell_signature(void)
  262. {
  263.     SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
  264.     int rc;
  265.     regs.eax = I8K_SMM_GET_DELL_SIG;
  266.     if ((rc=i8k_smm(&regs)) < 0) {
  267. return rc;
  268.     }
  269.     if ((regs.eax == 1145651527) && (regs.edx == 1145392204)) {
  270. return 0;
  271.     } else {
  272. return -1;
  273.     }
  274. }
  275. static int i8k_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
  276.      unsigned long arg)
  277. {
  278.     int val;
  279.     int speed;
  280.     unsigned char buff[16];
  281.     if (!arg) {
  282. return -EINVAL;
  283.     }
  284.     switch (cmd) {
  285.     case I8K_BIOS_VERSION:
  286. val = i8k_get_bios_version();
  287. break;
  288.     case I8K_MACHINE_ID:
  289. memset(buff, 0, 16);
  290. val = i8k_get_serial_number(buff);
  291. break;
  292.     case I8K_FN_STATUS:
  293. val = i8k_get_fn_status();
  294. break;
  295.     case I8K_POWER_STATUS:
  296. val = i8k_get_power_status();
  297. break;
  298.     case I8K_GET_TEMP:
  299. val = i8k_get_cpu_temp();
  300. break;
  301.     case I8K_GET_SPEED:
  302. if (copy_from_user(&val, (int *)arg, sizeof(int))) {
  303.     return -EFAULT;
  304. }
  305. val = i8k_get_fan_speed(val);
  306. break;
  307.     case I8K_GET_FAN:
  308. if (copy_from_user(&val, (int *)arg, sizeof(int))) {
  309.     return -EFAULT;
  310. }
  311. val = i8k_get_fan_status(val);
  312. break;
  313.     case I8K_SET_FAN:
  314. if (copy_from_user(&val, (int *)arg, sizeof(int))) {
  315.     return -EFAULT;
  316. }
  317. if (copy_from_user(&speed, (int *)arg+1, sizeof(int))) {
  318.     return -EFAULT;
  319. }
  320. val = i8k_set_fan(val, speed);
  321. break;
  322.     default:
  323. return -EINVAL;
  324.     }
  325.     if (val < 0) {
  326. return val;
  327.     }
  328.     switch (cmd) {
  329.     case I8K_BIOS_VERSION:
  330. if (copy_to_user((int *)arg, &val, 4)) {
  331.     return -EFAULT;
  332. }
  333. break;
  334.     case I8K_MACHINE_ID:
  335. if (copy_to_user((int *)arg, buff, 16)) {
  336.     return -EFAULT;
  337. }
  338. break;
  339.     default:
  340. if (copy_to_user((int *)arg, &val, sizeof(int))) {
  341.     return -EFAULT;
  342. }
  343. break;
  344.     }
  345.     return 0;
  346. }
  347. /*
  348.  * Print the information for /proc/i8k.
  349.  */
  350. static int i8k_get_info(char *buffer, char **start, off_t fpos, int length)
  351. {
  352.     int n, fn_key, cpu_temp, ac_power;
  353.     int left_fan, right_fan, left_speed, right_speed;
  354.     cpu_temp     = i8k_get_cpu_temp(); /* 11100 祍 */
  355.     left_fan     = i8k_get_fan_status(I8K_FAN_LEFT); /*   580 祍 */
  356.     right_fan    = i8k_get_fan_status(I8K_FAN_RIGHT); /*   580 祍 */
  357.     left_speed   = i8k_get_fan_speed(I8K_FAN_LEFT); /*   580 祍 */
  358.     right_speed  = i8k_get_fan_speed(I8K_FAN_RIGHT); /*   580 祍 */
  359.     fn_key       = i8k_get_fn_status(); /*   750 祍 */
  360.     if (power_status) {
  361. ac_power = i8k_get_power_status(); /* 14700 祍 */
  362.     } else {
  363. ac_power = -1;
  364.     }
  365.     /*
  366.      * Info:
  367.      *
  368.      * 1)  Format version (this will change if format changes)
  369.      * 2)  BIOS version
  370.      * 3)  BIOS machine ID
  371.      * 4)  Cpu temperature
  372.      * 5)  Left fan status
  373.      * 6)  Right fan status
  374.      * 7)  Left fan speed
  375.      * 8)  Right fan speed
  376.      * 9)  AC power
  377.      * 10) Fn Key status
  378.      */
  379.     n = sprintf(buffer, "%s %s %s %d %d %d %d %d %d %dn",
  380. I8K_PROC_FMT,
  381. bios_version,
  382. serial_number,
  383. cpu_temp,
  384. left_fan,
  385. right_fan,
  386. left_speed,
  387. right_speed,
  388. ac_power,
  389. fn_key);
  390.     return n;
  391. }
  392. static ssize_t i8k_read(struct file *f, char *buffer, size_t len, loff_t *fpos)
  393. {
  394.     int n;
  395.     char info[128];
  396.     n = i8k_get_info(info, NULL, 0, 128);
  397.     if (n <= 0) {
  398. return n;
  399.     }
  400.     if (*fpos >= n) {
  401. return 0;
  402.     }
  403.     if ((*fpos + len) >= n) {
  404. len = n - *fpos;
  405.     }
  406.     if (copy_to_user(buffer, info, len) != 0) {
  407. return -EFAULT;
  408.     }
  409.     *fpos += len;
  410.     return len;
  411. }
  412. static char* __init string_trim(char *s, int size)
  413. {
  414.     int len;
  415.     char *p;
  416.     if ((len = strlen(s)) > size) {
  417. len = size;
  418.     }
  419.     for (p=s+len-1; len && (*p==' '); len--,p--) {
  420. *p = '';
  421.     }
  422.     return s;
  423. }
  424. /* DMI code, stolen from arch/i386/kernel/dmi_scan.c */
  425. /*
  426.  * |<-- dmi->length -->|
  427.  * |                   |
  428.  * |dmi header    s=N  | string1,, ..., stringN,, ..., 
  429.  *                |                       |
  430.  *                +-----------------------+
  431.  */
  432. static char* __init dmi_string(DMIHeader *dmi, u8 s)
  433. {
  434.     u8 *p;
  435.     if (!s) {
  436. return "";
  437.     }
  438.     s--;
  439.     p = (u8 *)dmi + dmi->length;
  440.     while (s > 0) {
  441. p += strlen(p);
  442. p++;
  443. s--;
  444.     }
  445.     return p;
  446. }
  447. static void __init dmi_decode(DMIHeader *dmi)
  448. {
  449.     u8 *data = (u8 *) dmi;
  450.     char *p;
  451. #ifdef I8K_DEBUG
  452.     int i;
  453.     printk("%08x ", (int)data);
  454.     for (i=0; i<data[1] && i<64; i++) {
  455. printk("%02x ", data[i]);
  456.     }
  457.     printk("n");
  458. #endif
  459.     switch (dmi->type) {
  460.     case  0: /* BIOS Information */
  461. p = dmi_string(dmi,data[5]);
  462. if (*p) {
  463.     strncpy(bios_version, p, sizeof(bios_version));
  464.     string_trim(bios_version, sizeof(bios_version));
  465. }
  466. break;
  467.     case 1: /* System Information */
  468. p = dmi_string(dmi,data[4]);
  469. if (*p) {
  470.     strncpy(system_vendor, p, sizeof(system_vendor));
  471.     string_trim(system_vendor, sizeof(system_vendor));
  472. }
  473. p = dmi_string(dmi,data[5]);
  474. if (*p) {
  475.     strncpy(product_name, p, sizeof(product_name));
  476.     string_trim(product_name, sizeof(product_name));
  477. }
  478. p = dmi_string(dmi,data[7]);
  479. if (*p) {
  480.     strncpy(serial_number, p, sizeof(serial_number));
  481.     string_trim(serial_number, sizeof(serial_number));
  482. }
  483. break;
  484.     }
  485. }
  486. static int __init dmi_table(u32 base, int len, int num, void (*fn)(DMIHeader*))
  487. {
  488.     u8 *buf;
  489.     u8 *data;
  490.     DMIHeader *dmi;
  491.     int i = 1;
  492.     buf = ioremap(base, len);
  493.     if (buf == NULL) {
  494. return -1;
  495.     }
  496.     data = buf;
  497.     /*
  498.      * Stop when we see al the items the table claimed to have
  499.      * or we run off the end of the table (also happens)
  500.      */
  501.     while ((i<num) && ((data-buf) < len)) {
  502. dmi = (DMIHeader *)data;
  503. /*
  504.  * Avoid misparsing crud if the length of the last
  505.  * record is crap
  506.  */
  507. if ((data-buf+dmi->length) >= len) {
  508.     break;
  509. }
  510. fn(dmi);
  511. data += dmi->length;
  512. /*
  513.  * Don't go off the end of the data if there is
  514.  * stuff looking like string fill past the end
  515.  */
  516. while (((data-buf) < len) && (*data || data[1])) {
  517.     data++;
  518. }
  519. data += 2;
  520. i++;
  521.     }
  522.     iounmap(buf);
  523.     return 0;
  524. }
  525. static int __init dmi_iterate(void (*decode)(DMIHeader *))
  526. {
  527.     unsigned char buf[20];
  528.     long fp = 0x000e0000L;
  529.     fp -= 16;
  530.     while (fp < 0x000fffffL) {
  531. fp += 16;
  532. isa_memcpy_fromio(buf, fp, 20);
  533. if (memcmp(buf, "_DMI_", 5)==0) {
  534.     u16 num  = buf[13]<<8  | buf[12];
  535.     u16 len  = buf [7]<<8  | buf [6];
  536.     u32 base = buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8];
  537. #ifdef I8K_DEBUG
  538.     printk(KERN_INFO "DMI %d.%d present.n",
  539.    buf[14]>>4, buf[14]&0x0F);
  540.     printk(KERN_INFO "%d structures occupying %d bytes.n",
  541.    buf[13]<<8 | buf[12],
  542.    buf [7]<<8 | buf[6]);
  543.     printk(KERN_INFO "DMI table at 0x%08X.n",
  544.    buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8]);
  545. #endif
  546.     if (dmi_table(base, len, num, decode)==0) {
  547. return 0;
  548.     }
  549. }
  550.     }
  551.     return -1;
  552. }
  553. /* end of DMI code */
  554. /*
  555.  * Get DMI information.
  556.  */
  557. static int __init i8k_dmi_probe(void)
  558. {
  559.     char **p;
  560.     if (dmi_iterate(dmi_decode) != 0) {
  561. printk(KERN_INFO "i8k: unable to get DMI informationn");
  562. return -ENODEV;
  563.     }
  564.     if (strncmp(system_vendor,DELL_SIGNATURE,strlen(DELL_SIGNATURE)) != 0) {
  565. printk(KERN_INFO "i8k: not running on a Dell systemn");
  566. return -ENODEV;
  567.     }
  568.     for (p=supported_models; ; p++) {
  569. if (!*p) {
  570.     printk(KERN_INFO "i8k: unsupported model: %sn", product_name);
  571.     return -ENODEV;
  572. }
  573. if (strncmp(product_name,*p,strlen(*p)) == 0) {
  574.     break;
  575. }
  576.     }
  577.     return 0;
  578. }
  579. /*
  580.  * Probe for the presence of a supported laptop.
  581.  */
  582. static int __init i8k_probe(void)
  583. {
  584.     char buff[4];
  585.     int version;
  586.     int smm_found = 0;
  587.     /*
  588.      * Get DMI information
  589.      */
  590.     if (i8k_dmi_probe() != 0) {
  591. printk(KERN_INFO "i8k: vendor=%s, model=%s, version=%sn",
  592.        system_vendor, product_name, bios_version);
  593.     }
  594.     /*
  595.      * Get SMM Dell signature
  596.      */
  597.     if (i8k_get_dell_signature() != 0) {
  598. printk(KERN_INFO "i8k: unable to get SMM Dell signaturen");
  599.     } else {
  600. smm_found = 1;
  601.     }
  602.     /*
  603.      * Get SMM BIOS version.
  604.      */
  605.     version = i8k_get_bios_version();
  606.     if (version <= 0) {
  607. printk(KERN_INFO "i8k: unable to get SMM BIOS versionn");
  608.     } else {
  609. smm_found = 1;
  610. buff[0] = (version >> 16) & 0xff;
  611. buff[1] = (version >>  8) & 0xff;
  612. buff[2] = (version)       & 0xff;
  613. buff[3] = '';
  614. /*
  615.  * If DMI BIOS version is unknown use SMM BIOS version.
  616.  */
  617. if (bios_version[0] == '?') {
  618.     strcpy(bios_version, buff);
  619. }
  620. /*
  621.  * Check if the two versions match.
  622.  */
  623. if (strncmp(buff,bios_version,sizeof(bios_version)) != 0) {
  624.     printk(KERN_INFO "i8k: BIOS version mismatch: %s != %sn",
  625.    buff, bios_version);
  626. }
  627.     }
  628.     if (!smm_found && !force) {
  629. return -ENODEV;
  630.     }
  631.     return 0;
  632. }
  633. #ifdef MODULE
  634. static
  635. #endif
  636. int __init i8k_init(void)
  637. {
  638.     struct proc_dir_entry *proc_i8k;
  639.     /* Are we running on an supported laptop? */
  640.     if (i8k_probe() != 0) {
  641. return -ENODEV;
  642.     }
  643.     /* Register the proc entry */
  644.     proc_i8k = create_proc_info_entry("i8k", 0, NULL, i8k_get_info);
  645.     if (!proc_i8k) {
  646. return -ENOENT;
  647.     }
  648.     proc_i8k->proc_fops = &i8k_fops;
  649.     SET_MODULE_OWNER(proc_i8k);
  650.     printk(KERN_INFO
  651.    "Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)n",
  652.    I8K_VERSION);
  653.     return 0;
  654. }
  655. #ifdef MODULE
  656. int init_module(void)
  657. {
  658.     return i8k_init();
  659. }
  660. void cleanup_module(void)
  661. {
  662.     /* Remove the proc entry */
  663.     remove_proc_entry("i8k", NULL);
  664.     printk(KERN_INFO "i8k: module unloadedn");
  665. }
  666. #endif
  667. /* end of file */