From 9f88145f1871456de67ae6a242ac2661187bd4ff Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Fri, 9 Jun 2017 21:19:52 -0500 Subject: ipmi: Create a platform device for a DMI-specified IPMI interface Create a platform device for each IPMI device in the DMI table, a separate kind of device for SSIF types and for KCS, BT, and SMIC types. This is so auto-loading IPMI devices will work from just SMBIOS tables. This also adds the ability to extract the slave address from the SMBIOS tables, so that when the driver uses ACPI-specified interfaces, it can still extract the slave address from SMBIOS. Signed-off-by: Corey Minyard Cc: Andy Lutomirski --- drivers/char/ipmi/Kconfig | 4 + drivers/char/ipmi/Makefile | 1 + drivers/char/ipmi/ipmi_dmi.c | 273 +++++++++++++++++++++++++++++++++++++++++++ drivers/char/ipmi/ipmi_dmi.h | 12 ++ 4 files changed, 290 insertions(+) create mode 100644 drivers/char/ipmi/ipmi_dmi.c create mode 100644 drivers/char/ipmi/ipmi_dmi.h (limited to 'drivers/char/ipmi') diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 90f3edffb067..f6fa056a52fc 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -5,6 +5,7 @@ menuconfig IPMI_HANDLER tristate 'IPMI top-level message handler' depends on HAS_IOMEM + select IPMI_DMI_DECODE if DMI help This enables the central IPMI message handler, required for IPMI to work. @@ -16,6 +17,9 @@ menuconfig IPMI_HANDLER If unsure, say N. +config IPMI_DMI_DECODE + bool + if IPMI_HANDLER config IPMI_PANIC_EVENT diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index 0d98cd91def1..eefb0b301e83 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile @@ -7,6 +7,7 @@ ipmi_si-y := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o obj-$(CONFIG_IPMI_SI) += ipmi_si.o +obj-$(CONFIG_IPMI_DMI_DECODE) += ipmi_dmi.o obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o diff --git a/drivers/char/ipmi/ipmi_dmi.c b/drivers/char/ipmi/ipmi_dmi.c new file mode 100644 index 000000000000..2a84401dea05 --- /dev/null +++ b/drivers/char/ipmi/ipmi_dmi.c @@ -0,0 +1,273 @@ +/* + * A hack to create a platform device from a DMI entry. This will + * allow autoloading of the IPMI drive based on SMBIOS entries. + */ + +#include +#include +#include +#include +#include +#include "ipmi_dmi.h" + +struct ipmi_dmi_info { + int type; + u32 flags; + unsigned long addr; + u8 slave_addr; + struct ipmi_dmi_info *next; +}; + +static struct ipmi_dmi_info *ipmi_dmi_infos; + +static int ipmi_dmi_nr __initdata; + +static void __init dmi_add_platform_ipmi(unsigned long base_addr, + u32 flags, + u8 slave_addr, + int irq, + int offset, + int type) +{ + struct platform_device *pdev; + struct resource r[4]; + unsigned int num_r = 1, size; + struct property_entry p[4] = { + PROPERTY_ENTRY_U8("slave-addr", slave_addr), + PROPERTY_ENTRY_U8("ipmi-type", type), + PROPERTY_ENTRY_U16("i2c-addr", base_addr), + { } + }; + char *name, *override; + int rv; + struct ipmi_dmi_info *info; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + pr_warn("ipmi:dmi: Could not allocate dmi info\n"); + } else { + info->type = type; + info->flags = flags; + info->addr = base_addr; + info->slave_addr = slave_addr; + info->next = ipmi_dmi_infos; + ipmi_dmi_infos = info; + } + + name = "dmi-ipmi-si"; + override = "ipmi_si"; + switch (type) { + case IPMI_DMI_TYPE_SSIF: + name = "dmi-ipmi-ssif"; + override = "ipmi_ssif"; + offset = 1; + size = 1; + break; + case IPMI_DMI_TYPE_BT: + size = 3; + break; + case IPMI_DMI_TYPE_KCS: + case IPMI_DMI_TYPE_SMIC: + size = 2; + break; + default: + pr_err("ipmi:dmi: Invalid IPMI type: %d", type); + return; + } + + pdev = platform_device_alloc(name, ipmi_dmi_nr); + if (!pdev) { + pr_err("ipmi:dmi: Error allocation IPMI platform device"); + return; + } + pdev->driver_override = override; + + if (type == IPMI_DMI_TYPE_SSIF) + goto add_properties; + + memset(r, 0, sizeof(r)); + + r[0].start = base_addr; + r[0].end = r[0].start + offset - 1; + r[0].name = "IPMI Address 1"; + r[0].flags = flags; + + if (size > 1) { + r[1].start = r[0].start + offset; + r[1].end = r[1].start + offset - 1; + r[1].name = "IPMI Address 2"; + r[1].flags = flags; + num_r++; + } + + if (size > 2) { + r[2].start = r[1].start + offset; + r[2].end = r[2].start + offset - 1; + r[2].name = "IPMI Address 3"; + r[2].flags = flags; + num_r++; + } + + if (irq) { + r[num_r].start = irq; + r[num_r].end = irq; + r[num_r].name = "IPMI IRQ"; + r[num_r].flags = IORESOURCE_IRQ; + num_r++; + } + + rv = platform_device_add_resources(pdev, r, num_r); + if (rv) { + dev_err(&pdev->dev, + "ipmi:dmi: Unable to add resources: %d\n", rv); + goto err; + } + +add_properties: + rv = platform_device_add_properties(pdev, p); + if (rv) { + dev_err(&pdev->dev, + "ipmi:dmi: Unable to add properties: %d\n", rv); + goto err; + } + + rv = platform_device_add(pdev); + if (rv) { + dev_err(&pdev->dev, "ipmi:dmi: Unable to add device: %d\n", rv); + goto err; + } + + ipmi_dmi_nr++; + return; + +err: + platform_device_put(pdev); +} + +/* + * Look up the slave address for a given interface. This is here + * because ACPI doesn't have a slave address while SMBIOS does, but we + * prefer using ACPI so the ACPI code can use the IPMI namespace. + * This function allows an ACPI-specified IPMI device to look up the + * slave address from the DMI table. + */ +int ipmi_dmi_get_slave_addr(int type, u32 flags, unsigned long base_addr) +{ + struct ipmi_dmi_info *info = ipmi_dmi_infos; + + while (info) { + if (info->type == type && + info->flags == flags && + info->addr == base_addr) + return info->slave_addr; + info = info->next; + } + + return 0; +} +EXPORT_SYMBOL(ipmi_dmi_get_slave_addr); + +#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(const struct dmi_header *dm) +{ + const u8 *data = (const u8 *) dm; + u32 flags = IORESOURCE_IO; + unsigned long base_addr; + u8 len = dm->length; + u8 slave_addr; + int irq = 0, offset; + int type; + + if (len < DMI_IPMI_MIN_LENGTH) + return; + + type = data[DMI_IPMI_TYPE]; + slave_addr = data[DMI_IPMI_SLAVEADDR]; + + memcpy(&base_addr, data + DMI_IPMI_ADDR, sizeof(unsigned long)); + if (len >= DMI_IPMI_VER2_LENGTH) { + if (type == IPMI_DMI_TYPE_SSIF) { + offset = 0; + flags = 0; + base_addr = data[DMI_IPMI_ADDR] >> 1; + if (base_addr == 0) { + /* + * Some broken systems put the I2C address in + * the slave address field. We try to + * accommodate them here. + */ + base_addr = data[DMI_IPMI_SLAVEADDR] >> 1; + slave_addr = 0; + } + } else { + if (base_addr & 1) { + /* I/O */ + base_addr &= DMI_IPMI_IO_MASK; + } else { + /* Memory */ + flags = IORESOURCE_MEM; + } + + /* + * If bit 4 of byte 0x10 is set, then the lsb + * for the address is odd. + */ + base_addr |= (data[DMI_IPMI_ACCESS] >> 4) & 1; + + 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 */ + offset = 1; + break; + case 1: /* 32-bit boundaries */ + offset = 4; + break; + case 2: /* 16-byte boundaries */ + offset = 16; + break; + default: + pr_err("ipmi:dmi: Invalid offset: 0"); + 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. + */ + base_addr = base_addr & DMI_IPMI_IO_MASK; + offset = 1; + } + + dmi_add_platform_ipmi(base_addr, flags, slave_addr, irq, + offset, type); +} + +static int __init scan_for_dmi_ipmi(void) +{ + const struct dmi_device *dev = NULL; + + while ((dev = dmi_find_device(DMI_DEV_TYPE_IPMI, NULL, dev))) + dmi_decode_ipmi((const struct dmi_header *) dev->device_data); + + return 0; +} +subsys_initcall(scan_for_dmi_ipmi); diff --git a/drivers/char/ipmi/ipmi_dmi.h b/drivers/char/ipmi/ipmi_dmi.h new file mode 100644 index 000000000000..0a1afe5ceb1e --- /dev/null +++ b/drivers/char/ipmi/ipmi_dmi.h @@ -0,0 +1,12 @@ +/* + * DMI defines for use by IPMI + */ + +#define IPMI_DMI_TYPE_KCS 0x01 +#define IPMI_DMI_TYPE_SMIC 0x02 +#define IPMI_DMI_TYPE_BT 0x03 +#define IPMI_DMI_TYPE_SSIF 0x04 + +#ifdef CONFIG_IPMI_DMI_DECODE +int ipmi_dmi_get_slave_addr(int type, u32 flags, unsigned long base_addr); +#endif -- cgit v1.2.3