diff options
-rw-r--r-- | src/drivers/ipmi/Kconfig | 10 | ||||
-rw-r--r-- | src/drivers/ipmi/Makefile.inc | 1 | ||||
-rw-r--r-- | src/drivers/ipmi/ipmi_fru.c | 409 | ||||
-rw-r--r-- | src/drivers/ipmi/ipmi_kcs.h | 1 | ||||
-rw-r--r-- | src/drivers/ipmi/ipmi_ops.h | 73 |
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 |