diff options
-rw-r--r-- | hw/ipmi/Makefile.inc | 2 | ||||
-rw-r--r-- | hw/ipmi/ipmi-fru.c | 233 | ||||
-rw-r--r-- | hw/ipmi/test/Makefile.check | 34 | ||||
-rw-r--r-- | hw/ipmi/test/run-fru.c | 78 | ||||
-rw-r--r-- | include/ipmi.h | 8 | ||||
-rw-r--r-- | platforms/astbmc/common.c | 1 |
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); |