[Skiboot] [PATCH] ipmi/fru: Add support for populating fru data

alistair at popple.id.au alistair at popple.id.au
Mon Dec 1 16:00:27 AEDT 2014


From: Alistair Popple <alistair at popple.id.au>

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 at popple.id.au>
---
 hw/ipmi/Makefile.inc        |   2 +-
 hw/ipmi/ipmi-fru.c          | 233 ++++++++++++++++++++++++++++++++++++++++++++
 hw/ipmi/test/Makefile.check |  34 +++++++
 hw/ipmi/test/run-fru.c      |  78 +++++++++++++++
 include/ipmi.h              |   8 +-
 platforms/astbmc/common.c   |   1 +
 6 files changed, 353 insertions(+), 3 deletions(-)
 create mode 100644 hw/ipmi/ipmi-fru.c
 create mode 100644 hw/ipmi/test/Makefile.check
 create mode 100644 hw/ipmi/test/run-fru.c

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);
-- 
1.8.3.2



More information about the Skiboot mailing list