From: Corey Minyard <cminy...@mvista.com>

It makes more sense to be there, and it's cleaner with the
upcoming conversion of IPMI DMI to a platform device.

Signed-off-by: Corey Minyard <cminy...@mvista.com>
Cc: Jean Delvare <jdelv...@suse.de>
Cc: Andy Lutomirski <l...@kernel.org>
Tested-by: Andy Lutomirski <l...@kernel.org>
---
 drivers/firmware/dmi_scan.c | 108 ++++++++++++++++++++++++++++++++++++++++++--
 include/linux/dmi.h         |  26 +++++++++++
 2 files changed, 129 insertions(+), 5 deletions(-)

diff --git a/drivers/firmware/dmi_scan.c b/drivers/firmware/dmi_scan.c
index 5519b4f..16e5174 100644
--- a/drivers/firmware/dmi_scan.c
+++ b/drivers/firmware/dmi_scan.c
@@ -303,9 +303,105 @@ static void __init dmi_save_oem_strings_devices(const 
struct dmi_header *dm)
        }
 }
 
+#define DMI_IPMI_MIN_LENGTH    0x10
+#define DMI_IPMI_VER2_LENGTH   0x12
+#define DMI_IPMI_TYPE          4
+#define DMI_IPMI_SLAVEADDR     6
+#define DMI_IPMI_ADDR          8
+#define DMI_IPMI_ACCESS                0x10
+#define DMI_IPMI_IRQ           0x11
+#define DMI_IPMI_IO_MASK       0xfffe
+
+static void __init dmi_decode_ipmi(struct dmi_dev_ipmi *dev,
+                                  const struct dmi_header *dm)
+{
+       const u8        *data = (const u8 *) dm;
+       unsigned long   base_addr;
+       u8              len = dm->length;
+       bool            slave_addr_set;
+
+       if (len < DMI_IPMI_MIN_LENGTH)
+               return;
+
+       dev->type = data[DMI_IPMI_TYPE];
+
+       memcpy(&base_addr, data + DMI_IPMI_ADDR, sizeof(unsigned long));
+       if (len >= DMI_IPMI_VER2_LENGTH) {
+               if (dev->type == IPMI_DMI_TYPE_SSIF) {
+                       if ((data[DMI_IPMI_ADDR] >> 1) == 0) {
+                               /*
+                                * Some broken systems put the I2C address in
+                                * the slave address field.  We try to
+                                * accommodate them here.
+                                */
+                               dev->base_addr = data[DMI_IPMI_SLAVEADDR] >> 1;
+                               dev->slave_addr = 0;
+                               slave_addr_set = true;
+                       } else {
+                               dev->base_addr = data[DMI_IPMI_ADDR] >> 1;
+                       }
+               } else {
+                       if (base_addr & 1) {
+                               /* I/O */
+                               base_addr &= DMI_IPMI_IO_MASK;
+                               dev->is_io_space = 1;
+                       } else {
+                               /* Memory */
+                               dev->is_io_space = 0;
+                       }
+
+                       /*
+                        * If bit 4 of byte 0x10 is set, then the lsb
+                        * for the address is odd.
+                        */
+                       base_addr |= (data[DMI_IPMI_ACCESS] >> 4) & 1;
+
+                       dev->base_addr = base_addr;
+                       dev->irq = data[DMI_IPMI_IRQ];
+
+                       /*
+                        * The top two bits of byte 0x10 hold the
+                        * register spacing.
+                        */
+                       switch ((data[DMI_IPMI_ACCESS] >> 6) & 3) {
+                       case 0: /* Byte boundaries */
+                               dev->offset = 1;
+                               break;
+                       case 1: /* 32-bit boundaries */
+                               dev->offset = 4;
+                               break;
+                       case 2: /* 16-byte boundaries */
+                               dev->offset = 16;
+                               break;
+                       default:
+                               /* Leave 0 for undefined. */
+                               return;
+                       }
+               }
+       } else {
+               /* Old DMI spec. */
+               /*
+                * Note that technically, the lower bit of the base
+                * address should be 1 if the address is I/O and 0 if
+                * the address is in memory.  So many systems get that
+                * wrong (and all that I have seen are I/O) so we just
+                * ignore that bit and assume I/O.  Systems that use
+                * memory should use the newer spec, anyway.
+                */
+               dev->base_addr = base_addr & DMI_IPMI_IO_MASK;
+               dev->is_io_space = 1;
+               dev->offset = 1;
+       }
+
+       if (!slave_addr_set)
+               dev->slave_addr = data[DMI_IPMI_SLAVEADDR];
+
+       dev->good_data = 1;
+}
+
 static void __init dmi_save_ipmi_device(const struct dmi_header *dm)
 {
-       struct dmi_device *dev;
+       struct dmi_dev_ipmi *dev;
        void *data;
 
        data = dmi_alloc(dm->length);
@@ -318,11 +414,13 @@ static void __init dmi_save_ipmi_device(const struct 
dmi_header *dm)
        if (!dev)
                return;
 
-       dev->type = DMI_DEV_TYPE_IPMI;
-       dev->name = "IPMI controller";
-       dev->device_data = data;
+       dev->dev.type = DMI_DEV_TYPE_IPMI;
+       dev->dev.name = "IPMI controller";
+       dev->dev.device_data = data;
 
-       dmi_devices_list_add(dev);
+       dmi_decode_ipmi(dev, dm);
+
+       dmi_devices_list_add(&dev->dev);
 }
 
 static void __init dmi_save_dev_pciaddr(int instance, int segment, int bus,
diff --git a/include/linux/dmi.h b/include/linux/dmi.h
index c8dd5b8..96dc644 100644
--- a/include/linux/dmi.h
+++ b/include/linux/dmi.h
@@ -97,6 +97,24 @@ struct dmi_dev_onboard {
        int devfn;
 };
 
+/* Device data for an IPMI entry. */
+struct dmi_dev_ipmi {
+       struct dmi_device dev;
+       u8              good_data;
+       u8              type;
+       u8              is_io_space;
+       u8              irq;
+       u8              offset;
+       u8              slave_addr;
+       unsigned long   base_addr;
+};
+
+/* dmi_ipmi_data type field */
+#define IPMI_DMI_TYPE_KCS      0x01
+#define IPMI_DMI_TYPE_SMIC     0x02
+#define IPMI_DMI_TYPE_BT       0x03
+#define IPMI_DMI_TYPE_SSIF     0x04
+
 extern struct kobject *dmi_kobj;
 extern int dmi_check_system(const struct dmi_system_id *list);
 const struct dmi_system_id *dmi_first_match(const struct dmi_system_id *list);
@@ -127,6 +145,14 @@ static inline struct dmi_device *to_dmi_device(struct 
fwnode_handle *fwnode)
        return NULL;
 }
 
+static inline struct dmi_dev_ipmi *to_dmi_dev_ipmi(struct dmi_device *dev)
+{
+       if (!dev || dev->type != DMI_DEV_TYPE_IPMI)
+               return NULL;
+
+       return container_of(dev, struct dmi_dev_ipmi, dev);
+}
+
 #else
 
 static inline int dmi_check_system(const struct dmi_system_id *list) { return 
0; }
-- 
2.7.4

Reply via email to