diff mbox series

[v5,3/5] dmi: Add IPMI DMI scanning to the DMI code

Message ID 1495050984-5602-4-git-send-email-minyard@acm.org
State New
Headers show
Series [v5,1/5] dmi: remove const from return of dmi_find_device | expand

Commit Message

Corey Minyard May 17, 2017, 7:56 p.m. UTC
From: Corey Minyard <cminyard@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 <cminyard@mvista.com>

Cc: Jean Delvare <jdelvare@suse.de>
Cc: Andy Lutomirski <luto@kernel.org>
Tested-by: Andy Lutomirski <luto@kernel.org>

---
 drivers/firmware/dmi_scan.c | 108 ++++++++++++++++++++++++++++++++++++++++++--
 include/linux/dmi.h         |  26 +++++++++++
 2 files changed, 129 insertions(+), 5 deletions(-)

-- 
2.7.4
diff mbox series

Patch

diff --git a/drivers/firmware/dmi_scan.c b/drivers/firmware/dmi_scan.c
index 78ef357..96c0743 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; }