[PATCH] latest version of i8k driver

From: Massimo Dal Zotto (dz@cs.unitn.it)
Date: Mon Nov 26 2001 - 06:11:29 EST


Hi,

here is the latest version of the i8k driver. I have changed the i8k_probe
again and now it loads cleanly on most Dell Inspiron and Latitude laptops.
I have also disabled by default the power status since this information is
already available from apm. The patch is against linux-2.4.15.

--- linux-2.4.15.orig/Documentation/Configure.help Mon Nov 26 11:15:22 2001
+++ linux/Documentation/Configure.help Sat Nov 24 13:31:04 2001
@@ -17321,23 +17307,24 @@
   Say Y if you intend to run this kernel on a Toshiba portable.
   Say N otherwise.
 
-Dell Inspiron 8000 support
+Dell laptop support
 CONFIG_I8K
   This adds a driver to safely access the System Management Mode
- of the CPU on the Dell Inspiron 8000. The System Management Mode
- is used to read cpu temperature and cooling fan status and to
- control the fans on the I8K portables.
-
- This driver has been tested only on the Inspiron 8000 but it may
- also work with other Dell laptops. You can force loading on other
- models by passing the parameter `force=1' to the module. Use at
- your own risk.
-
- For information on utilities to make use of this driver see the
- I8K Linux utilities web site at:
- <http://www.debian.org/~dz/i8k/>
+ of the CPU on the Dell Inspiron and Latitude laptops. The System
+ Management Mode is used to read cpu temperature, cooling fan
+ status and Fn-keys status on Dell laptops. It can also be used
+ to switch the fans on and off.
+
+ The driver has been developed and tested on an Inspiron 8000
+ but it should work on any Dell Inspiron or Latitude laptop.
+ You can force loading on unsupported models by passing the
+ parameter `force=1' to the module. Use at your own risk.
+
+ For more information on this driver and for utilities that make
+ use of the module see the I8K Linux Utilities web site at:
+ <http://www.debian.org/~dz/i8k/>.
 
- Say Y if you intend to run this kernel on a Dell Inspiron 8000.
+ Say Y if you intend to run this kernel on a Dell laptop.
   Say N otherwise.
 
 /dev/cpu/microcode - Intel IA32 CPU microcode support
--- linux-2.4.15.orig/MAINTAINERS Mon Nov 26 11:15:22 2001
+++ linux/MAINTAINERS Mon Nov 26 11:29:20 2001
@@ -404,6 +404,12 @@
 L: linux-decnet-user@lists.sourceforge.net
 S: Maintained
 
+DELL LAPTOP SMM DRIVER
+P: Massimo Dal Zotto
+M: dz@debian.org
+W: http://www.debian.org/~dz/i8k/
+S: Maintained
+
 DEVICE NUMBER REGISTRY
 P: H. Peter Anvin
 M: hpa@zytor.com
--- linux-2.4.15.orig/arch/i386/config.in Mon Nov 26 11:15:23 2001
+++ linux/arch/i386/config.in Sat Nov 24 13:41:12 2001
@@ -149,7 +149,7 @@
    define_bool CONFIG_X86_USE_PPRO_CHECKSUM y
 fi
 tristate 'Toshiba Laptop support' CONFIG_TOSHIBA
-tristate 'Dell Inspiron 8000 support' CONFIG_I8K
+tristate 'Dell laptop support' CONFIG_I8K
 
 tristate '/dev/cpu/microcode - Intel IA32 CPU microcode support' CONFIG_MICROCODE
 tristate '/dev/cpu/*/msr - Model-specific register support' CONFIG_X86_MSR
--- linux-2.4.15.orig/drivers/char/i8k.c Sat Nov 3 02:46:47 2001
+++ linux/drivers/char/i8k.c Sat Nov 24 11:43:26 2001
@@ -1,5 +1,7 @@
 /*
- * i8k.c -- Linux driver for accessing the SMM BIOS on Dell I8000 laptops
+ * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
+ * See http://www.debian.org/~dz/i8k/ for more information
+ * and for latest version of this driver.
  *
  * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org>
  *
@@ -19,15 +21,13 @@
 #include <linux/types.h>
 #include <linux/init.h>
 #include <linux/proc_fs.h>
-#include <asm/io.h>
+#include <linux/apm_bios.h>
 #include <asm/uaccess.h>
+#include <asm/io.h>
 
 #include <linux/i8k.h>
 
-#define I8K_VERSION "1.1 02/11/2001"
-#define I8K_BIOS_SIGNATURE "Dell System Inspiron 8000"
-#define I8K_BIOS_SIGNATURE_ADDR 0x000ec000
-#define I8K_BIOS_VERSION_OFFSET 32
+#define I8K_VERSION "1.7 21/11/2001"
 
 #define I8K_SMM_FN_STATUS 0x0025
 #define I8K_SMM_POWER_STATUS 0x0069
@@ -35,31 +35,47 @@
 #define I8K_SMM_GET_FAN 0x00a3
 #define I8K_SMM_GET_SPEED 0x02a3
 #define I8K_SMM_GET_TEMP 0x10a3
+#define I8K_SMM_GET_DELL_SIG 0xffa3
 #define I8K_SMM_BIOS_VERSION 0x00a6
 
 #define I8K_FAN_MULT 30
 #define I8K_MAX_TEMP 127
 
-#define I8K_FN_NONE 0x08
-#define I8K_FN_UP 0x09
-#define I8K_FN_DOWN 0x0a
-#define I8K_FN_MUTE 0x0c
+#define I8K_FN_NONE 0x00
+#define I8K_FN_UP 0x01
+#define I8K_FN_DOWN 0x02
+#define I8K_FN_MUTE 0x04
+#define I8K_FN_MASK 0x07
+#define I8K_FN_SHIFT 8
 
 #define I8K_POWER_AC 0x05
 #define I8K_POWER_BATTERY 0x01
 
 #define I8K_TEMPERATURE_BUG 1
 
-static char bios_version[4] = "?";
-static char bios_machine_id[16] = "?";
+#define DELL_SIGNATURE "Dell Computer"
+
+static char *supported_models[] = {
+ "Inspiron",
+ "Latitude",
+ NULL
+};
+
+static char system_vendor[48] = "?";
+static char product_name [48] = "?";
+static char bios_version [4] = "?";
+static char serial_number[16] = "?";
 
 int force = 0;
+int power_status = 0;
 
 MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
-MODULE_DESCRIPTION("Driver for accessing the SMM BIOS on Dell I8000 laptops");
+MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
 MODULE_LICENSE("GPL");
 MODULE_PARM(force, "i");
-MODULE_PARM_DESC(force, "Force loading without checking for an Inspiron 8000");
+MODULE_PARM(power_status, "i");
+MODULE_PARM_DESC(force, "Force loading without checking for supported models");
+MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
 
 static ssize_t i8k_read(struct file *, char *, size_t, loff_t *);
 static int i8k_ioctl(struct inode *, struct file *, unsigned int,
@@ -79,12 +95,19 @@
     unsigned int edi __attribute__ ((packed));
 } SMMRegisters;
 
+typedef struct {
+ u8 type;
+ u8 length;
+ u16 handle;
+} DMIHeader;
+
 /*
- * Call the System Management Mode BIOS.
+ * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
  */
 static int i8k_smm(SMMRegisters *regs)
 {
     int rc;
+ int eax = regs->eax;
 
     asm("pushl %%eax\n\t" \
         "movl 0(%%eax),%%edx\n\t" \
@@ -112,7 +135,7 @@
         : "a" (regs)
         : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
 
- if ((rc != 0) || ((regs->eax & 0xffff) == 0xffff)) {
+ if ((rc != 0) || ((regs->eax & 0xffff) == 0xffff) || (regs->eax == eax)) {
         return -EINVAL;
     }
 
@@ -137,11 +160,12 @@
 }
 
 /*
- * Read the machine id. Not yet implemented.
+ * Read the machine id.
  */
-static int i8k_get_machine_id(unsigned char *buff)
+static int i8k_get_serial_number(unsigned char *buff)
 {
- return -EINVAL;
+ strncpy(buff, serial_number, 16);
+ return 0;
 }
 
 /*
@@ -157,7 +181,7 @@
         return rc;
     }
 
- switch ((regs.eax & 0xff00) >> 8) {
+ switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
     case I8K_FN_UP:
         return I8K_VOL_UP;
     case I8K_FN_DOWN:
@@ -281,6 +305,23 @@
     return temp;
 }
 
+static int i8k_get_dell_signature(void)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ regs.eax = I8K_SMM_GET_DELL_SIG;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ if ((regs.eax == 1145651527) && (regs.edx == 1145392204)) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
 static int i8k_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
                      unsigned long arg)
 {
@@ -299,7 +340,7 @@
 
     case I8K_MACHINE_ID:
         memset(buff, 0, 16);
- val = i8k_get_machine_id(buff);
+ val = i8k_get_serial_number(buff);
         break;
 
     case I8K_FN_STATUS:
@@ -375,13 +416,17 @@
     int n, fn_key, cpu_temp, ac_power;
     int left_fan, right_fan, left_speed, right_speed;
 
- cpu_temp = i8k_get_cpu_temp();
- left_fan = i8k_get_fan_status(I8K_FAN_LEFT);
- right_fan = i8k_get_fan_status(I8K_FAN_RIGHT);
- left_speed = i8k_get_fan_speed(I8K_FAN_LEFT);
- right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT);
- ac_power = i8k_get_power_status();
- fn_key = i8k_get_fn_status();
+ cpu_temp = i8k_get_cpu_temp(); /* 11100 µs */
+ left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */
+ right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */
+ left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */
+ right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */
+ fn_key = i8k_get_fn_status(); /* 750 µs */
+ if (power_status) {
+ ac_power = i8k_get_power_status(); /* 14700 µs */
+ } else {
+ ac_power = -1;
+ }
 
     /*
      * Info:
@@ -400,7 +445,7 @@
     n = sprintf(buffer, "%s %s %s %d %d %d %d %d %d %d\n",
                 I8K_PROC_FMT,
                 bios_version,
- bios_machine_id,
+ serial_number,
                 cpu_temp,
                 left_fan,
                 right_fan,
@@ -438,76 +483,253 @@
     return len;
 }
 
+static char* __init string_trim(char *s, int size)
+{
+ int len;
+ char *p;
+
+ if ((len = strlen(s)) > size) {
+ len = size;
+ }
+
+ for (p=s+len-1; len && (*p==' '); len--,p--) {
+ *p = '\0';
+ }
+
+ return s;
+}
+
+/* DMI code, stolen from arch/i386/kernel/dmi_scan.c */
+
 /*
- * Probe for the presence of an Inspiron I8000.
+ * |<-- dmi->length -->|
+ * | |
+ * |dmi header s=N | string1,\0, ..., stringN,\0, ..., \0
+ * | |
+ * +-----------------------+
  */
-static int i8k_probe(void)
+static char* __init dmi_string(DMIHeader *dmi, u8 s)
 {
- unsigned char *buff, *p;
- unsigned char bios_vers[4];
- int version;
+ u8 *p;
+
+ if (!s) {
+ return "";
+ }
+ s--;
+
+ p = (u8 *)dmi + dmi->length;
+ while (s > 0) {
+ p += strlen(p);
+ p++;
+ s--;
+ }
+
+ return p;
+}
+
+static void __init dmi_decode(DMIHeader *dmi)
+{
+ u8 *data = (u8 *) dmi;
+ char *p;
+
+#ifdef I8K_DEBUG
+ int i;
+ printk("%08x ", (int)data);
+ for (i=0; i<data[1] && i<64; i++) {
+ printk("%02x ", data[i]);
+ }
+ printk("\n");
+#endif
+
+ switch (dmi->type) {
+ case 0: /* BIOS Information */
+ p = dmi_string(dmi,data[5]);
+ if (*p) {
+ strncpy(bios_version, p, sizeof(bios_version));
+ string_trim(bios_version, sizeof(bios_version));
+ }
+ break;
+ case 1: /* System Information */
+ p = dmi_string(dmi,data[4]);
+ if (*p) {
+ strncpy(system_vendor, p, sizeof(system_vendor));
+ string_trim(system_vendor, sizeof(system_vendor));
+ }
+ p = dmi_string(dmi,data[5]);
+ if (*p) {
+ strncpy(product_name, p, sizeof(product_name));
+ string_trim(product_name, sizeof(product_name));
+ }
+ p = dmi_string(dmi,data[7]);
+ if (*p) {
+ strncpy(serial_number, p, sizeof(serial_number));
+ string_trim(serial_number, sizeof(serial_number));
+ }
+ break;
+ }
+}
+
+static int __init dmi_table(u32 base, int len, int num, void (*fn)(DMIHeader*))
+{
+ u8 *buf;
+ u8 *data;
+ DMIHeader *dmi;
+ int i = 1;
+
+ buf = ioremap(base, len);
+ if (buf == NULL) {
+ return -1;
+ }
+ data = buf;
 
     /*
- * Until Dell tell us how to reliably check for an Inspiron system
- * look for a signature at a fixed location in the BIOS memory.
- * Ugly but safe.
+ * Stop when we see al the items the table claimed to have
+ * or we run off the end of the table (also happens)
      */
- if (!force) {
- buff = ioremap(I8K_BIOS_SIGNATURE_ADDR, I8K_BIOS_VERSION_OFFSET+4);
- if (buff == NULL) {
- printk("i8k: ioremap failed\n");
- return -ENODEV;
+ while ((i<num) && ((data-buf) < len)) {
+ dmi = (DMIHeader *)data;
+ /*
+ * Avoid misparsing crud if the length of the last
+ * record is crap
+ */
+ if ((data-buf+dmi->length) >= len) {
+ break;
+ }
+ fn(dmi);
+ data += dmi->length;
+ /*
+ * Don't go off the end of the data if there is
+ * stuff looking like string fill past the end
+ */
+ while (((data-buf) < len) && (*data || data[1])) {
+ data++;
         }
- if (strncmp(buff,I8K_BIOS_SIGNATURE,sizeof(I8K_BIOS_SIGNATURE)) != 0) {
- printk("i8k: Inspiron 8000 BIOS signature not found\n");
- iounmap(buff);
- return -ENODEV;
+ data += 2;
+ i++;
+ }
+ iounmap(buf);
+
+ return 0;
+}
+
+static int __init dmi_iterate(void (*decode)(DMIHeader *))
+{
+ unsigned char buf[20];
+ long fp = 0x000e0000L;
+ fp -= 16;
+
+ while (fp < 0x000fffffL) {
+ fp += 16;
+ isa_memcpy_fromio(buf, fp, 20);
+ if (memcmp(buf, "_DMI_", 5)==0) {
+ u16 num = buf[13]<<8 | buf[12];
+ u16 len = buf [7]<<8 | buf [6];
+ u32 base = buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8];
+#ifdef I8K_DEBUG
+ printk(KERN_INFO "DMI %d.%d present.\n",
+ buf[14]>>4, buf[14]&0x0F);
+ printk(KERN_INFO "%d structures occupying %d bytes.\n",
+ buf[13]<<8 | buf[12],
+ buf [7]<<8 | buf[6]);
+ printk(KERN_INFO "DMI table at 0x%08X.\n",
+ buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8]);
+#endif
+ if (dmi_table(base, len, num, decode)==0) {
+ return 0;
+ }
         }
- strncpy(bios_vers, buff+I8K_BIOS_VERSION_OFFSET, 3);
- bios_vers[3] = '\0';
- iounmap(buff);
- }
- if (force >= 2) {
- buff = ioremap(0x000c0000, 0x00100000-0x000c0000);
- if (buff == NULL) {
- printk("i8k: ioremap failed\n");
+ }
+ return -1;
+}
+/* end of DMI code */
+
+/*
+ * Get DMI information.
+ */
+static int __init i8k_dmi_probe(void)
+{
+ char **p;
+
+ if (dmi_iterate(dmi_decode) != 0) {
+ printk(KERN_INFO "i8k: unable to get DMI information\n");
+ return -ENODEV;
+ }
+
+ if (strncmp(system_vendor,DELL_SIGNATURE,strlen(DELL_SIGNATURE)) != 0) {
+ printk(KERN_INFO "i8k: not running on a Dell system\n");
+ return -ENODEV;
+ }
+
+ for (p=supported_models; ; p++) {
+ if (!*p) {
+ printk(KERN_INFO "i8k: unsupported model: %s\n", product_name);
             return -ENODEV;
         }
- for (p=buff; (p-buff)<(0x00100000-0x000c0000); p+=16) {
- if (strncmp(p,I8K_BIOS_SIGNATURE,sizeof(I8K_BIOS_SIGNATURE))==0) {
- printk("i8k: Inspiron 8000 BIOS signature found at %08x\n",
- 0x000c0000+(p-buff));
- break;
- }
+ if (strncmp(product_name,*p,strlen(*p)) == 0) {
+ break;
         }
- iounmap(buff);
     }
 
+ return 0;
+}
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static int __init i8k_probe(void)
+{
+ char buff[4];
+ int version;
+ int smm_found = 0;
+
     /*
- * Next try to get the BIOS version with an SMM call. If this
- * fails SMM can't be reliably used on this system.
+ * Get DMI information
      */
- version = i8k_get_bios_version();
- if (version <= 0) {
- printk("i8k: unable to get BIOS version\n");
- return -ENODEV;
+ if (i8k_dmi_probe() != 0) {
+ printk(KERN_INFO "i8k: vendor=%s, model=%s, version=%s\n",
+ system_vendor, product_name, bios_version);
     }
- bios_version[0] = (version >> 16) & 0xff;
- bios_version[1] = (version >> 8) & 0xff;
- bios_version[2] = (version) & 0xff;
- bios_version[3] = '\0';
 
     /*
- * Finally check if the two versions match.
+ * Get SMM Dell signature
      */
- if (!force) {
- if (strncmp(bios_version,bios_vers,sizeof(bios_version)) != 0) {
- printk("i8k: BIOS version mismatch: %s != %s\n",
- bios_version, bios_vers);
- return -ENODEV;
+ if (i8k_get_dell_signature() != 0) {
+ printk(KERN_INFO "i8k: unable to get SMM Dell signature\n");
+ } else {
+ smm_found = 1;
+ }
+
+ /*
+ * Get SMM BIOS version.
+ */
+ version = i8k_get_bios_version();
+ if (version <= 0) {
+ printk(KERN_INFO "i8k: unable to get SMM BIOS version\n");
+ } else {
+ smm_found = 1;
+ buff[0] = (version >> 16) & 0xff;
+ buff[1] = (version >> 8) & 0xff;
+ buff[2] = (version) & 0xff;
+ buff[3] = '\0';
+ /*
+ * If DMI BIOS version is unknown use SMM BIOS version.
+ */
+ if (bios_version[0] == '?') {
+ strcpy(bios_version, buff);
+ }
+ /*
+ * Check if the two versions match.
+ */
+ if (strncmp(buff,bios_version,sizeof(bios_version)) != 0) {
+ printk(KERN_INFO "i8k: BIOS version mismatch: %s != %s\n",
+ buff, bios_version);
         }
     }
 
+ if (!smm_found && !force) {
+ return -ENODEV;
+ }
+
     return 0;
 }
 
@@ -518,7 +740,7 @@
 {
     struct proc_dir_entry *proc_i8k;
 
- /* Are we running on an Inspiron 8000 laptop? */
+ /* Are we running on an supported laptop? */
     if (i8k_probe() != 0) {
         return -ENODEV;
     }
@@ -532,7 +754,7 @@
     SET_MODULE_OWNER(proc_i8k);
 
     printk(KERN_INFO
- "Inspiron 8000 SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
+ "Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
            I8K_VERSION);
 
     return 0;
--- linux-2.4.15.orig/include/linux/i8k.h Sat Nov 3 02:46:47 2001
+++ linux/include/linux/i8k.h Sat Nov 24 11:43:26 2001
@@ -1,5 +1,5 @@
 /*
- * i8k.h -- Linux driver for accessing the SMM BIOS on Dell I8000 laptops
+ * i8k.h -- Linux driver for accessing the SMM BIOS on Dell laptops
  *
  * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org>
  *
@@ -36,9 +36,9 @@
 #define I8K_FAN_HIGH 2
 #define I8K_FAN_MAX I8K_FAN_HIGH
 
-#define I8K_VOL_UP 0x01
-#define I8K_VOL_DOWN 0x02
-#define I8K_VOL_MUTE 0x03
+#define I8K_VOL_UP 1
+#define I8K_VOL_DOWN 2
+#define I8K_VOL_MUTE 4
 
 #define I8K_AC 1
 #define I8K_BATTERY 0

-- 
Massimo Dal Zotto

+----------------------------------------------------------------------+ | Massimo Dal Zotto email: dz@debian.org | | Via Marconi, 141 phone: ++39-461534251 | | 38057 Pergine Valsugana (TN) www: http://www.cs.unitn.it/~dz/ | | Italy http://www.debian.org/~dz/ | | gpg: 2DB65596 3CED BDC6 4F23 BEDA F489 2445 147F 1AEA 2DB6 5596 | +----------------------------------------------------------------------+ - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Fri Nov 30 2001 - 21:00:21 EST