Johnny Lin has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/37442 )
Change subject: drivers/ipmi: Add IPMI Read FRU functions ......................................................................
drivers/ipmi: Add IPMI Read FRU functions
Implemented according to IPMI "Platform Management FRU Information Storage Definition" specification v1.0 for reading FRU data Product Info Area and Board Info Area.
SMBIOS type 1 data can be updated with data read from Product Info Area, SMBIOS type 2 data can be updated with data read from Board Info Area data.
Tested on OCP Mono Lake.
Change-Id: Id6353f5ce3f7ddd3bb161b91364b3cf276d020b8 Signed-off-by: Johnny Lin johnny_lin@wiwynn.com --- M src/drivers/ipmi/Kconfig M src/drivers/ipmi/Makefile.inc A src/drivers/ipmi/ipmi_fru.c M src/drivers/ipmi/ipmi_kcs.h M src/drivers/ipmi/ipmi_ops.h 5 files changed, 419 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/42/37442/1
diff --git a/src/drivers/ipmi/Kconfig b/src/drivers/ipmi/Kconfig index 0f7152d..b686a84 100644 --- a/src/drivers/ipmi/Kconfig +++ b/src/drivers/ipmi/Kconfig @@ -8,3 +8,13 @@ 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 + the reading/writing the data over multiple commands. diff --git a/src/drivers/ipmi/Makefile.inc b/src/drivers/ipmi/Makefile.inc index 9d5b3d4..973fff8 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 0000000..3319704 --- /dev/null +++ b/src/drivers/ipmi/ipmi_fru.c @@ -0,0 +1,348 @@ +/* + * 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 + +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(30); + } 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; + } else + 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 * 8; + 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 * 8; + 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 = data_ptr[BOARD_MAN_TYPE_LEN_OFFSET] & 0x3f; + 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 = data_ptr[0] & 0x3f; + 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 = data_ptr[0] & 0x3f; + 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 = data_ptr[0] & 0x3f; + 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 * 8; + 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 * 8; + 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 = data_ptr[PRODUCT_MAN_TYPE_LEN_OFFSET] & 0x3f; + 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 = data_ptr[0] & 0x3f; + 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 = data_ptr[0] & 0x3f; + 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 = data_ptr[0] & 0x3f; + 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 = data_ptr[0] & 0x3f; + 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); + } + +out: + free(data_ptr); +} + +void read_fru_areas_data(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; + + /* Make sure to set all the string pointers to 0, to avoid + * mainboard overwriting SMBIOS string with any non-NULL + * char* 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); +} diff --git a/src/drivers/ipmi/ipmi_kcs.h b/src/drivers/ipmi/ipmi_kcs.h index b377521..9a04377 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 77fc727..9afc90e 100644 --- a/src/drivers/ipmi/ipmi_ops.h +++ b/src/drivers/ipmi/ipmi_ops.h @@ -49,6 +49,61 @@ 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. */ +} __packed; + +struct ipmi_read_fru_data_rsp { + struct ipmi_rsp resp; + uint8_t count; /* count returned. */ + 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; +}; + +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, +}; /* * Initialize and start BMC FRB2 watchdog timer with the * provided timer countdown and action values. @@ -62,4 +117,8 @@ /* 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 FRU inventory areas string data into fru_info_str. */ +void read_fru_areas_data(const int port, uint8_t id, uint16_t offset, + struct fru_info_str *fru_info_str); #endif