[PATCH v3 3/5] dmi: Add IPMI DMI scanning to the DMI code

From: minyard
Date: Wed Apr 27 2016 - 09:05:31 EST


From: Corey Minyard <cminyard@xxxxxxxxxx>

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 <cminyard@xxxxxxxxxx>
Cc: Jean Delvare <jdelvare@xxxxxxx>
Cc: Andy Lutomirski <luto@xxxxxxxxxx>
Tested-by: Andy Lutomirski <luto@xxxxxxxxxx>
---
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