drivers/ipmi: Add chip ops
* Add chips ops for IPMI KCS.
* Get IPMI version over KCS.
* Generates ACPI SPMI table for IPMI KCS.
* Generates SMBIOS type 38 for IPMI KCS.
* Generates ACPI SPMI device for IPMI KCS on LPC device.
* Add documentation
To use this driver on BMC that support KCS on I/O:
1. Add an entry to the devicetree.cb:
chip drivers/ipmi
device pnp ca2.0 on end # IPMI KCS
end
2. Select IPMI_KCS in Kconfig.
3. (Optional) enable LPC I/O decode for the given address.
Tested on Wedge100s.
Change-Id: I73cbd2058ccdc5395baf244f31345a85eb0047d7
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/33255
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Felix Held <felix-coreboot@felixheld.de>
diff --git a/Documentation/drivers/index.md b/Documentation/drivers/index.md
new file mode 100644
index 0000000..642ae1a
--- /dev/null
+++ b/Documentation/drivers/index.md
@@ -0,0 +1,7 @@
+# Platform indenpendend drivers documentation
+
+The drivers can be found in `src/drivers`. They are intended for onboard
+and plugin devices, significantly reducing integration complexity and
+they allow to easily reuse existing code accross platforms.
+
+* [IPMI KCS](ipmi_kcs.md)
diff --git a/Documentation/drivers/ipmi_kcs.md b/Documentation/drivers/ipmi_kcs.md
new file mode 100644
index 0000000..f6f0fb9
--- /dev/null
+++ b/Documentation/drivers/ipmi_kcs.md
@@ -0,0 +1,47 @@
+# IPMI KCS driver
+
+The driver can be found in `src/drivers/ipmi/`. It works with BMC that provide
+a KCS I/O interface as specified in the [IPMI] standard.
+
+The driver detects the IPMI version, reserves the I/O space in coreboot's
+resource allocator and writes the required ACPI and SMBIOS tables.
+
+## For developers
+
+To use the driver, select the `IPMI_KCS` Kconfig and add the following PNP
+device under the LPC bridge device (in example for the KCS at 0xca2):
+
+```
+ chip drivers/ipmi
+ device pnp ca2.0 on end # IPMI KCS
+ end
+```
+
+**Note:** The I/O base address needs to be aligned to 2.
+
+The following registers can be set:
+
+* `have_nv_storage`
+ * Boolean
+ * If true `nv_storage_device_address` will be added to SMBIOS type 38.
+* `nv_storage_device_address`
+ * Integer
+ * The NV storage address as defined in SMBIOS spec for type 38.
+* `bmc_i2c_address`
+ * Integer
+ * The i2c address of the BMC. zero if not applicable.
+* `have_apic`
+ * Boolean
+ * If true the `apic_interrupt` will be added to SPMI table.
+* `apic_interrupt`
+ * Integer
+ * The APIC interrupt used to notify about a change on the KCS.
+* `have_gpe`
+ * Boolean
+ * If true the `gpe_interrupt` will be added to SPMI table.
+* `gpe_interrupt`
+ * Integer
+ * The bit in GPE (SCI) used to notify about a change on the KCS.
+
+
+[IPMI]: https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf
diff --git a/Documentation/index.md b/Documentation/index.md
index 76faffa..a2c2878 100644
--- a/Documentation/index.md
+++ b/Documentation/index.md
@@ -175,6 +175,7 @@
* [Native Graphics Initialization with libgfxinit](gfx/libgfxinit.md)
* [Display panel-specific documentation](gfx/display-panel.md)
* [Architecture-specific documentation](arch/index.md)
+* [Platform independend drivers documentation](drivers/index.md)
* [Northbridge-specific documentation](northbridge/index.md)
* [System on Chip-specific documentation](soc/index.md)
* [Mainboard-specific documentation](mainboard/index.md)
diff --git a/src/drivers/ipmi/Makefile.inc b/src/drivers/ipmi/Makefile.inc
index e9e7ff3..a29c2e2 100644
--- a/src/drivers/ipmi/Makefile.inc
+++ b/src/drivers/ipmi/Makefile.inc
@@ -1 +1,2 @@
ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs.c
+ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs_ops.c
diff --git a/src/drivers/ipmi/chip.h b/src/drivers/ipmi/chip.h
new file mode 100644
index 0000000..eb8b4e6
--- /dev/null
+++ b/src/drivers/ipmi/chip.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2017 Patrick Rudolph <siro@das-labor.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _IMPI_CHIP_H_
+#define _IPMI_CHIP_H_
+
+struct drivers_ipmi_config {
+ u8 bmc_i2c_address;
+ u8 have_nv_storage;
+ u8 nv_storage_device_address;
+ u8 have_gpe;
+ u8 gpe_interrupt;
+ u8 have_apic;
+ u32 apic_interrupt;
+};
+
+#endif /* _IMPI_CHIP_H_ */
diff --git a/src/drivers/ipmi/ipmi_kcs.h b/src/drivers/ipmi/ipmi_kcs.h
index 3cd7609..a194dd2 100644
--- a/src/drivers/ipmi/ipmi_kcs.h
+++ b/src/drivers/ipmi/ipmi_kcs.h
@@ -20,6 +20,10 @@
#define IPMI_NETFN_BRIDGE 0x02
#define IPMI_NETFN_SENSOREVENT 0x04
#define IPMI_NETFN_APPLICATION 0x06
+#define IPMI_BMC_GET_DEVICE_ID 0x01
+#define IPMI_IPMI_VERSION_MINOR(x) ((x) >> 4)
+#define IPMI_IPMI_VERSION_MAJOR(x) ((x) & 0xf)
+
#define IPMI_NETFN_FIRMWARE 0x08
#define IPMI_NETFN_STORAGE 0x0a
#define IPMI_NETFN_TRANSPORT 0x0c
@@ -29,4 +33,24 @@
extern int ipmi_kcs_message(int port, int netfn, int lun, int cmd,
const unsigned char *inmsg, int inlen,
unsigned char *outmsg, int outlen);
+
+struct ipmi_rsp {
+ uint8_t lun;
+ uint8_t cmd;
+ uint8_t completion_code;
+} __packed;
+
+/* Get Device ID */
+struct ipmi_devid_rsp {
+ struct ipmi_rsp resp;
+ uint8_t device_id;
+ uint8_t device_revision;
+ uint8_t fw_rev1;
+ uint8_t fw_rev2;
+ uint8_t ipmi_version;
+ uint8_t additional_device_support;
+ uint8_t manufacturer_id[3];
+ uint8_t product_id[2];
+} __packed;
+
#endif
diff --git a/src/drivers/ipmi/ipmi_kcs_ops.c b/src/drivers/ipmi/ipmi_kcs_ops.c
new file mode 100644
index 0000000..0cc4e0a
--- /dev/null
+++ b/src/drivers/ipmi/ipmi_kcs_ops.c
@@ -0,0 +1,267 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2019 9elements Agency GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of
+ * the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Place in devicetree.cb:
+ *
+ * chip drivers/ipmi
+ * device pnp ca2.0 on end # IPMI KCS
+ * end
+ */
+
+#include <console/console.h>
+#include <device/device.h>
+#include <device/pnp.h>
+#if CONFIG(HAVE_ACPI_TABLES)
+#include <arch/acpi.h>
+#include <arch/acpigen.h>
+#endif
+#if CONFIG(GENERATE_SMBIOS_TABLES)
+#include <smbios.h>
+#endif
+#include <version.h>
+#include <delay.h>
+#include "ipmi_kcs.h"
+#include "chip.h"
+
+/* 4 bit encoding */
+static u8 ipmi_revision_major = 0x1;
+static u8 ipmi_revision_minor = 0x0;
+
+static int ipmi_get_device_id(struct device *dev, struct ipmi_devid_rsp *rsp)
+{
+ int ret;
+
+ ret = ipmi_kcs_message(dev->path.pnp.port, IPMI_NETFN_APPLICATION, 0,
+ IPMI_BMC_GET_DEVICE_ID, NULL, 0, (u8 *)rsp,
+ sizeof(*rsp));
+ if (ret < sizeof(struct ipmi_rsp) || rsp->resp.completion_code) {
+ printk(BIOS_ERR, "IPMI: %s command failed (ret=%d resp=0x%x)\n",
+ __func__, ret, rsp->resp.completion_code);
+ return 1;
+ }
+ if (ret != sizeof(*rsp)) {
+ printk(BIOS_ERR, "IPMI: %s response truncated\n", __func__);
+ return 1;
+ }
+ return 0;
+}
+
+static void ipmi_kcs_init(struct device *dev)
+{
+ struct ipmi_devid_rsp rsp;
+ uint32_t man_id = 0, prod_id = 0;
+
+ if (!dev->enabled)
+ return;
+
+ /* Get IPMI version for ACPI and SMBIOS */
+ if (!ipmi_get_device_id(dev, &rsp)) {
+ ipmi_revision_minor = IPMI_IPMI_VERSION_MINOR(rsp.ipmi_version);
+ ipmi_revision_major = IPMI_IPMI_VERSION_MAJOR(rsp.ipmi_version);
+
+ memcpy(&man_id, rsp.manufacturer_id,
+ sizeof(rsp.manufacturer_id));
+
+ memcpy(&prod_id, rsp.product_id, sizeof(rsp.product_id));
+
+ printk(BIOS_INFO, "IPMI: Found man_id 0x%06x, prod_id 0x%04x\n",
+ man_id, prod_id);
+
+ printk(BIOS_INFO, "IPMI: Version %01x.%01x\n",
+ ipmi_revision_major, ipmi_revision_minor);
+ } else {
+ /* Don't write tables if communication failed */
+ dev->enabled = 0;
+ }
+}
+
+#if CONFIG(HAVE_ACPI_TABLES)
+static uint32_t uid_cnt = 0;
+
+static unsigned long
+ipmi_write_acpi_tables(struct device *dev, unsigned long current,
+ struct acpi_rsdp *rsdp)
+{
+ struct drivers_ipmi_config *conf = NULL;
+ struct acpi_spmi *spmi;
+ s8 gpe_interrupt = -1;
+ u32 apic_interrupt = 0;
+ acpi_addr_t addr = {
+ .space_id = ACPI_ADDRESS_SPACE_IO,
+ .access_size = ACPI_ACCESS_SIZE_BYTE_ACCESS,
+ .addrl = dev->path.pnp.port,
+ };
+
+ current = ALIGN_UP(current, 8);
+ printk(BIOS_DEBUG, "ACPI: * SPMI at %lx\n", current);
+ spmi = (struct acpi_spmi *)current;
+
+ if (dev->chip_info)
+ conf = dev->chip_info;
+
+ if (conf) {
+ if (conf->have_gpe)
+ gpe_interrupt = conf->gpe_interrupt;
+ if (conf->have_apic)
+ apic_interrupt = conf->apic_interrupt;
+ }
+
+ /* Use command to get UID from ipmi_ssdt */
+ acpi_create_ipmi(dev, spmi, (ipmi_revision_major << 8) |
+ (ipmi_revision_minor << 4), &addr,
+ IPMI_INTERFACE_KCS, gpe_interrupt, apic_interrupt,
+ dev->command);
+
+ acpi_add_table(rsdp, spmi);
+
+ current += spmi->header.length;
+
+ return current;
+}
+
+static void ipmi_ssdt(struct device *dev)
+{
+ const char *scope = acpi_device_scope(dev);
+ struct drivers_ipmi_config *conf = NULL;
+
+ if (!scope) {
+ printk(BIOS_ERR, "IPMI: Missing ACPI scope for %s\n",
+ dev_path(dev));
+ return;
+ }
+
+ if (dev->chip_info)
+ conf = dev->chip_info;
+
+ /* Use command to pass UID to ipmi_write_acpi_tables */
+ dev->command = uid_cnt++;
+
+ /* write SPMI device */
+ acpigen_write_scope(scope);
+ acpigen_write_device("SPMI");
+ acpigen_write_name_string("_HID", "IPI0001");
+ acpigen_write_name_string("_STR", "IPMI_KCS");
+ acpigen_write_name_byte("_UID", dev->command);
+ acpigen_write_STA(0xf);
+ acpigen_write_name("_CRS");
+ acpigen_write_resourcetemplate_header();
+ acpigen_write_io16(dev->path.pnp.port, dev->path.pnp.port, 1, 2, 1);
+
+ if (conf) {
+ // FIXME: is that correct?
+ if (conf->have_apic)
+ acpigen_write_irq(1 << conf->apic_interrupt);
+ }
+
+ acpigen_write_resourcetemplate_footer();
+
+ acpigen_write_method("_IFT", 0);
+ acpigen_write_return_byte(1); // KCS
+ acpigen_pop_len();
+
+ acpigen_write_method("_SRV", 0);
+ acpigen_write_return_integer((ipmi_revision_major << 8) |
+ (ipmi_revision_minor << 4));
+ acpigen_pop_len();
+
+ acpigen_pop_len(); /* pop device */
+ acpigen_pop_len(); /* pop scope */
+}
+#endif
+
+#if CONFIG(GENERATE_SMBIOS_TABLES)
+static int ipmi_smbios_data(struct device *dev, int *handle,
+ unsigned long *current)
+{
+ struct drivers_ipmi_config *conf = NULL;
+ u8 nv_storage = 0xff;
+ u8 i2c_address = 0;
+ int len = 0;
+
+ if (dev->chip_info)
+ conf = dev->chip_info;
+
+ if (conf) {
+ if (conf->have_nv_storage)
+ nv_storage = conf->nv_storage_device_address;
+ i2c_address = conf->bmc_i2c_address;
+ }
+
+ // add IPMI Device Information
+ len += smbios_write_type38(
+ current, handle,
+ SMBIOS_BMC_INTERFACE_KCS,
+ ipmi_revision_minor | (ipmi_revision_major << 4),
+ i2c_address, // I2C address
+ nv_storage, // NV storage
+ dev->path.pnp.port | 1, // IO interface
+ 0,
+ 0); // no IRQ
+
+ return len;
+}
+#endif
+
+static void ipmi_set_resources(struct device *dev)
+{
+ struct resource *res;
+
+ for (res = dev->resource_list; res; res = res->next) {
+ if (!(res->flags & IORESOURCE_ASSIGNED))
+ continue;
+
+ res->flags |= IORESOURCE_STORED;
+ report_resource_stored(dev, res, "");
+ }
+}
+
+static void ipmi_read_resources(struct device *dev)
+{
+ struct resource *res = new_resource(dev, 0);
+ res->base = dev->path.pnp.port;
+ res->size = 2;
+ res->flags = IORESOURCE_IO | IORESOURCE_ASSIGNED | IORESOURCE_FIXED;
+}
+
+static struct device_operations ops = {
+ .read_resources = ipmi_read_resources,
+ .set_resources = ipmi_set_resources,
+ .enable_resources = DEVICE_NOOP,
+ .init = ipmi_kcs_init,
+#if CONFIG(HAVE_ACPI_TABLES)
+ .write_acpi_tables = ipmi_write_acpi_tables,
+ .acpi_fill_ssdt_generator = ipmi_ssdt,
+#endif
+#if CONFIG(GENERATE_SMBIOS_TABLES)
+ .get_smbios_data = ipmi_smbios_data,
+#endif
+};
+
+static void enable_dev(struct device *dev)
+{
+ if (dev->path.type != DEVICE_PATH_PNP)
+ printk(BIOS_ERR, "%s: Unsupported device type\n",
+ dev_path(dev));
+ else if (dev->path.pnp.port & 1)
+ printk(BIOS_ERR, "%s: Base address needs to be aligned to 2\n",
+ dev_path(dev));
+ else
+ dev->ops = &ops;
+}
+
+struct chip_operations drivers_ipmi_ops = {
+ CHIP_NAME("IPMI KCS")
+ .enable_dev = enable_dev,
+};