diff --git a/Documentation/drivers/index.md b/Documentation/drivers/index.md index 45c491bbaa..a797416008 100644 --- a/Documentation/drivers/index.md +++ b/Documentation/drivers/index.md @@ -21,6 +21,7 @@ Some of the drivers currently available include: CFR CFR use within coreboot Intel DPTF +IPMI BT (Block Transfer) IPMI KCS SMMSTORE SMMSTOREv2 diff --git a/Documentation/drivers/ipmi_bt.md b/Documentation/drivers/ipmi_bt.md new file mode 100644 index 0000000000..bcd68a02e8 --- /dev/null +++ b/Documentation/drivers/ipmi_bt.md @@ -0,0 +1,79 @@ +# IPMI Block Transfer (BT) driver + +The driver can be found in `src/drivers/ipmi/` (same as KCS). It works with BMC +that provides a BT I/O interface as specified in the [IPMI] standard. See +"Intelligent Platform Management Interface Specification", v2.0, Rev. 1.1 for +more details on the interface and IPMI in general. + +The driver detects the IPMI version and reserves the I/O space in coreboot's +resource allocator. + +## For developers + +To use the driver, select the `IPMI_BT` Kconfig and add the following PNP +device (in example for the BT at 0xe4): + +``` +chip drivers/ipmi + device pnp e4.0 on end # IPMI BT +end +``` + +**Note:** The I/O base address must be aligned to 4. + +The following settings can be set in a device tree: + +```{eval-rst} ++------------------+--------------+-------------------------------------------+ +| Setting | Type/Default | Description/Purpose | ++==================+==============+===========================================+ +| wait_for_bmc | | Boolean | Wait for BMC to boot. This can be used if | +| | | false | the BMC takes a long time to boot after | +| | | PoR. | ++------------------+--------------+-------------------------------------------+ +| bmc_boot_timeout | | Integer | The timeout in seconds to wait for the | +| | | 0 | IPMI service to be loaded. Will be used | +| | | if wait_for_bmc is true. | ++------------------+--------------+-------------------------------------------+ +``` + +## Debugging/testing the driver + +`ipmi_sim` from [OpenIPMI] project can be used by running `ipmi_sim -d` in one +console to watch what's being sent/received and starting QEMU like this in +another console: + +``` +qemu-system-x86_64 \ + -M q35,smm=on \ + -bios build/coreboot.rom \ + -chardev socket,id=ipmichr0,host=localhost,port=9002,reconnect=10 \ + -device ipmi-bmc-extern,chardev=ipmichr0,id=bmc0 \ + -device isa-ipmi-bt,bmc=bmc0,irq=0 \ + -serial stdio +``` + +A simpler alternative is to use QEMU's builtin BMC simulator: + +``` +qemu-system-x86_64 \ + -M q35,smm=on \ + -bios build/coreboot.rom \ + -device ipmi-bmc-sim,id=bmc0 \ + -device isa-ipmi-bt,bmc=bmc0,irq=0 \ + -serial stdio +``` + +## References + +Useful links on the subject: + * README of `ipmi_sim`: + + * slides about OpenIPMI: + + * a usage example: + * old docs (the options are still there, but no longer have a dedicated page in + modern documentation): + +[IPMI]: https://www.intel.com/content/dam/www/public/us/en/documents/specification-updates/ipmi-intelligent-platform-mgt-interface-spec-2nd-gen-v2-0-spec-update.pdf +[OpenIPMI]: https://github.com/wrouesnel/openipmi diff --git a/src/drivers/ipmi/Kconfig b/src/drivers/ipmi/Kconfig index abfcade90a..101a781b55 100644 --- a/src/drivers/ipmi/Kconfig +++ b/src/drivers/ipmi/Kconfig @@ -55,3 +55,31 @@ config DRIVERS_IPMI_SUPERMICRO_OEM The following features are implemented: * Communicates the BIOS version to the BMC * Communicates the BIOS date to the BMC + +config IPMI_BT + bool + default n + depends on !IPMI_KCS + +config IPMI_BT_ROMSTAGE + bool + default n + depends on IPMI_BT + help + IPMI BT support in romstage. + +config BMC_BT_BASE + hex + default 0xe4 + depends on IPMI_BT + help + The PNP base address of BMC BT. It must be equal to the + pnp port value defined in devicetree for chip drivers/ipmi. + +config IPMI_TIMEOUT_MS + int + default 5000 + depends on IPMI_BT || IPMI_KCS + help + The time unit is millisecond for each IPMI transfer. + The default is suitable for implementations using polling. diff --git a/src/drivers/ipmi/Makefile.mk b/src/drivers/ipmi/Makefile.mk index ade7147030..c1e2adae3e 100644 --- a/src/drivers/ipmi/Makefile.mk +++ b/src/drivers/ipmi/Makefile.mk @@ -11,3 +11,13 @@ romstage-$(CONFIG_IPMI_KCS_ROMSTAGE) += ipmi_ops_premem.c romstage-$(CONFIG_IPMI_KCS_ROMSTAGE) += ipmi_kcs.c romstage-$(CONFIG_IPMI_KCS_ROMSTAGE) += ipmi_ops.c smm-$(CONFIG_SOC_RAS_BMC_SEL) += ipmi_kcs.c + +ramstage-$(CONFIG_IPMI_BT) += ipmi_if.c +ramstage-$(CONFIG_IPMI_BT) += ipmi_bt.c +ramstage-$(CONFIG_IPMI_BT) += ipmi_bt_ops.c +ramstage-$(CONFIG_IPMI_BT) += ipmi_ops.c +ramstage-$(CONFIG_IPMI_BT) += ipmi_fru.c +romstage-$(CONFIG_IPMI_BT_ROMSTAGE) += ipmi_if.c +romstage-$(CONFIG_IPMI_BT_ROMSTAGE) += ipmi_ops_premem.c +romstage-$(CONFIG_IPMI_BT_ROMSTAGE) += ipmi_bt.c +romstage-$(CONFIG_IPMI_BT_ROMSTAGE) += ipmi_ops.c diff --git a/src/drivers/ipmi/ipmi_bt.c b/src/drivers/ipmi/ipmi_bt.c new file mode 100644 index 0000000000..511f664834 --- /dev/null +++ b/src/drivers/ipmi/ipmi_bt.c @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* + * IPMI specification: + * https://www.intel.com/content/dam/www/public/us/en/documents/specification-updates/ipmi-intelligent-platform-mgt-interface-spec-2nd-gen-v2-0-spec-update.pdf + * + * LUN seems to be always zero. + */ + +#include "ipmi_bt.h" + +#include +#include +#include +#include +#include + +#include "ipmi_if.h" + +/* + * 11.6.3 BT Host to BMC Buffer (HOST2BMC) + * "The buffer must be a minimum of 64-bytes deep." This includes the length byte for + * convenience of the implementation. + */ +#define MAX_SEND_SIZE 64 +/* + * 11.1 BT Interface-BMC Request Message Format + * The length field is not considered part of the message on receiving (it's part of framing). + */ +#define MAX_RECEIVE_SIZE 255 +/* + * 11.1 BT Interface-BMC Request Message Format + * 4 leading bytes form a frame for the message. + */ +#define MAX_PAYLOAD_SIZE (MAX_SEND_SIZE - 4) + +#define BT_CTRL_REG 0 // Typical address of BT_CTRL is 0xE4 +#define HOST2BMC_REG 1 // Typical address of HOST2BMC is 0xE5 +#define BMC2HOST_REG 1 // Typical address of BMC2HOST is 0xE5 + +/* Bits of BT_CTRL */ +#define B_BUSY BIT(7) +#define H_BUSY BIT(6) +#define OEM0 BIT(5) +#define SMS_ATN BIT(4) +#define B2H_ATN BIT(3) +#define H2B_ATN BIT(2) +#define CLR_RD_PTR BIT(1) +#define CLR_WR_PTR BIT(0) + +static enum cb_err wait_for_control_bits(uint16_t port, uint8_t mask, uint8_t expected) +{ + uint16_t bt_ctrl_port = port + BT_CTRL_REG; + if (!wait_ms(CONFIG_IPMI_TIMEOUT_MS, (inb(bt_ctrl_port) & mask) == expected)) { + printk(BIOS_ERR, "%s(0x%04x, 0x%02x, 0x%02x) timeout!\n", + __func__, port, mask, expected); + return CB_ERR; + } + + return CB_SUCCESS; +} + +enum cb_err ipmi_bt_clear(uint16_t port) +{ + uint8_t bt_ctrl; + + /* + * First, make sure H_BUSY is set so BMC won't try to write new commands + * while we're resetting pointers. + */ + outb(H_BUSY, port + BT_CTRL_REG); + + /* If BMC is already in the process of writing, wait until it's done */ + if (wait_for_control_bits(port, B_BUSY, 0) == CB_ERR) + return CB_ERR; + + bt_ctrl = inb(port + BT_CTRL_REG); + + printk(BIOS_SPEW, "%s(): BT_CTRL = 0x%02x\n", __func__, bt_ctrl); + + /* + * Clear all bits which are already set (they are either toggle bits or + * write-1-to-clear) and reset buffer pointers. This also clears H_BUSY. + */ + outb(bt_ctrl | CLR_RD_PTR | CLR_WR_PTR, port + BT_CTRL_REG); + + return CB_SUCCESS; +} + +static enum cb_err ipmi_bt_send(uint16_t port, uint8_t addr, uint8_t cmd, + const uint8_t *payload, uint8_t payload_len, + uint8_t seq_num) +{ + uint16_t i; + uint16_t len; + uint8_t buf[MAX_SEND_SIZE]; + + len = 4 + payload_len; + + /* The length doesn't include the length byte. */ + buf[0] = len - 1; + buf[1] = addr; + buf[2] = seq_num; + buf[3] = cmd; + memcpy(&buf[4], payload, payload_len); + + /* Wait for BMC to be available and ready for the next command */ + if (wait_for_control_bits(port, B_BUSY | H2B_ATN, 0) == CB_ERR) + return CB_ERR; + + /* Clear write pointer */ + outb(CLR_WR_PTR, port + BT_CTRL_REG); + + /* Send our message */ + for (i = 0; i < len; ++i) + outb(buf[i], port + HOST2BMC_REG); + + /* Tell BMC to process the data */ + outb(H2B_ATN, port + BT_CTRL_REG); + + return CB_SUCCESS; +} + +static int ipmi_bt_recv(uint16_t port, uint8_t addr, uint8_t cmd, + uint8_t *response, uint8_t response_len, + uint8_t seq_num) +{ + uint16_t i; + uint16_t len; + uint16_t out_len; + uint8_t buf[MAX_RECEIVE_SIZE]; + + /* Wait for BMC's response */ + if (wait_for_control_bits(port, B2H_ATN, B2H_ATN) == CB_ERR) + return -1; + + /* Tell BMC that host is busy */ + outb(H_BUSY, port + BT_CTRL_REG); + + /* Acknowledge that response is being processed */ + outb(B2H_ATN, port + BT_CTRL_REG); + + /* Clear read pointer */ + outb(CLR_RD_PTR, port + BT_CTRL_REG); + + /* Receive response */ + len = inb(port + BMC2HOST_REG); + for (i = 0; i < len; ++i) + buf[i] = inb(port + BMC2HOST_REG); + + /* Indicate that the host is done working with the buffer */ + outb(H_BUSY, port + BT_CTRL_REG); + + if (len < 3) { + printk(BIOS_ERR, "IPMI BT response is shorter than 3 bytes: %d\n", len); + goto error; + } + + if (buf[0] != addr) { + printk(BIOS_ERR, + "NETFN/LUN field mismatch in IPMI BT response: 0x%02x instead of 0x%02x\n", + buf[0], addr); + goto error; + } + if (buf[1] != seq_num) { + printk(BIOS_ERR, + "SEQ field mismatch in IPMI BT response: 0x%02x instead of 0x%02x\n", + buf[1], seq_num); + goto error; + } + if (buf[2] != cmd) { + printk(BIOS_ERR, + "CMD field mismatch in IPMI BT response: 0x%02x instead of 0x%02x\n", + buf[2], cmd); + goto error; + } + + /* + * Copy response skipping sequence number to match KCS messages. + * Sequence number is really an implementation detail anyway. + */ + out_len = MIN(response_len, len - 1); + if (out_len > 0) + response[0] = buf[0]; + if (out_len > 1) + memcpy(&response[1], &buf[2], out_len - 1); + + return out_len; + +error: + printk(BIOS_DEBUG, " IPMI response length field: 0x%02x\n", len); + printk(BIOS_DEBUG, " IPMI NetFn/LUN: 0x%02x\n", addr); + printk(BIOS_DEBUG, " IPMI SEQ: 0x%02x\n", seq_num); + printk(BIOS_DEBUG, " IPMI command: 0x%02x\n", cmd); + return -1; +} + +int ipmi_message(int port, int netfn, int lun, int cmd, + const uint8_t *payload, int payload_len, + uint8_t *response, int response_len) +{ + static uint8_t seq_num = 0xff; + + uint8_t addr; + + if (netfn < 0 || netfn > 0x3f) { + printk(BIOS_ERR, "%s(): NetFn (%d) is not within [0, %d] range\n", + __func__, netfn, 0x3f); + return -1; + } + if (lun < 0 || lun > 0x3) { + printk(BIOS_ERR, "%s(): LUN (%d) is not within [0, %d] range\n", + __func__, lun, 0x3); + return -1; + } + if (payload_len < 0 || payload_len > MAX_PAYLOAD_SIZE) { + printk(BIOS_ERR, "%s(): payload size (%d) is not within [0, %d] range\n", + __func__, payload_len, MAX_PAYLOAD_SIZE); + return -1; + } + + addr = (netfn << 2) | (lun & 0x3); + if (ipmi_bt_send(port, addr, cmd, payload, payload_len, ++seq_num) == CB_ERR) { + printk(BIOS_ERR, "Failed to send IPMI BT command 0x%02x\n", cmd); + return -1; + } + + addr = ((netfn + 1) << 2) | (lun & 0x3); + return ipmi_bt_recv(port, addr, cmd, response, response_len, seq_num); +} diff --git a/src/drivers/ipmi/ipmi_bt.h b/src/drivers/ipmi/ipmi_bt.h new file mode 100644 index 0000000000..23bfda1ad0 --- /dev/null +++ b/src/drivers/ipmi/ipmi_bt.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __IPMI_BT_H +#define __IPMI_BT_H + +#include + +/* Drops events from BMC and resets state of the BT interface */ +enum cb_err ipmi_bt_clear(uint16_t port); + +#endif /* __IPMI_BT_H */ diff --git a/src/drivers/ipmi/ipmi_bt_ops.c b/src/drivers/ipmi/ipmi_bt_ops.c new file mode 100644 index 0000000000..82bfc33f3b --- /dev/null +++ b/src/drivers/ipmi/ipmi_bt_ops.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* + * Place in devicetree.cb: + * + * chip drivers/ipmi + * device pnp e4.0 on end # IPMI BT + * end + */ + +#include +#include +#include +#include + +#include "ipmi_if.h" +#include "ipmi_bt.h" + +static u8 bmc_revision_major = 0x0; +static u8 bmc_revision_minor = 0x0; + +static void ipmi_bt_init(struct device *dev) +{ + struct ipmi_devid_rsp rsp; + struct drivers_ipmi_config *conf = dev->chip_info; + + if (!conf) { + printk(BIOS_WARNING, "IPMI: chip_info is missing! Skip init.\n"); + return; + } + + printk(BIOS_DEBUG, "IPMI: PNP BT 0x%x\n", dev->path.pnp.port); + + if (ipmi_process_self_test_result(dev)) { + dev->enabled = 0; + return; + } + + if (!ipmi_get_device_id(dev, &rsp)) { + uint32_t man_id = 0; + uint32_t prod_id = 0; + + /* 4 bit encoding */ + u8 ipmi_revision_minor = IPMI_IPMI_VERSION_MINOR(rsp.ipmi_version); + u8 ipmi_revision_major = IPMI_IPMI_VERSION_MAJOR(rsp.ipmi_version); + + bmc_revision_major = rsp.fw_rev1; + bmc_revision_minor = rsp.fw_rev2; + + 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 { + dev->enabled = 0; + return; + } + + if (ipmi_bt_clear(dev->path.pnp.port) == CB_ERR) + dev->enabled = 0; +} + +void ipmi_bmc_version(uint8_t *ipmi_bmc_major_revision, uint8_t *ipmi_bmc_minor_revision) +{ + if (bmc_revision_major == 0 && bmc_revision_minor == 0) + printk(BIOS_ERR, "IPMI: BMC revision missing\n"); + + *ipmi_bmc_major_revision = bmc_revision_major; + *ipmi_bmc_minor_revision = bmc_revision_minor; +} + +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 = 3; + res->flags = IORESOURCE_IO | IORESOURCE_ASSIGNED | IORESOURCE_FIXED; +} + +static struct device_operations ops = { + .read_resources = ipmi_read_resources, + .set_resources = ipmi_set_resources, + .init = ipmi_bt_init, +}; + +static void enable_dev(struct device *dev) +{ + if (dev->path.type != DEVICE_PATH_PNP) + printk(BIOS_ERR, "%s: Expected device type %s, got %s\n", + dev_path(dev), + dev_path_name(DEVICE_PATH_PNP), + dev_path_name(dev->path.type)); + else if (!IS_ALIGNED(dev->path.pnp.port, 4)) + printk(BIOS_ERR, "%s: Base address %#x must be aligned to 4, but isn't\n", + dev_path(dev), dev->path.pnp.port); + else + dev->ops = &ops; +} + +struct chip_operations drivers_ipmi_ops = { + .name = "IPMI BT", + .enable_dev = enable_dev, +}; diff --git a/src/drivers/ipmi/ipmi_ops_premem.c b/src/drivers/ipmi/ipmi_ops_premem.c index 99c5842bb3..e65ab4ae95 100644 --- a/src/drivers/ipmi/ipmi_ops_premem.c +++ b/src/drivers/ipmi/ipmi_ops_premem.c @@ -6,6 +6,7 @@ #include #include +#include "ipmi_bt.h" #include "ipmi_if.h" #include "chip.h" @@ -26,7 +27,8 @@ enum cb_err ipmi_premem_init(const u16 port, const u16 device) printk(BIOS_ERR, "IPMI: device is not enabled\n"); return CB_ERR; } - printk(BIOS_DEBUG, "IPMI: romstage PNP KCS 0x%x\n", dev->path.pnp.port); + printk(BIOS_DEBUG, "IPMI: romstage PNP %s 0x%x\n", + CONFIG(IPMI_KCS) ? "KCS" : "BT", dev->path.pnp.port); if (dev->chip_info) conf = dev->chip_info; @@ -49,5 +51,8 @@ enum cb_err ipmi_premem_init(const u16 port, const u16 device) if (ipmi_process_self_test_result(dev)) return CB_ERR; + if (CONFIG(IPMI_BT) && ipmi_bt_clear(dev->path.pnp.port) == CB_ERR) + return CB_ERR; + return CB_SUCCESS; }