summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/drivers/ipmi/Kconfig10
-rw-r--r--src/drivers/ipmi/Makefile.inc1
-rw-r--r--src/drivers/ipmi/ipmi_fru.c409
-rw-r--r--src/drivers/ipmi/ipmi_kcs.h1
-rw-r--r--src/drivers/ipmi/ipmi_ops.h73
5 files changed, 494 insertions, 0 deletions
diff --git a/src/drivers/ipmi/Kconfig b/src/drivers/ipmi/Kconfig
index 0f7152d558..37cfc0d230 100644
--- a/src/drivers/ipmi/Kconfig
+++ b/src/drivers/ipmi/Kconfig
@@ -8,3 +8,13 @@ config IPMI_KCS_REGISTER_SPACING
depends on IPMI_KCS
help
KCS status and command register IO port address spacing
+
+config IPMI_FRU_SINGLE_RW_SZ
+ int
+ default 16
+ depends on IPMI_KCS
+ help
+ The data size in a single IPMI FRU read/write command.
+ IPMB messages are limited to 32-bytes total. When the
+ data size is larger than this value, IPMI can complete
+ reading/writing the data over multiple commands.
diff --git a/src/drivers/ipmi/Makefile.inc b/src/drivers/ipmi/Makefile.inc
index 9d5b3d418f..973fff82c7 100644
--- a/src/drivers/ipmi/Makefile.inc
+++ b/src/drivers/ipmi/Makefile.inc
@@ -1,3 +1,4 @@
ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs.c
ramstage-$(CONFIG_IPMI_KCS) += ipmi_kcs_ops.c
ramstage-$(CONFIG_IPMI_KCS) += ipmi_ops.c
+ramstage-$(CONFIG_IPMI_KCS) += ipmi_fru.c
diff --git a/src/drivers/ipmi/ipmi_fru.c b/src/drivers/ipmi/ipmi_fru.c
new file mode 100644
index 0000000000..a5e6ea60d5
--- /dev/null
+++ b/src/drivers/ipmi/ipmi_fru.c
@@ -0,0 +1,409 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2019 Wiwynn Corp.
+ *
+ * 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.
+ */
+
+#include <console/console.h>
+#include <string.h>
+#include <delay.h>
+#include "ipmi_ops.h"
+
+#define MAX_FRU_BUSY_RETRY 5
+#define READ_FRU_DATA_RETRY_INTERVAL_MS 30 /* From IPMI spec v2.0 rev 1.1 */
+#define OFFSET_LENGTH_MULTIPLIER 8 /* offsets/lengths are multiples of 8 */
+#define NUM_DATA_BYTES(t) (t & 0x3f) /* Encoded in type/length byte */
+
+static enum cb_err ipmi_read_fru(const int port, struct ipmi_read_fru_data_req *req,
+ uint8_t *fru_data)
+{
+ int ret;
+ uint8_t total_size;
+ uint16_t offset = 0;
+ struct ipmi_read_fru_data_rsp rsp;
+ int retry_count = 0;
+
+ if (req == NULL || fru_data == NULL) {
+ printk(BIOS_ERR, "%s failed, null pointer parameter\n",
+ __func__);
+ return CB_ERR;
+ }
+
+ total_size = req->count;
+ do {
+ if (req->count > CONFIG_IPMI_FRU_SINGLE_RW_SZ)
+ req->count = CONFIG_IPMI_FRU_SINGLE_RW_SZ;
+
+ while (retry_count <= MAX_FRU_BUSY_RETRY) {
+ ret = ipmi_kcs_message(port, IPMI_NETFN_STORAGE, 0x0,
+ IPMI_READ_FRU_DATA, (const unsigned char *) req,
+ sizeof(*req), (unsigned char *) &rsp, sizeof(rsp));
+ if (rsp.resp.completion_code == 0x81) {
+ /* Device is busy */
+ if (retry_count == MAX_FRU_BUSY_RETRY) {
+ printk(BIOS_ERR, "IPMI: %s command failed, "
+ "device busy timeout\n", __func__);
+ return CB_ERR;
+ }
+ printk(BIOS_ERR, "IPMI: FRU device is busy, "
+ "retry count:%d\n", retry_count);
+ retry_count++;
+ mdelay(READ_FRU_DATA_RETRY_INTERVAL_MS);
+ } else 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 CB_ERR;
+ }
+ break;
+ }
+ retry_count = 0;
+ memcpy(fru_data + offset, rsp.data, rsp.count);
+ offset += rsp.count;
+ total_size -= rsp.count;
+ req->fru_offset += rsp.count;
+ req->count = total_size;
+ } while (total_size > 0);
+
+ return CB_SUCCESS;
+}
+
+/* data: data to check, offset: offset to checksum. */
+static uint8_t checksum(uint8_t *data, int offset)
+{
+ uint8_t c = 0;
+ for (; offset > 0; offset--, data++)
+ c += *data;
+ return -c;
+}
+
+static uint8_t data2str(const uint8_t *frudata, char *stringdata, uint8_t length)
+{
+ uint8_t type;
+
+ /* bit[7:6] is the type code. */
+ type = ((frudata[0] & 0xc0) >> 6);
+ if (type != ASCII_8BIT) {
+ printk(BIOS_ERR, "%s typecode %d is unsupported, FRU string only "
+ "supports 8-bit ASCII + Latin 1 for now.\n", __func__, type);
+ return 0;
+ }
+ /* In the spec the string data is always the next byte to the type/length byte. */
+ memcpy(stringdata, frudata + 1, length);
+ stringdata[length] = '\0';
+ return length;
+}
+
+static void read_fru_board_info_area(const int port, const uint8_t id,
+ uint8_t offset, struct fru_board_info *info)
+{
+ uint8_t length;
+ struct ipmi_read_fru_data_req req;
+ uint8_t *data_ptr;
+
+ offset = offset * OFFSET_LENGTH_MULTIPLIER;
+ if (!offset)
+ return;
+ req.fru_device_id = id;
+ /* Read Board Info Area length first. */
+ req.fru_offset = offset + 1;
+ req.count = sizeof(length);
+ if (ipmi_read_fru(port, &req, &length) != CB_SUCCESS || !length) {
+ printk(BIOS_ERR, "%s failed, length: %d\n", __func__, length);
+ return;
+ }
+ length = length * OFFSET_LENGTH_MULTIPLIER;
+ data_ptr = (uint8_t *)malloc(length);
+ if (!data_ptr) {
+ printk(BIOS_ERR, "malloc %d bytes for board info failed\n", length);
+ return;
+ }
+
+ /* Read Board Info Area data. */
+ req.fru_offset = offset;
+ req.count = length;
+ if (ipmi_read_fru(port, &req, data_ptr) != CB_SUCCESS) {
+ printk(BIOS_ERR, "%s failed to read fru\n", __func__);
+ goto out;
+ }
+ if (checksum(data_ptr, length)) {
+ printk(BIOS_ERR, "Bad FRU board info checksum.\n");
+ goto out;
+ }
+ /* Read manufacturer string, bit[5:0] is the string length. */
+ length = NUM_DATA_BYTES(data_ptr[BOARD_MAN_TYPE_LEN_OFFSET]);
+ data_ptr += BOARD_MAN_TYPE_LEN_OFFSET;
+ if (length > 0) {
+ info->manufacturer = malloc(length + 1);
+ if (!info->manufacturer) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "manufacturer.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->manufacturer, length))
+ free(info->manufacturer);
+ }
+
+ /* Read product name string. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->product_name = malloc(length+1);
+ if (!info->product_name) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "product_name.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->product_name, length))
+ free(info->product_name);
+ }
+
+ /* Read serial number string. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->serial_number = malloc(length + 1);
+ if (!info->serial_number) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "serial_number.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->serial_number, length))
+ free(info->serial_number);
+ }
+
+ /* Read part number string. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->part_number = malloc(length + 1);
+ if (!info->part_number) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "part_number.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->part_number, length))
+ free(info->part_number);
+ }
+
+out:
+ free(data_ptr);
+}
+
+static void read_fru_product_info_area(const int port, const uint8_t id,
+ uint8_t offset, struct fru_product_info *info)
+{
+ uint8_t length;
+ struct ipmi_read_fru_data_req req;
+ uint8_t *data_ptr;
+
+ offset = offset * OFFSET_LENGTH_MULTIPLIER;
+ if (!offset)
+ return;
+
+ req.fru_device_id = id;
+ /* Read Product Info Area length first. */
+ req.fru_offset = offset + 1;
+ req.count = sizeof(length);
+ if (ipmi_read_fru(port, &req, &length) != CB_SUCCESS || !length) {
+ printk(BIOS_ERR, "%s failed, length: %d\n", __func__, length);
+ return;
+ }
+ length = length * OFFSET_LENGTH_MULTIPLIER;
+ data_ptr = (uint8_t *)malloc(length);
+ if (!data_ptr) {
+ printk(BIOS_ERR, "malloc %d bytes for product info failed\n", length);
+ return;
+ }
+
+ /* Read Product Info Area data. */
+ req.fru_offset = offset;
+ req.count = length;
+ if (ipmi_read_fru(port, &req, data_ptr) != CB_SUCCESS) {
+ printk(BIOS_ERR, "%s failed to read fru\n", __func__);
+ goto out;
+ }
+ if (checksum(data_ptr, length)) {
+ printk(BIOS_ERR, "Bad FRU product info checksum.\n");
+ goto out;
+ }
+ /* Read manufacturer string, bit[5:0] is the string length. */
+ length = NUM_DATA_BYTES(data_ptr[PRODUCT_MAN_TYPE_LEN_OFFSET]);
+ data_ptr += PRODUCT_MAN_TYPE_LEN_OFFSET;
+ if (length > 0) {
+ info->manufacturer = malloc(length + 1);
+ if (!info->manufacturer) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "manufacturer.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->manufacturer, length))
+ free(info->manufacturer);
+ }
+
+ /* Read product_name string. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->product_name = malloc(length + 1);
+ if (!info->product_name) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "product_name.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->product_name, length))
+ free(info->product_name);
+ }
+
+ /* Read product part/model number. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->product_partnumber = malloc(length + 1);
+ if (!info->product_partnumber) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "product_partnumber.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->product_partnumber, length))
+ free(info->product_partnumber);
+ }
+
+ /* Read product version string. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->product_version = malloc(length + 1);
+ if (!info->product_version) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "product_version.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->product_version, length))
+ free(info->product_version);
+ }
+
+ /* Read serial number string. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->serial_number = malloc(length + 1);
+ if (!info->serial_number) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "serial_number.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->serial_number, length))
+ free(info->serial_number);
+ }
+
+ /* Read asset tag string. */
+ data_ptr += length + 1;
+ length = NUM_DATA_BYTES(data_ptr[0]);
+ if (length > 0) {
+ info->asset_tag = malloc(length + 1);
+ if (!info->asset_tag) {
+ printk(BIOS_ERR, "%s failed to malloc %d bytes for "
+ "asset_tag.\n", __func__, length + 1);
+ goto out;
+ }
+ if (!data2str((const uint8_t *)data_ptr, info->asset_tag, length))
+ free(info->serial_number);
+ }
+
+out:
+ free(data_ptr);
+}
+
+void read_fru_areas(const int port, const uint8_t id, uint16_t offset,
+ struct fru_info_str *fru_info_str)
+{
+ struct ipmi_read_fru_data_req req;
+ struct ipmi_fru_common_hdr fru_common_hdr;
+
+ /* Set all the char pointers to 0 first, to avoid mainboard
+ * overwriting SMBIOS string with any non-NULL char pointer
+ * by accident. */
+ memset(fru_info_str, 0, sizeof(*fru_info_str));
+ req.fru_device_id = id;
+ req.fru_offset = offset;
+ req.count = sizeof(fru_common_hdr);
+ /* Read FRU common header first */
+ if (ipmi_read_fru(port, &req, (uint8_t *)&fru_common_hdr) == CB_SUCCESS) {
+ if (checksum((uint8_t *)&fru_common_hdr, sizeof(fru_common_hdr))) {
+ printk(BIOS_ERR, "Bad FRU common header checksum.\n");
+ return;
+ }
+ printk(BIOS_DEBUG, "FRU common header: format_version: %x\n"
+ "product_area_offset: %x\n"
+ "board_area_offset: %x\n"
+ "chassis_area_offset: %x\n",
+ fru_common_hdr.format_version,
+ fru_common_hdr.product_area_offset,
+ fru_common_hdr.board_area_offset,
+ fru_common_hdr.chassis_area_offset);
+ } else {
+ printk(BIOS_ERR, "Read FRU common header failed\n");
+ return;
+ }
+
+ read_fru_product_info_area(port, id, fru_common_hdr.product_area_offset,
+ &fru_info_str->prod_info);
+ read_fru_board_info_area(port, id, fru_common_hdr.board_area_offset,
+ &fru_info_str->board_info);
+ /* ToDo: Add read_fru_chassis_info_area(). */
+}
+
+void read_fru_one_area(const int port, const uint8_t id, uint16_t offset,
+ struct fru_info_str *fru_info_str, enum fru_area fru_area)
+{
+ struct ipmi_read_fru_data_req req;
+ struct ipmi_fru_common_hdr fru_common_hdr;
+
+ req.fru_device_id = id;
+ req.fru_offset = offset;
+ req.count = sizeof(fru_common_hdr);
+ if (ipmi_read_fru(port, &req, (uint8_t *)&fru_common_hdr) == CB_SUCCESS) {
+ if (checksum((uint8_t *)&fru_common_hdr, sizeof(fru_common_hdr))) {
+ printk(BIOS_ERR, "Bad FRU common header checksum.\n");
+ return;
+ }
+ printk(BIOS_DEBUG, "FRU common header: format_version: %x\n"
+ "product_area_offset: %x\n"
+ "board_area_offset: %x\n"
+ "chassis_area_offset: %x\n",
+ fru_common_hdr.format_version,
+ fru_common_hdr.product_area_offset,
+ fru_common_hdr.board_area_offset,
+ fru_common_hdr.chassis_area_offset);
+ } else {
+ printk(BIOS_ERR, "Read FRU common header failed\n");
+ return;
+ }
+
+ switch (fru_area) {
+ case PRODUCT_INFO_AREA:
+ memset(&fru_info_str->prod_info, 0, sizeof(fru_info_str->prod_info));
+ read_fru_product_info_area(port, id, fru_common_hdr.product_area_offset,
+ &fru_info_str->prod_info);
+ break;
+ case BOARD_INFO_AREA:
+ memset(&fru_info_str->board_info, 0, sizeof(fru_info_str->board_info));
+ read_fru_board_info_area(port, id, fru_common_hdr.board_area_offset,
+ &fru_info_str->board_info);
+ break;
+ /* ToDo: Add case for CHASSIS_INFO_AREA. */
+ default:
+ printk(BIOS_ERR, "Invalid fru_area: %d\n", fru_area);
+ break;
+ }
+}
diff --git a/src/drivers/ipmi/ipmi_kcs.h b/src/drivers/ipmi/ipmi_kcs.h
index b3775219c3..9a04377a0a 100644
--- a/src/drivers/ipmi/ipmi_kcs.h
+++ b/src/drivers/ipmi/ipmi_kcs.h
@@ -31,6 +31,7 @@
#define IPMI_NETFN_FIRMWARE 0x08
#define IPMI_NETFN_STORAGE 0x0a
+#define IPMI_READ_FRU_DATA 0x11
#define IPMI_NETFN_TRANSPORT 0x0c
#define IPMI_CMD_ACPI_POWERON 0x06
diff --git a/src/drivers/ipmi/ipmi_ops.h b/src/drivers/ipmi/ipmi_ops.h
index 77fc727cc8..dd12786b8e 100644
--- a/src/drivers/ipmi/ipmi_ops.h
+++ b/src/drivers/ipmi/ipmi_ops.h
@@ -49,6 +49,70 @@ struct ipmi_get_system_guid_rsp {
struct ipmi_rsp resp;
uint8_t data[16];
} __packed;
+
+struct ipmi_read_fru_data_req {
+ uint8_t fru_device_id;
+ uint16_t fru_offset;
+ uint8_t count; /* count to read, 1-based. */
+} __packed;
+
+struct ipmi_read_fru_data_rsp {
+ struct ipmi_rsp resp;
+ uint8_t count; /* count returned, 1-based. */
+ uint8_t data[CONFIG_IPMI_FRU_SINGLE_RW_SZ];
+} __packed;
+
+/* Platform Management FRU Information Storage Definition Spec. */
+#define PRODUCT_MAN_TYPE_LEN_OFFSET 3
+#define BOARD_MAN_TYPE_LEN_OFFSET 6
+
+struct ipmi_fru_common_hdr {
+ uint8_t format_version;
+ uint8_t internal_use_area_offset;
+ uint8_t chassis_area_offset;
+ uint8_t board_area_offset;
+ uint8_t product_area_offset;
+ uint8_t multirecord_area_offset;
+ uint8_t pad;
+ uint8_t checksum;
+} __packed;
+
+/* The fru_xxx_info only declares the strings that may be added to SMBIOS. */
+struct fru_product_info {
+ char *manufacturer;
+ char *product_name;
+ char *product_partnumber;
+ char *product_version;
+ char *serial_number;
+ char *asset_tag;
+};
+
+struct fru_board_info {
+ char *manufacturer;
+ char *product_name;
+ char *serial_number;
+ char *part_number;
+};
+
+struct fru_info_str {
+ struct fru_product_info prod_info;
+ struct fru_board_info board_info;
+};
+
+enum typecode {
+ BINARY = 0,
+ BCD_PLUS = 1,
+ ASCII_6BIT = 2,
+ ASCII_8BIT = 3,
+};
+
+enum fru_area {
+ INTERNAL_USE_AREA = 0,
+ CHASSIS_INFO_AREA = 1,
+ BOARD_INFO_AREA = 2,
+ PRODUCT_INFO_AREA = 3,
+ MULTIRECORD_INFO_AREA = 4,
+};
/*
* Initialize and start BMC FRB2 watchdog timer with the
* provided timer countdown and action values.
@@ -62,4 +126,13 @@ enum cb_err ipmi_stop_bmc_wdt(const int port);
/* IPMI get BMC system GUID and store it to parameter uuid.
* Returns CB_SUCCESS on success and CB_ERR if an error occurred */
enum cb_err ipmi_get_system_guid(const int port, uint8_t *uuid);
+
+/* Read all FRU inventory areas string data into fru_info_str with
+ * the same FRU device id. */
+void read_fru_areas(const int port, uint8_t id, uint16_t offset,
+ struct fru_info_str *fru_info_str);
+
+/* Read a particular FRU inventory area into fru_info_str. */
+void read_fru_one_area(const int port, uint8_t id, uint16_t offset,
+ struct fru_info_str *fru_info_str, enum fru_area fru_area);
#endif