diff options
-rw-r--r-- | Documentation/drivers/index.md | 7 | ||||
-rw-r--r-- | Documentation/drivers/ipmi_kcs.md | 47 | ||||
-rw-r--r-- | Documentation/index.md | 1 | ||||
-rw-r--r-- | src/drivers/ipmi/Makefile.inc | 1 | ||||
-rw-r--r-- | src/drivers/ipmi/chip.h | 29 | ||||
-rw-r--r-- | src/drivers/ipmi/ipmi_kcs.h | 24 | ||||
-rw-r--r-- | src/drivers/ipmi/ipmi_kcs_ops.c | 267 |
7 files changed, 376 insertions, 0 deletions
diff --git a/Documentation/drivers/index.md b/Documentation/drivers/index.md new file mode 100644 index 0000000000..642ae1a5f3 --- /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 0000000000..f6f0fb986a --- /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 76faffa497..a2c2878ddb 100644 --- a/Documentation/index.md +++ b/Documentation/index.md @@ -175,6 +175,7 @@ Contents: * [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 e9e7ff3bca..a29c2e2d0e 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 0000000000..eb8b4e6d34 --- /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 3cd7609039..a194dd22e9 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 0000000000..0cc4e0a965 --- /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, +}; |