diff mbox

ipmi/fru: Add support for populating fru data

Message ID 1417410027-21925-1-git-send-email-alistair@popple.id.au
State Accepted, archived
Headers show

Commit Message

Alistair Popple Dec. 1, 2014, 5 a.m. UTC
From: Alistair Popple <alistair@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@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

Comments

Stewart Smith Dec. 2, 2014, 6:47 a.m. UTC | #1
alistair@popple.id.au writes:
> From: Alistair Popple <alistair@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@popple.id.au>

Looks good (and merged). One thing to keep in mind is that the version
string we generate can get pretty long (i've clocked 53 characters
once).. maybe we want to truncate the leading 'skiboot-' ?
Alistair Popple Dec. 2, 2014, 11:23 p.m. UTC | #2
Hi Stewart,

On Tue, 2 Dec 2014 17:47:19 Stewart Smith wrote:
> Looks good (and merged). One thing to keep in mind is that the version
> string we generate can get pretty long (i've clocked 53 characters
> once).. maybe we want to truncate the leading 'skiboot-' ?

We already truncate the version string (adding a + at the end if it's too 
long) however removing the leading skiboot- is a good idea. I will get a patch 
together to do that. In released versions the idea was we shouldn't have to 
truncate the version as a released skiboot shouldn't have the alistair-dirty-
blah-blah-blah at the end anyway.

Regards,

Alistair
diff mbox

Patch

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