From b64d1426149d54e2926fab145d185f3c6fc8b4ed Mon Sep 17 00:00:00 2001 From: Alistair Popple Date: Mon, 1 Dec 2014 16:00:27 +1100 Subject: 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 Signed-off-by: Stewart Smith --- hw/ipmi/Makefile.inc | 2 +- hw/ipmi/ipmi-fru.c | 233 ++++++++++++++++++++++++++++++++++++++++++++ hw/ipmi/test/Makefile.check | 34 +++++++ hw/ipmi/test/run-fru.c | 78 +++++++++++++++ 4 files changed, 346 insertions(+), 1 deletion(-) 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 (limited to 'hw') 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 +#include +#include +#include +#include + +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 +#include +#include + +#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; +} -- cgit v1.1