| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* This file is part of the coreboot project. */ |
| |
| #include <console/console.h> |
| #include <string.h> |
| #include <delay.h> |
| #include <stdlib.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->asset_tag); |
| } |
| |
| 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; |
| } |
| } |