aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlistair Popple <alistair@popple.id.au>2014-12-01 16:00:27 +1100
committerStewart Smith <stewart@linux.vnet.ibm.com>2014-12-02 17:41:48 +1100
commitb64d1426149d54e2926fab145d185f3c6fc8b4ed (patch)
tree2eff2295b4f022f29f20339a65ec57a8a6c51ad1
parent0f8acff8d61accde3b3cd69327a10a94bac92088 (diff)
downloadskiboot-b64d1426149d54e2926fab145d185f3c6fc8b4ed.zip
skiboot-b64d1426149d54e2926fab145d185f3c6fc8b4ed.tar.gz
skiboot-b64d1426149d54e2926fab145d185f3c6fc8b4ed.tar.bz2
ipmi/fru: Add support for populating fru data
This patch adds basic support for populating some fru data. Currently we only support adding the skiboot version number to the product information area. Signed-off-by: Alistair Popple <alistair@popple.id.au> Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
-rw-r--r--hw/ipmi/Makefile.inc2
-rw-r--r--hw/ipmi/ipmi-fru.c233
-rw-r--r--hw/ipmi/test/Makefile.check34
-rw-r--r--hw/ipmi/test/run-fru.c78
-rw-r--r--include/ipmi.h8
-rw-r--r--platforms/astbmc/common.c1
6 files changed, 353 insertions, 3 deletions
diff --git a/hw/ipmi/Makefile.inc b/hw/ipmi/Makefile.inc
index b27ef93..2d9f41f 100644
--- a/hw/ipmi/Makefile.inc
+++ b/hw/ipmi/Makefile.inc
@@ -1,5 +1,5 @@
SUBDIRS += hw/ipmi
-IPMI_OBJS = ipmi-rtc.o ipmi-power.o ipmi-opal.o
+IPMI_OBJS = ipmi-rtc.o ipmi-power.o ipmi-opal.o ipmi-fru.o
IPMI = hw/ipmi/built-in.o
$(IPMI): $(IPMI_OBJS:%=hw/ipmi/%)
diff --git a/hw/ipmi/ipmi-fru.c b/hw/ipmi/ipmi-fru.c
new file mode 100644
index 0000000..3c8ea03
--- /dev/null
+++ b/hw/ipmi/ipmi-fru.c
@@ -0,0 +1,233 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.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;
+
+#define min(x,y) ((x) < (y) ? x : y)
+
+/* 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;
+ char short_version[MAX_STR_LEN];
+ struct common_header common_hdr;
+ 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));
+
+ info.version = short_version;
+ strncpy(info.version, version, MAX_STR_LEN);
+ info.version[MAX_STR_LEN] = '\0';
+ if (info.version[MAX_STR_LEN - 1] != '\0')
+ info.version[MAX_STR_LEN - 1] = '+';
+
+ len = fru_fill_product_info(&buf[64], &info, size - 64);
+ 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];
+ msg->req_size = MIN(msg->data[REMAINING] + 3, IPMI_MAX_REQ_SIZE);
+ msg->cmd = IPMI_CMD(IPMI_WRITE_FRU);
+ msg->netfn = IPMI_NETFN(IPMI_WRITE_FRU) << 2;
+ msg->resp_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);
+
+ 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;
+}
diff --git a/hw/ipmi/test/Makefile.check b/hw/ipmi/test/Makefile.check
new file mode 100644
index 0000000..364a921
--- /dev/null
+++ b/hw/ipmi/test/Makefile.check
@@ -0,0 +1,34 @@
+# -*-Makefile-*-
+IPMI_TEST := hw/ipmi/test/run-fru
+
+check: $(IPMI_TEST:%=%-check) $(IPMI_TEST:%=%-gcov-run)
+
+coverage: $(IPMI_TEST:%=%-gcov-run)
+
+$(IPMI_TEST:%=%-gcov-run) : %-run: %
+ $<
+
+$(IPMI_TEST:%=%-check) : %-check: %
+ $(VALGRIND) $<
+
+$(IPMI_TEST) : % : %.c
+ $(HOSTCC) $(HOSTCFLAGS) -O0 -g -I include -I . -o $@ $<
+
+$(IPMI_TEST): % : %.d
+
+$(IPMI_TEST:%=%-gcov): %-gcov : %.c %
+ $(HOSTCC) $(HOSTCFLAGS) -fprofile-arcs -ftest-coverage -O0 -g -I include -I . -I libfdt -lgcov -o $@ $<
+
+$(IPMI_TEST:%=%-gcov): % : $(%.d:-gcov=)
+
+hw/ipmi/test/%.d: hw/ipmi/test/%.c
+ $(HOSTCC) $(HOSTCFLAGS) -I include -I . -I libfdt -M $< > $@
+
+-include $(wildcard hw/ipmi/test/*.d)
+
+clean: ipmi-test-clean
+
+ipmi-test-clean:
+ $(RM) -f hw/ipmi/test/*.[od] $(IPMI_TEST) $(IPMI_TEST:%=%-gcov)
+ $(RM) -f *.gcda *.gcno skiboot.info
+ $(RM) -rf coverage-report
diff --git a/hw/ipmi/test/run-fru.c b/hw/ipmi/test/run-fru.c
new file mode 100644
index 0000000..f147a28
--- /dev/null
+++ b/hw/ipmi/test/run-fru.c
@@ -0,0 +1,78 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "../ipmi-fru.c"
+
+int error = 0;
+
+const char version[] = "a-too-long-version-test-string-is-here";
+
+void ipmi_free_msg(struct ipmi_msg __unused *msg)
+{
+}
+
+struct ipmi_msg *ipmi_mkmsg(int __unused interface, uint32_t __unused code,
+ void __unused (*complete)(struct ipmi_msg *),
+ void __unused *user_data, void __unused *req_data, size_t __unused req_size,
+ size_t __unused resp_size)
+{
+ return NULL;
+}
+
+int ipmi_queue_msg(struct ipmi_msg __unused *msg)
+{
+ return 0;
+}
+
+void prlog(int __unused log_level, const __unused char* fmt, ...)
+{
+ return;
+}
+
+int main(void)
+{
+ u8 *buf;
+ int len;
+ struct product_info info = {
+ .manufacturer = (char *) "IBM",
+ .product = (char *) "skiboot",
+ .part_no = (char *) "hello",
+ .version = (char *) "12345",
+ .serial_no = (char *) "12345",
+ .asset_tag = (char *) "abcd",
+ };
+
+ buf = malloc(256);
+
+ len = fru_fill_product_info(buf, &info, 40);
+ assert(len > 0);
+
+ /* Make sure the checksum is right */
+ assert(!fru_checksum(buf, len));
+
+ /* This should fail (not enough space) */
+ assert(fru_fill_product_info(buf, &info, 39) < 0);
+
+ memset(buf, 0, 256);
+ assert(fru_add(buf, 256) > 0);
+
+ free(buf);
+
+ return 0;
+}
diff --git a/include/ipmi.h b/include/ipmi.h
index 42906e6..8c178f5 100644
--- a/include/ipmi.h
+++ b/include/ipmi.h
@@ -84,6 +84,7 @@
#define IPMI_NETFN_STORAGE 0x0a
#define IPMI_NETFN_APP 0x06
+#define IPMI_WRITE_FRU IPMI_CODE(IPMI_NETFN_STORAGE, 0x12)
#define IPMI_GET_SEL_INFO IPMI_CODE(IPMI_NETFN_STORAGE, 0x40)
#define IPMI_GET_SEL_TIME IPMI_CODE(IPMI_NETFN_STORAGE, 0x48)
#define IPMI_SET_SEL_TIME IPMI_CODE(IPMI_NETFN_STORAGE, 0x49)
@@ -109,8 +110,8 @@
#define IPMI_DEFAULT_INTERFACE 0
-#define IPMI_MAX_REQ_SIZE 64
-#define IPMI_MAX_RESP_SIZE 64
+#define IPMI_MAX_REQ_SIZE 60
+#define IPMI_MAX_RESP_SIZE 60
struct ipmi_backend;
struct ipmi_msg {
@@ -179,4 +180,7 @@ void ipmi_rtc_init(void);
/* Register ipmi host interface access callbacks */
void ipmi_opal_init(void);
+/* Populate fru data */
+void ipmi_fru_init(uint8_t fru_dev_id);
+
#endif
diff --git a/platforms/astbmc/common.c b/platforms/astbmc/common.c
index df79733..d6b5b07 100644
--- a/platforms/astbmc/common.c
+++ b/platforms/astbmc/common.c
@@ -52,6 +52,7 @@ void astbmc_init(void)
bt_init();
ipmi_rtc_init();
ipmi_opal_init();
+ ipmi_fru_init(0x01);
/* As soon as IPMI is up, inform BMC we are in "S0" */
ipmi_set_power_state(IPMI_PWR_SYS_S0_WORKING, IPMI_PWR_NOCHANGE);