diff options
Diffstat (limited to 'roms/skiboot/hw/ipmi/ipmi-fru.c')
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-fru.c | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/roms/skiboot/hw/ipmi/ipmi-fru.c b/roms/skiboot/hw/ipmi/ipmi-fru.c new file mode 100644 index 000000000..86c9ca0ce --- /dev/null +++ b/roms/skiboot/hw/ipmi/ipmi-fru.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Fill out firmware related FRUs (Field Replaceable Units) + * + * Copyright 2013-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <stdlib.h> +#include <string.h> +#include <ipmi.h> +#include <lock.h> +#include <opal.h> +#include <device.h> + +struct product_info { + char *manufacturer; + char *product; + char *part_no; + char *version; + char *serial_no; + char *asset_tag; +}; + +struct common_header { + u8 version; + u8 internal_offset; + u8 chassis_offset; + u8 board_offset; + u8 product_offset; + u8 multirecord_offset; + u8 pad; + u8 checksum; +} __packed; + +/* The maximum amount of FRU data we can store. */ +#define FRU_DATA_SIZE 256 + +/* We allocate two bytes at these locations in the data array to track + * state. */ +#define WRITE_INDEX 256 +#define REMAINING 257 + +/* The ASCII string encoding used only has 5 bits to encode length + * hence the maximum is 31 characters. */ +#define MAX_STR_LEN 31 + +static u8 fru_dev_id = 0; + +static int fru_insert_string(u8 *buf, char *str) +{ + int len = strlen(str); + + /* The ASCII type/length format only supports a string length + * between 2 and 31 characters. Zero characters is ok though + * as it indicates no data present. */ + if (len == 1 || len > MAX_STR_LEN) + return OPAL_PARAMETER; + + buf[0] = 0xc0 | len; + memcpy(&buf[1], str, len); + + return len + 1; +} + +static u8 fru_checksum(u8 *buf, int len) +{ + int i; + u8 checksum = 0; + + for(i = 0; i < len; i++) { + checksum += buf[i]; + } + checksum = ~checksum + 1; + return checksum; +} + +#define FRU_INSERT_STRING(x, y) \ + ({ rc = fru_insert_string(x, y); \ + { if (rc < 1) return OPAL_PARAMETER; } rc; }) + +static int fru_fill_product_info(u8 *buf, struct product_info *info, size_t size) +{ + size_t total_size = 11; + int index = 0; + int rc; + + total_size += strlen(info->manufacturer); + total_size += strlen(info->product); + total_size += strlen(info->part_no); + total_size += strlen(info->version); + total_size += strlen(info->serial_no); + total_size += strlen(info->asset_tag); + total_size += (8 - (total_size % 8)) % 8; + if (total_size > size) + return OPAL_PARAMETER; + + buf[index++] = 0x1; /* Version */ + buf[index++] = total_size / 8; /* Size */ + buf[index++] = 0; /* Language code (English) */ + + index += FRU_INSERT_STRING(&buf[index], info->manufacturer); + index += FRU_INSERT_STRING(&buf[index], info->product); + index += FRU_INSERT_STRING(&buf[index], info->part_no); + index += FRU_INSERT_STRING(&buf[index], info->version); + index += FRU_INSERT_STRING(&buf[index], info->serial_no); + index += FRU_INSERT_STRING(&buf[index], info->asset_tag); + + buf[index++] = 0xc1; /* End of data marker */ + memset(&buf[index], 0, total_size - index - 1); + index += total_size - index - 1; + buf[index] = fru_checksum(buf, index); + assert(index == total_size - 1); + + return total_size; +} + +static int fru_add(u8 *buf, int size) +{ + int len; + struct common_header common_hdr; + char *short_version; + struct product_info info = { + .manufacturer = (char *) "IBM", + .product = (char *) "skiboot", + .part_no = (char *) "", + .serial_no = (char *) "", + .asset_tag = (char *) "", + }; + + if (size < sizeof(common_hdr)) + return OPAL_PARAMETER; + + /* We currently only support adding the version number at the + * product information offset. We choose an offset of 64 bytes + * because that's what the standard recommends. */ + common_hdr.version = 1; + common_hdr.internal_offset = 0; + common_hdr.chassis_offset = 0; + common_hdr.board_offset = 0; + common_hdr.product_offset = 64/8; + common_hdr.multirecord_offset = 0; + common_hdr.pad = 0; + common_hdr.checksum = fru_checksum((u8 *) &common_hdr, sizeof(common_hdr) - 1); + memcpy(buf, &common_hdr, sizeof(common_hdr)); + + short_version = strdup(version); + info.version = short_version; + if (!strncmp(version, "skiboot-", 8)) + info.version = &short_version[8]; + + if (strlen(info.version) >= MAX_STR_LEN) { + if (info.version[MAX_STR_LEN] != '\0') + info.version[MAX_STR_LEN - 1] = '+'; + info.version[MAX_STR_LEN] = '\0'; + } + + len = fru_fill_product_info(&buf[64], &info, size - 64); + free(short_version); + if (len < 0) + return OPAL_PARAMETER; + + return len + 64; +} + +static void fru_write_complete(struct ipmi_msg *msg) +{ + u8 write_count = msg->data[0]; + u16 offset; + + msg->data[WRITE_INDEX] += write_count; + msg->data[REMAINING] -= write_count; + if (msg->data[REMAINING] == 0) + goto out; + + offset = msg->data[WRITE_INDEX]; + ipmi_init_msg(msg, IPMI_DEFAULT_INTERFACE, IPMI_WRITE_FRU, + fru_write_complete, NULL, + MIN(msg->data[REMAINING] + 3, IPMI_MAX_REQ_SIZE), 2); + + memmove(&msg->data[3], &msg->data[offset + 3], msg->req_size - 3); + + msg->data[0] = fru_dev_id; /* FRU Device ID */ + msg->data[1] = offset & 0xff; /* Offset LSB */ + msg->data[2] = (offset >> 8) & 0xff; /* Offset MSB */ + + ipmi_queue_msg(msg); + + return; + +out: + ipmi_free_msg(msg); +} + +static int fru_write(void) +{ + struct ipmi_msg *msg; + int len; + + /* We allocate FRU_DATA_SIZE + 5 bytes for the message: + * - 3 bytes for the the write FRU command header + * - FRU_DATA_SIZE bytes for FRU data + * - 2 bytes for offset & bytes remaining count + */ + msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_WRITE_FRU, + fru_write_complete, NULL, NULL, FRU_DATA_SIZE + 5, 2); + if (!msg) + return OPAL_RESOURCE; + + msg->data[0] = fru_dev_id; /* FRU Device ID */ + msg->data[1] = 0x0; /* Offset LSB (we always write a new common header) */ + msg->data[2] = 0x0; /* Offset MSB */ + len = fru_add(&msg->data[3], FRU_DATA_SIZE); + + if (len < 0) + return len; + + /* Three bytes for the actual FRU Data Command */ + msg->data[WRITE_INDEX] = 0; + msg->data[REMAINING] = len; + msg->req_size = MIN(len + 3, IPMI_MAX_REQ_SIZE); + return ipmi_queue_msg(msg); +} + +void ipmi_fru_init(u8 dev_id) +{ + fru_dev_id = dev_id; + fru_write(); + + return; +} |