aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hw/ipmi/ipmi-fru.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hw/ipmi/ipmi-fru.c')
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-fru.c231
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;
+}