aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hw/ipmi
diff options
context:
space:
mode:
authorAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
committerAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
commitaf1a266670d040d2f4083ff309d732d648afba2a (patch)
tree2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/hw/ipmi
parente02cda008591317b1625707ff8e115a4841aa889 (diff)
Add submodule dependency filesHEADmaster
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/hw/ipmi')
-rw-r--r--roms/skiboot/hw/ipmi/Makefile.inc9
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-attn.c100
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-fru.c231
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-info.c206
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-power.c85
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-rtc.c127
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-sel.c701
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-sensor.c160
-rw-r--r--roms/skiboot/hw/ipmi/ipmi-watchdog.c218
-rw-r--r--roms/skiboot/hw/ipmi/test/Makefile.check34
-rw-r--r--roms/skiboot/hw/ipmi/test/run-fru.c116
11 files changed, 1987 insertions, 0 deletions
diff --git a/roms/skiboot/hw/ipmi/Makefile.inc b/roms/skiboot/hw/ipmi/Makefile.inc
new file mode 100644
index 000000000..c6b36a2b3
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/Makefile.inc
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+SUBDIRS += hw/ipmi
+
+IPMI_OBJS = ipmi-rtc.o ipmi-power.o ipmi-fru.o ipmi-sel.o
+IPMI_OBJS += ipmi-watchdog.o ipmi-sensor.o ipmi-attn.o ipmi-info.o
+
+IPMI = hw/ipmi/built-in.a
+$(IPMI): $(IPMI_OBJS:%=hw/ipmi/%)
diff --git a/roms/skiboot/hw/ipmi/ipmi-attn.c b/roms/skiboot/hw/ipmi/ipmi-attn.c
new file mode 100644
index 000000000..280b2525f
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-attn.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * When everything is terrible, tell the FSP as much as possible as to why
+ *
+ * Copyright 2013-2019 IBM Corp.
+ */
+
+#include <errorlog.h>
+#include <ipmi.h>
+#include <pel.h>
+#include <platform.h>
+#include <processor.h>
+#include <sbe-p9.h>
+#include <skiboot.h>
+#include <stack.h>
+#include <timebase.h>
+#include <xscom.h>
+
+/* Use same attention SRC for BMC based machine */
+DEFINE_LOG_ENTRY(OPAL_RC_ATTN, OPAL_PLATFORM_ERR_EVT,
+ OPAL_ATTN, OPAL_PLATFORM_FIRMWARE,
+ OPAL_ERROR_PANIC, OPAL_ABNORMAL_POWER_OFF);
+
+/* Maximum buffer size to capture backtrace and other useful information */
+#define IPMI_TI_BUFFER_SIZE (IPMI_MAX_PEL_SIZE - PEL_MIN_SIZE)
+static char ti_buffer[IPMI_TI_BUFFER_SIZE];
+
+#define STACK_BUF_ENTRIES 20
+static struct bt_entry bt_buf[STACK_BUF_ENTRIES];
+
+/* Log eSEL event with OPAL backtrace */
+static void ipmi_log_terminate_event(const char *msg)
+{
+ struct bt_metadata metadata;
+ unsigned int ti_len;
+ unsigned int ti_size;
+ struct errorlog *elog_buf;
+
+ /* Fill OPAL version */
+ ti_len = snprintf(ti_buffer, IPMI_TI_BUFFER_SIZE,
+ "OPAL version : %s\n", version);
+
+ /* File information */
+ ti_len += snprintf(ti_buffer + ti_len, IPMI_TI_BUFFER_SIZE - ti_len,
+ "File info : %s\n", msg);
+ ti_size = IPMI_TI_BUFFER_SIZE - ti_len;
+
+ /* Backtrace */
+ backtrace_create(bt_buf, STACK_BUF_ENTRIES, &metadata);
+ metadata.token = OPAL_LAST + 1;
+ backtrace_print(bt_buf, &metadata, ti_buffer + ti_len, &ti_size, true);
+
+ /* Create eSEL event and commit */
+ elog_buf = opal_elog_create(&e_info(OPAL_RC_ATTN), 0);
+ log_append_data(elog_buf, (char *)&ti_buffer, ti_len + ti_size);
+ log_commit(elog_buf);
+}
+
+void __attribute__((noreturn)) ipmi_terminate(const char *msg)
+{
+ /* Log eSEL event */
+ if (ipmi_present())
+ ipmi_log_terminate_event(msg);
+
+ /*
+ * If mpipl is supported then trigger SBE interrupt
+ * to initiate mpipl
+ */
+ p9_sbe_terminate();
+
+ /*
+ * Trigger software xstop (OPAL TI). It will stop all the CPU threads
+ * moving them into quiesced state. OCC will collect all FIR data.
+ * Upon checkstop signal, BMC will then decide whether to reboot/IPL or
+ * not depending on AutoReboot policy, if any. This helps in cases
+ * where OPAL is crashing/terminating before host reaches to runtime.
+ * With OpenBMC AutoReboot policy, in such cases, it will make sure
+ * that system is moved to Quiesced state after 3 or so attempts to
+ * IPL. Without OPAL TI, OpenBMC will never know that OPAL is
+ * terminating and system would go into never ending IPL'ing loop.
+ *
+ * Once the system reaches to runtime, OpenBMC resets the boot counter.
+ * Hence next time when BMC receieves the OPAL TI, it will IPL the
+ * system if AutoReboot is enabled. We don't need to worry about self
+ * rebooting.
+ */
+
+ xscom_trigger_xstop();
+ /*
+ * Control will not reach here if software xstop has been supported and
+ * enabled. If not supported then fallback to cec reboot path below.
+ */
+
+ /* Reboot call */
+ if (platform.cec_reboot)
+ platform.cec_reboot();
+
+ while (1)
+ time_wait_ms(100);
+}
diff --git a/roms/skiboot/hw/ipmi/ipmi-fru.c b/roms/skiboot/hw/ipmi/ipmi-fru.c
new file mode 100644
index 000000000..86c9ca0ce
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-fru.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Fill out firmware related FRUs (Field Replaceable Units)
+ *
+ * Copyright 2013-2019 IBM Corp.
+ */
+
+#include <skiboot.h>
+#include <stdlib.h>
+#include <string.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;
+
+/* 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;
+ struct common_header common_hdr;
+ char *short_version;
+ 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));
+
+ short_version = strdup(version);
+ info.version = short_version;
+ if (!strncmp(version, "skiboot-", 8))
+ info.version = &short_version[8];
+
+ if (strlen(info.version) >= MAX_STR_LEN) {
+ if (info.version[MAX_STR_LEN] != '\0')
+ info.version[MAX_STR_LEN - 1] = '+';
+ info.version[MAX_STR_LEN] = '\0';
+ }
+
+ len = fru_fill_product_info(&buf[64], &info, size - 64);
+ free(short_version);
+ 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];
+ ipmi_init_msg(msg, IPMI_DEFAULT_INTERFACE, IPMI_WRITE_FRU,
+ fru_write_complete, NULL,
+ MIN(msg->data[REMAINING] + 3, IPMI_MAX_REQ_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);
+ if (!msg)
+ return OPAL_RESOURCE;
+
+ 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/roms/skiboot/hw/ipmi/ipmi-info.c b/roms/skiboot/hw/ipmi/ipmi-info.c
new file mode 100644
index 000000000..d93b59d7d
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-info.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Various bits of info retreived over IPMI
+ *
+ * Copyright 2018-2019 IBM Corp.
+ */
+
+#include <device.h>
+#include <skiboot.h>
+#include <stdlib.h>
+#include <ipmi.h>
+#include <mem_region-malloc.h>
+#include <opal.h>
+#include <timebase.h>
+
+/*
+ * Response data from IPMI Get device ID command (As defined in
+ * Section 20.1 Get Device ID Command - IPMI standard spec).
+ */
+struct ipmi_dev_id {
+ uint8_t dev_id;
+ uint8_t dev_revision;
+ uint8_t fw_rev1;
+ uint8_t fw_rev2;
+ uint8_t ipmi_ver;
+ uint8_t add_dev_support;
+ uint8_t manufactur_id[3];
+ uint8_t product_id[2];
+ uint8_t aux_fw_rev[4];
+};
+static struct ipmi_dev_id *ipmi_dev_id;
+
+/*
+ * Response data from IPMI Chassis Get System Boot Option (As defined in
+ * Section 28.13 Get System Boot Options Command - IPMI standard spec).
+ */
+struct ipmi_sys_boot_opt {
+ uint8_t param_version;
+ uint8_t param_valid;
+ /*
+ * Fields for OEM parameter 0x62. This parameter does not follow
+ * the normal layout and just has a single byte to signal if it
+ * is active or not.
+ */
+ uint8_t flag_set;
+};
+static struct ipmi_sys_boot_opt *ipmi_sys_boot_opt;
+
+/* Got response from BMC? */
+static bool bmc_info_waiting = false;
+static bool bmc_info_valid = false;
+static bool bmc_boot_opt_waiting = false;
+static bool bmc_boot_opt_valid = false;
+
+/* This will free ipmi_dev_id structure */
+void ipmi_dt_add_bmc_info(void)
+{
+ char buf[8];
+ struct dt_node *dt_fw_version;
+
+ while (bmc_info_waiting)
+ time_wait_ms(5);
+
+ if (!bmc_info_valid)
+ return;
+
+ dt_fw_version = dt_find_by_name(dt_root, "ibm,firmware-versions");
+ if (!dt_fw_version) {
+ free(ipmi_dev_id);
+ return;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf), "%x.%02x",
+ ipmi_dev_id->fw_rev1, ipmi_dev_id->fw_rev2);
+ dt_add_property_string(dt_fw_version, "bmc-firmware-version", buf);
+
+ free(ipmi_dev_id);
+}
+
+static void ipmi_get_bmc_info_resp(struct ipmi_msg *msg)
+{
+ bmc_info_waiting = false;
+
+ if (msg->cc != IPMI_CC_NO_ERROR) {
+ prlog(PR_ERR, "IPMI: IPMI_BMC_GET_DEVICE_ID cmd returned error"
+ " [rc : 0x%x]\n", msg->data[0]);
+ return;
+ }
+
+ /* ipmi_dev_id has optional fields */
+ if (msg->resp_size <= sizeof(struct ipmi_dev_id)) {
+ bmc_info_valid = true;
+ memcpy(ipmi_dev_id, msg->data, msg->resp_size);
+ } else {
+ prlog(PR_WARNING, "IPMI: IPMI_BMC_GET_DEVICE_ID unexpected response size\n");
+ }
+
+ ipmi_free_msg(msg);
+}
+
+int ipmi_get_bmc_info_request(void)
+{
+ int rc;
+ struct ipmi_msg *msg;
+
+ ipmi_dev_id = zalloc(sizeof(struct ipmi_dev_id));
+ assert(ipmi_dev_id);
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_BMC_GET_DEVICE_ID,
+ ipmi_get_bmc_info_resp, NULL, NULL,
+ 0, sizeof(struct ipmi_dev_id));
+ if (!msg)
+ return OPAL_NO_MEM;
+
+ msg->error = ipmi_get_bmc_info_resp;
+ prlog(PR_INFO, "IPMI: Requesting IPMI_BMC_GET_DEVICE_ID\n");
+ rc = ipmi_queue_msg(msg);
+ if (rc) {
+ prlog(PR_ERR, "IPMI: Failed to queue IPMI_BMC_GET_DEVICE_ID\n");
+ ipmi_free_msg(msg);
+ return rc;
+ }
+
+ bmc_info_waiting = true;
+ return rc;
+}
+
+/* This will free ipmi_sys_boot_opt structure */
+int ipmi_chassis_check_sbe_validation(void)
+{
+ int rc = -1;
+
+ while (bmc_boot_opt_waiting)
+ time_wait_ms(10);
+
+ if (!bmc_boot_opt_valid)
+ goto out;
+
+ if ((ipmi_sys_boot_opt->param_valid & 0x8) != 0)
+ goto out;
+ if (ipmi_sys_boot_opt->param_valid != 0x62)
+ goto out;
+
+ rc = ipmi_sys_boot_opt->flag_set;
+
+out:
+ free(ipmi_sys_boot_opt);
+ return rc;
+}
+
+static void ipmi_get_chassis_boot_opt_resp(struct ipmi_msg *msg)
+{
+ bmc_boot_opt_waiting = false;
+
+ if (msg->cc != IPMI_CC_NO_ERROR) {
+ prlog(PR_INFO, "IPMI: IPMI_CHASSIS_GET_BOOT_OPT cmd returned error"
+ " [rc : 0x%x]\n", msg->data[0]);
+ ipmi_free_msg(msg);
+ return;
+ }
+
+ if (msg->resp_size == sizeof(struct ipmi_sys_boot_opt)) {
+ bmc_boot_opt_valid = true;
+ memcpy(ipmi_sys_boot_opt, msg->data, msg->resp_size);
+ } else {
+ prlog(PR_WARNING, "IPMI: IPMI_CHASSIS_GET_BOOT_OPT unexpected response size\n");
+ }
+
+ ipmi_free_msg(msg);
+}
+
+int ipmi_get_chassis_boot_opt_request(void)
+{
+ int rc;
+ struct ipmi_msg *msg;
+ uint8_t req[] = {
+ 0x62, /* OEM parameter (SBE Validation on astbmc) */
+ 0x00, /* no set selector */
+ 0x00, /* no block selector */
+ };
+
+ ipmi_sys_boot_opt = zalloc(sizeof(struct ipmi_sys_boot_opt));
+ assert(ipmi_sys_boot_opt);
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_CHASSIS_GET_BOOT_OPT,
+ ipmi_get_chassis_boot_opt_resp, NULL, req,
+ sizeof(req), sizeof(struct ipmi_sys_boot_opt));
+ if (!msg) {
+ free(ipmi_sys_boot_opt);
+ return OPAL_NO_MEM;
+ }
+
+ msg->error = ipmi_get_chassis_boot_opt_resp;
+ prlog(PR_INFO, "IPMI: Requesting IPMI_CHASSIS_GET_BOOT_OPT\n");
+ rc = ipmi_queue_msg(msg);
+ if (rc) {
+ prlog(PR_ERR, "IPMI: Failed to queue IPMI_CHASSIS_GET_BOOT_OPT\n");
+ free(ipmi_sys_boot_opt);
+ ipmi_free_msg(msg);
+ return rc;
+ }
+
+ bmc_boot_opt_waiting = true;
+ return rc;
+}
diff --git a/roms/skiboot/hw/ipmi/ipmi-power.c b/roms/skiboot/hw/ipmi/ipmi-power.c
new file mode 100644
index 000000000..8101a8524
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-power.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Power as in electricity, not POWER as in POWER
+ *
+ * Copyright 2013-2019 IBM Corp.
+ */
+
+#include <skiboot.h>
+#include <stdlib.h>
+#include <ipmi.h>
+#include <opal.h>
+#include <timebase.h>
+
+static void ipmi_chassis_control_complete(struct ipmi_msg *msg)
+{
+ uint8_t request = msg->data[0];
+ uint8_t cc = msg->cc;
+
+ ipmi_free_msg(msg);
+ if (cc == IPMI_CC_NO_ERROR)
+ return;
+
+ prlog(PR_INFO, "IPMI: Chassis control request failed. "
+ "request=0x%02x, rc=0x%02x\n", request, cc);
+
+ if (ipmi_chassis_control(request)) {
+ prlog(PR_INFO, "IPMI: Failed to resend chassis control "
+ "request [0x%02x]\n", request);
+ }
+}
+
+int ipmi_chassis_control(uint8_t request)
+{
+ struct ipmi_msg *msg;
+
+ if (!ipmi_present())
+ return OPAL_CLOSED;
+
+ if (request > IPMI_CHASSIS_SOFT_SHUTDOWN)
+ return OPAL_PARAMETER;
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_CHASSIS_CONTROL,
+ ipmi_chassis_control_complete, NULL,
+ &request, sizeof(request), 0);
+ if (!msg)
+ return OPAL_HARDWARE;
+ /* Set msg->error callback function */
+ msg->error = ipmi_chassis_control_complete;
+
+ prlog(PR_INFO, "IPMI: sending chassis control request 0x%02x\n",
+ request);
+
+ return ipmi_queue_msg(msg);
+}
+
+int ipmi_set_power_state(uint8_t system, uint8_t device)
+{
+ struct ipmi_msg *msg;
+ struct {
+ uint8_t system;
+ uint8_t device;
+ } power_state;
+
+ if (!ipmi_present())
+ return OPAL_CLOSED;
+
+ power_state.system = system;
+ power_state.device = device;
+
+ if (system != IPMI_PWR_NOCHANGE)
+ power_state.system |= 0x80;
+ if (device != IPMI_PWR_NOCHANGE)
+ power_state.device |= 0x80;
+
+ msg = ipmi_mkmsg_simple(IPMI_SET_POWER_STATE, &power_state,
+ sizeof(power_state));
+
+ if (!msg)
+ return OPAL_HARDWARE;
+
+ prlog(PR_INFO, "IPMI: setting power state: sys %02x, dev %02x\n",
+ power_state.system, power_state.device);
+
+ return ipmi_queue_msg(msg);
+}
diff --git a/roms/skiboot/hw/ipmi/ipmi-rtc.c b/roms/skiboot/hw/ipmi/ipmi-rtc.c
new file mode 100644
index 000000000..52da2946c
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-rtc.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Talk to a Real Time Clock (RTC) over IPMI
+ *
+ * Copyright 2013-2015 IBM Corp.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ipmi.h>
+#include <time.h>
+#include <time-utils.h>
+#include <device.h>
+#include <opal.h>
+#include <rtc.h>
+
+static enum {idle, waiting, updated, error} time_status;
+
+static void get_sel_time_error(struct ipmi_msg *msg)
+{
+ time_status = error;
+ ipmi_free_msg(msg);
+}
+
+static void get_sel_time_complete(struct ipmi_msg *msg)
+{
+ struct tm tm;
+ le32 result;
+ time_t time;
+
+ memcpy(&result, msg->data, 4);
+ time = le32_to_cpu(result);
+ gmtime_r(&time, &tm);
+ rtc_cache_update(&tm);
+ time_status = updated;
+ ipmi_free_msg(msg);
+}
+
+static int64_t ipmi_get_sel_time(void)
+{
+ struct ipmi_msg *msg;
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_GET_SEL_TIME,
+ get_sel_time_complete, NULL, NULL, 0, 4);
+ if (!msg)
+ return OPAL_HARDWARE;
+
+ msg->error = get_sel_time_error;
+
+ return ipmi_queue_msg(msg);
+}
+
+static int64_t ipmi_set_sel_time(uint32_t _tv)
+{
+ struct ipmi_msg *msg;
+ const le32 tv = cpu_to_le32(_tv);
+
+ msg = ipmi_mkmsg_simple(IPMI_SET_SEL_TIME, (void*)&tv, sizeof(tv));
+ if (!msg)
+ return OPAL_HARDWARE;
+
+ return ipmi_queue_msg(msg);
+}
+
+static int64_t ipmi_opal_rtc_read(__be32 *__ymd, __be64 *__hmsm)
+{
+ int ret = 0;
+ uint32_t ymd;
+ uint64_t hmsm;
+
+ if (!__ymd || !__hmsm)
+ return OPAL_PARAMETER;
+
+ switch(time_status) {
+ case idle:
+ if (ipmi_get_sel_time() < 0)
+ return OPAL_HARDWARE;
+ time_status = waiting;
+ ret = OPAL_BUSY_EVENT;
+ break;
+
+ case waiting:
+ ret = OPAL_BUSY_EVENT;
+ break;
+
+ case updated:
+ rtc_cache_get_datetime(&ymd, &hmsm);
+ *__ymd = cpu_to_be32(ymd);
+ *__hmsm = cpu_to_be64(hmsm);
+ time_status = idle;
+ ret = OPAL_SUCCESS;
+ break;
+
+ case error:
+ time_status = idle;
+ ret = OPAL_HARDWARE;
+ break;
+ }
+
+ return ret;
+}
+
+static int64_t ipmi_opal_rtc_write(uint32_t year_month_day,
+ uint64_t hour_minute_second_millisecond)
+{
+ time_t t;
+ struct tm tm;
+
+ datetime_to_tm(year_month_day, hour_minute_second_millisecond, &tm);
+ t = mktime(&tm);
+ if (ipmi_set_sel_time(t))
+ return OPAL_HARDWARE;
+
+ return OPAL_SUCCESS;
+}
+
+void ipmi_rtc_init(void)
+{
+ struct dt_node *np = dt_new(opal_node, "rtc");
+ dt_add_property_strings(np, "compatible", "ibm,opal-rtc");
+
+ opal_register(OPAL_RTC_READ, ipmi_opal_rtc_read, 2);
+ opal_register(OPAL_RTC_WRITE, ipmi_opal_rtc_write, 2);
+
+ /* Initialise the rtc cache */
+ ipmi_get_sel_time();
+}
diff --git a/roms/skiboot/hw/ipmi/ipmi-sel.c b/roms/skiboot/hw/ipmi/ipmi-sel.c
new file mode 100644
index 000000000..215b8ba7d
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-sel.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2018 IBM Corp. */
+
+#define pr_fmt(fmt) "IPMI: " fmt
+#include <ccan/list/list.h>
+#include <ccan/str/str.h>
+#include <compiler.h>
+#include <errno.h>
+#include <skiboot.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ipmi.h>
+#include <device.h>
+#include <opal.h>
+#include <lock.h>
+#include <errorlog.h>
+#include <pel.h>
+#include <opal-msg.h>
+#include <debug_descriptor.h>
+#include <occ.h>
+#include <timebase.h>
+
+/* OEM SEL fields */
+#define SEL_OEM_ID_0 0x55
+#define SEL_OEM_ID_1 0x55
+#define SEL_RECORD_TYPE_OEM 0xC0
+#define SEL_RECORD_TYPE_EVENT 0x02
+
+#define SEL_NETFN_IBM 0x3a
+
+/* OEM SEL Commands */
+/* TODO: Move these to their respective source files */
+#define CMD_AMI_POWER 0x04
+#define CMD_AMI_PNOR_ACCESS 0x07
+#define CMD_AMI_OCC_RESET 0x0e
+#define CMD_HEARTBEAT 0xff
+
+/* XXX: Listed here for completeness, registered in libflash/ipmi-flash.c */
+#define CMD_OP_HIOMAP_EVENT 0x0f
+
+#define SOFT_OFF 0x00
+#define SOFT_REBOOT 0x01
+
+#define RELEASE_PNOR 0x00
+#define REQUEST_PNOR 0x01
+
+/* 32.1 SEL Event Records type */
+#define SEL_REC_TYPE_SYS_EVENT 0x02
+#define SEL_REC_TYPE_AMI_ESEL 0xDF
+
+/* OEM SEL generator ID for AMI */
+#define SEL_GENERATOR_ID_AMI 0x0020
+
+/* IPMI SEL version */
+#define SEL_EVM_VER_1 0x03
+#define SEL_EVM_VER_2 0x04
+
+/*
+ * Sensor type for System events
+ *
+ * Sensor information (type, number, etc) is passed to us via
+ * device tree. Currently we are using System Event type to
+ * log OPAL events.
+ */
+#define SENSOR_TYPE_SYS_EVENT 0x12
+
+/*
+ * 42.1 Event/Reading Type Codes
+ *
+ * Note that device hotplug and availability related events
+ * are not defined as we are not using those events type.
+ */
+#define SEL_EVENT_DIR_TYPE_UNSPECIFIED 0x00
+#define SEL_EVENT_DIR_TYPE_THRESHOLD 0x01
+#define SEL_EVENT_DIR_TYPE_STATE 0x03
+#define SEL_EVENT_DIR_TYPE_PREDICTIVE 0x04
+#define SEL_EVENT_DIR_TYPE_LIMIT 0x05
+#define SEL_EVENT_DIR_TYPE_PERFORMANCE 0x06
+#define SEL_EVENT_DIR_TYPE_TRANSITION 0x07
+#define SEL_EVENT_DIR_TYPE_OEM 0x70
+
+/*
+ * 42.1 Event/Reading Type Codes
+ */
+#define SEL_DATA1_AMI 0xAA
+#define SEL_DATA1_DEASSERTED 0x00
+#define SEL_DATA1_ASSERTED 0x01
+#define SEL_DATA1_OK 0x00
+#define SEL_DATA1_NON_CRIT_FROM_OK 0x01
+#define SEL_DATA1_CRIT_FROM_LESS_SEV 0x02
+#define SEL_DATA1_NON_REC_FROM_LESS_SEV 0x03
+#define SEL_DATA1_NON_CRIT 0x04
+#define SEL_DATA1_CRITICAL 0x05
+#define SEL_DATA1_NON_RECOVERABLE 0X06
+#define SEL_DATA1_MONITOR 0x07
+#define SEL_DATA1_INFORMATIONAL 0x08
+
+/* SEL Record Entry */
+struct sel_record {
+ le16 record_id;
+ uint8_t record_type;
+ le32 timestamp;
+ le16 generator_id;
+ uint8_t evm_ver;
+ uint8_t sensor_type;
+ uint8_t sensor_number;
+ uint8_t event_dir_type;
+ uint8_t event_data1;
+ uint8_t event_data2;
+ uint8_t event_data3;
+} __packed;
+
+static struct sel_record sel_record;
+
+struct oem_sel {
+ /* SEL header */
+ uint8_t id[2];
+ uint8_t type;
+ uint8_t timestamp[4];
+ uint8_t manuf_id[3];
+ /* OEM SEL data (6 bytes) follows */
+ uint8_t netfun;
+ uint8_t cmd;
+ uint8_t data[4];
+};
+
+#define ESEL_HDR_SIZE 7
+
+/* Used for sending PANIC events like abort() path */
+struct ipmi_sel_panic_msg {
+ bool busy;
+ struct ipmi_msg *msg;
+ struct lock lock;
+};
+static struct ipmi_sel_panic_msg ipmi_sel_panic_msg;
+
+static LIST_HEAD(sel_handlers);
+
+/* Forward declaration */
+static void ipmi_elog_poll(struct ipmi_msg *msg);
+
+/*
+ * Allocate IPMI message:
+ * For normal event, allocate memory using ipmi_mkmsg and for PANIC
+ * event, use pre-allocated buffer.
+ */
+static struct ipmi_msg *ipmi_sel_alloc_msg(struct errorlog *elog_buf)
+{
+ struct ipmi_msg *msg = NULL;
+
+ if (elog_buf->event_severity == OPAL_ERROR_PANIC) {
+ /* Called before initialization completes */
+ if (ipmi_sel_panic_msg.msg == NULL) {
+ ipmi_sel_init(); /* Try to allocate IPMI message */
+ if (ipmi_sel_panic_msg.msg == NULL)
+ return NULL;
+ }
+
+ if (ipmi_sel_panic_msg.busy == true)
+ return NULL;
+
+ lock(&ipmi_sel_panic_msg.lock);
+ msg = ipmi_sel_panic_msg.msg;
+ ipmi_sel_panic_msg.busy = true;
+ unlock(&ipmi_sel_panic_msg.lock);
+
+ ipmi_init_msg(msg, IPMI_DEFAULT_INTERFACE, IPMI_RESERVE_SEL,
+ ipmi_elog_poll, elog_buf, IPMI_MAX_REQ_SIZE, 2);
+ } else {
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_RESERVE_SEL,
+ ipmi_elog_poll, elog_buf, NULL,
+ IPMI_MAX_REQ_SIZE, 2);
+ }
+
+ return msg;
+}
+
+static void ipmi_sel_free_msg(struct ipmi_msg *msg)
+{
+ if (msg == ipmi_sel_panic_msg.msg) {
+ lock(&ipmi_sel_panic_msg.lock);
+ ipmi_sel_panic_msg.busy = false;
+ unlock(&ipmi_sel_panic_msg.lock);
+ } else {
+ ipmi_free_msg(msg);
+ }
+
+ msg = NULL;
+}
+
+/* Initialize eSEL record */
+static void ipmi_init_esel_record(void)
+{
+ memset(&sel_record, 0, sizeof(struct sel_record));
+ sel_record.record_type = SEL_REC_TYPE_AMI_ESEL;
+ sel_record.generator_id = cpu_to_le16(SEL_GENERATOR_ID_AMI);
+ sel_record.evm_ver = SEL_EVM_VER_2;
+ sel_record.sensor_type = SENSOR_TYPE_SYS_EVENT;
+ sel_record.sensor_number =
+ ipmi_get_sensor_number(SENSOR_TYPE_SYS_EVENT);
+ sel_record.event_dir_type = SEL_EVENT_DIR_TYPE_OEM;
+ sel_record.event_data1 = SEL_DATA1_AMI;
+}
+
+/* Update required fields in SEL record */
+static void ipmi_update_sel_record(uint8_t event_severity, uint16_t esel_record_id)
+{
+ sel_record.record_type = SEL_REC_TYPE_SYS_EVENT;
+ sel_record.event_data2 = (esel_record_id >> 8) & 0xff;
+ sel_record.event_data3 = esel_record_id & 0xff;
+
+ switch (event_severity) {
+ case OPAL_ERROR_PANIC:
+ sel_record.event_dir_type = SEL_EVENT_DIR_TYPE_TRANSITION;
+ sel_record.event_data1 = SEL_DATA1_CRITICAL;
+ break;
+ case OPAL_UNRECOVERABLE_ERR_GENERAL: /* Fall through */
+ case OPAL_UNRECOVERABLE_ERR_DEGRADE_PERF:
+ case OPAL_UNRECOVERABLE_ERR_LOSS_REDUNDANCY:
+ case OPAL_UNRECOVERABLE_ERR_LOSS_REDUNDANCY_PERF:
+ case OPAL_UNRECOVERABLE_ERR_LOSS_OF_FUNCTION:
+ sel_record.event_dir_type = SEL_EVENT_DIR_TYPE_TRANSITION;
+ sel_record.event_data1 = SEL_DATA1_NON_RECOVERABLE;
+ break;
+ case OPAL_PREDICTIVE_ERR_GENERAL: /* Fall through */
+ case OPAL_PREDICTIVE_ERR_DEGRADED_PERF:
+ case OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT:
+ case OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_BOOT_DEGRADE_PERF:
+ case OPAL_PREDICTIVE_ERR_LOSS_OF_REDUNDANCY:
+ sel_record.event_dir_type = SEL_EVENT_DIR_TYPE_PREDICTIVE;
+ sel_record.event_data1 = SEL_DATA1_NON_CRIT_FROM_OK;
+ break;
+ case OPAL_RECOVERED_ERR_GENERAL:
+ sel_record.event_dir_type = SEL_EVENT_DIR_TYPE_TRANSITION;
+ sel_record.event_data1 = SEL_DATA1_OK;
+ break;
+ case OPAL_INFO:
+ sel_record.event_dir_type = SEL_EVENT_DIR_TYPE_TRANSITION;
+ sel_record.event_data1 = SEL_DATA1_INFORMATIONAL;
+ break;
+ default:
+ sel_record.event_dir_type = SEL_EVENT_DIR_TYPE_STATE;
+ sel_record.event_data1 = SEL_DATA1_ASSERTED;
+ break;
+ }
+}
+
+static void ipmi_elog_error(struct ipmi_msg *msg)
+{
+ if (msg->cc == IPMI_LOST_ARBITRATION_ERR)
+ /* Retry due to SEL erase */
+ ipmi_queue_msg(msg);
+ else {
+ opal_elog_complete(msg->user_data, false);
+ ipmi_sel_free_msg(msg);
+ }
+}
+
+static void ipmi_log_sel_event_error(struct ipmi_msg *msg)
+{
+ if (msg->cc != IPMI_CC_NO_ERROR)
+ prlog(PR_INFO, "SEL: Failed to log SEL event\n");
+
+ ipmi_sel_free_msg(msg);
+}
+
+static void ipmi_log_sel_event_complete(struct ipmi_msg *msg)
+{
+ prlog(PR_INFO, "SEL: New event logged [ID : %x%x]\n", msg->data[1],
+ msg->data[0]);
+
+ ipmi_sel_free_msg(msg);
+}
+
+/* Log SEL event with eSEL record ID */
+static void ipmi_log_sel_event(struct ipmi_msg *msg, uint8_t event_severity,
+ uint16_t esel_record_id)
+{
+ /* Fill required SEL event fields */
+ ipmi_update_sel_record(event_severity, esel_record_id);
+
+ /* Fill IPMI message */
+ ipmi_init_msg(msg, IPMI_DEFAULT_INTERFACE, IPMI_ADD_SEL_EVENT,
+ ipmi_log_sel_event_complete, NULL,
+ sizeof(struct sel_record), 2);
+
+ /* Copy SEL data */
+ memcpy(msg->data, &sel_record, sizeof(struct sel_record));
+
+ msg->error = ipmi_log_sel_event_error;
+ ipmi_queue_msg_head(msg);
+}
+
+/* Goes through the required steps to add a complete eSEL:
+ *
+ * 1. Get a reservation
+ * 2. Add eSEL header
+ * 3. Partially add data to the SEL
+ *
+ * Because a reservation is needed we need to ensure eSEL's are added
+ * as a single transaction as concurrent/interleaved adds would cancel
+ * the reservation. We guarantee this by always adding our messages to
+ * the head of the transmission queue, blocking any other messages
+ * being sent until we have completed sending this message.
+ *
+ * There is still a very small chance that we will accidentally
+ * interleave a message if there is another one waiting at the head of
+ * the ipmi queue and another cpu calls the ipmi poller before we
+ * complete. However this should just cause a resevation cancelled
+ * error which we have to deal with anyway (eg. because there may be a
+ * SEL erase in progress) so it shouldn't cause any problems.
+ */
+static void ipmi_elog_poll(struct ipmi_msg *msg)
+{
+ static bool first = false;
+ static char pel_buf[IPMI_MAX_PEL_SIZE];
+ static size_t pel_size;
+ static size_t esel_size;
+ static int esel_index = 0;
+ int pel_index;
+ static unsigned int reservation_id = 0;
+ static unsigned int record_id = 0;
+ struct errorlog *elog_buf = (struct errorlog *) msg->user_data;
+ size_t req_size;
+
+ if (bmc_platform->sw->ipmi_oem_partial_add_esel == 0) {
+ prlog(PR_WARNING, "Dropped eSEL: BMC code is buggy/missing\n");
+ ipmi_sel_free_msg(msg);
+ return;
+ }
+
+ ipmi_init_esel_record();
+ if (msg->cmd == IPMI_CMD(IPMI_RESERVE_SEL)) {
+ first = true;
+ reservation_id = msg->data[0];
+ reservation_id |= msg->data[1] << 8;
+ if (!reservation_id) {
+ /*
+ * According to specification we should never
+ * get here, but just in case we do we cancel
+ * sending the message.
+ */
+ prerror("Invalid reservation id");
+ opal_elog_complete(elog_buf, false);
+ ipmi_sel_free_msg(msg);
+ return;
+ }
+
+ pel_size = create_pel_log(elog_buf, pel_buf, IPMI_MAX_PEL_SIZE);
+ esel_size = pel_size + sizeof(struct sel_record);
+ esel_index = 0;
+ record_id = 0;
+ } else {
+ record_id = msg->data[0];
+ record_id |= msg->data[1] << 8;
+ }
+
+ /* Start or continue the IPMI_PARTIAL_ADD_SEL */
+ if (esel_index >= esel_size) {
+ /*
+ * We're all done. Invalidate the resevation id to
+ * ensure we get an error if we cut in on another eSEL
+ * message.
+ */
+ reservation_id = 0;
+ esel_index = 0;
+
+ /* Log SEL event and free ipmi message */
+ ipmi_log_sel_event(msg, elog_buf->event_severity, record_id);
+
+ opal_elog_complete(elog_buf, true);
+ return;
+ }
+
+ if ((esel_size - esel_index) <= (IPMI_MAX_REQ_SIZE - ESEL_HDR_SIZE)) {
+ /* Last data to send */
+ msg->data[6] = 1;
+ req_size = esel_size - esel_index + ESEL_HDR_SIZE;
+ } else {
+ msg->data[6] = 0;
+ req_size = IPMI_MAX_REQ_SIZE;
+ }
+
+ ipmi_init_msg(msg, IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_partial_add_esel,
+ ipmi_elog_poll, elog_buf, req_size, 2);
+
+ msg->data[0] = reservation_id & 0xff;
+ msg->data[1] = (reservation_id >> 8) & 0xff;
+ msg->data[2] = record_id & 0xff;
+ msg->data[3] = (record_id >> 8) & 0xff;
+ msg->data[4] = esel_index & 0xff;
+ msg->data[5] = (esel_index >> 8) & 0xff;
+
+ if (first) {
+ first = false;
+ memcpy(&msg->data[ESEL_HDR_SIZE], &sel_record,
+ sizeof(struct sel_record));
+ esel_index = sizeof(struct sel_record);
+ msg->req_size = esel_index + ESEL_HDR_SIZE;
+ } else {
+ pel_index = esel_index - sizeof(struct sel_record);
+ memcpy(&msg->data[ESEL_HDR_SIZE], &pel_buf[pel_index],
+ msg->req_size - ESEL_HDR_SIZE);
+ esel_index += msg->req_size - ESEL_HDR_SIZE;
+ }
+
+ ipmi_queue_msg_head(msg);
+ return;
+}
+
+int ipmi_elog_commit(struct errorlog *elog_buf)
+{
+ struct ipmi_msg *msg;
+
+ /* Only log events that needs attention */
+ if (elog_buf->event_severity <
+ OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT ||
+ elog_buf->elog_origin != ORG_SAPPHIRE) {
+ prlog(PR_INFO, "dropping non severe PEL event\n");
+ opal_elog_complete(elog_buf, true);
+ return 0;
+ }
+
+ /*
+ * We pass a large request size in to mkmsg so that we have a
+ * large enough allocation to reuse the message to pass the
+ * PEL data via a series of partial add commands.
+ */
+ msg = ipmi_sel_alloc_msg(elog_buf);
+ if (!msg) {
+ opal_elog_complete(elog_buf, false);
+ return OPAL_RESOURCE;
+ }
+
+ msg->error = ipmi_elog_error;
+ msg->req_size = 0;
+ if (elog_buf->event_severity == OPAL_ERROR_PANIC) {
+ ipmi_queue_msg_sync(msg);
+
+ /*
+ * eSEL logs are split into multiple smaller chunks and sent
+ * to BMC. Lets wait until we finish sending all the chunks
+ * to BMC.
+ */
+ while (ipmi_sel_panic_msg.busy != false) {
+ if (msg->backend->poll)
+ msg->backend->poll();
+ time_wait_ms(10);
+ }
+ } else {
+ ipmi_queue_msg(msg);
+ }
+
+ return 0;
+}
+
+#define ACCESS_DENIED 0x00
+#define ACCESS_GRANTED 0x01
+
+static void sel_pnor(uint8_t access, void *context __unused)
+{
+ struct ipmi_msg *msg;
+ uint8_t granted = ACCESS_GRANTED;
+
+ switch (access) {
+ case REQUEST_PNOR:
+ prlog(PR_NOTICE, "PNOR access requested\n");
+ if (bmc_platform->sw->ipmi_oem_pnor_access_status == 0) {
+ /**
+ * @fwts-label PNORAccessYeahButNoBut
+ * @fwts-advice OPAL doesn't know that the BMC supports
+ * PNOR access commands. This will be a bug in the OPAL
+ * support for this BMC.
+ */
+ prlog(PR_ERR, "PNOR BUG: access requested but BMC doesn't support request\n");
+ break;
+ }
+
+ granted = flash_reserve();
+ if (granted)
+ occ_pnor_set_owner(PNOR_OWNER_EXTERNAL);
+ /* Ack the request */
+ msg = ipmi_mkmsg_simple(bmc_platform->sw->ipmi_oem_pnor_access_status, &granted, 1);
+ ipmi_queue_msg(msg);
+ break;
+ case RELEASE_PNOR:
+ prlog(PR_NOTICE, "PNOR access released\n");
+ flash_release();
+ occ_pnor_set_owner(PNOR_OWNER_HOST);
+ break;
+ default:
+ /**
+ * @fwts-label InvalidPNORAccessRequest
+ * @fwts-advice In negotiating PNOR access with BMC, we
+ * got an odd/invalid request from the BMC. Likely a bug
+ * in OPAL/BMC interaction.
+ */
+ prlog(PR_ERR, "invalid PNOR access requested: %02x\n",
+ access);
+ }
+}
+
+static void sel_power(uint8_t power, void *context __unused)
+{
+ switch (power) {
+ case SOFT_OFF:
+ prlog(PR_NOTICE, "Soft shutdown requested\n");
+ if (opal_booting() && platform.cec_power_down) {
+ prlog(PR_NOTICE, "Host not up, shutting down now\n");
+ platform.cec_power_down(IPMI_CHASSIS_PWR_DOWN);
+ } else {
+ opal_queue_msg(OPAL_MSG_SHUTDOWN, NULL, NULL,
+ cpu_to_be64(SOFT_OFF));
+ }
+
+ break;
+ case SOFT_REBOOT:
+ prlog(PR_NOTICE, "Soft reboot requested\n");
+ if (opal_booting() && platform.cec_reboot) {
+ prlog(PR_NOTICE, "Host not up, rebooting now\n");
+ platform.cec_reboot();
+ } else {
+ opal_queue_msg(OPAL_MSG_SHUTDOWN, NULL, NULL,
+ cpu_to_be64(SOFT_REBOOT));
+ }
+
+ break;
+ default:
+ prlog(PR_WARNING, "requested bad power state: %02x\n",
+ power);
+ }
+}
+
+static void sel_heartbeat(uint8_t heartbeat, void *context __unused)
+{
+ /* There is only one sub-command so no processing needed */
+ prlog(PR_DEBUG, "BMC issued heartbeat command: %02x\n",
+ heartbeat);
+}
+
+static uint32_t occ_sensor_id_to_chip(uint8_t sensor, uint32_t *chip)
+{
+ struct dt_node *node, *bmc_node, *sensors_node;
+
+ /* Default chip id */
+ *chip = 0;
+
+ bmc_node = dt_find_by_name(dt_root, "bmc");
+ if (!bmc_node)
+ return 0;
+
+ sensors_node = dt_find_by_name(bmc_node, "sensors");
+ if (!sensors_node)
+ return 0;
+
+ node = dt_find_by_name_addr(sensors_node, "sensor", sensor);
+ if (!node) {
+ prlog(PR_DEBUG, "Could not find OCC sensor node. Id : %d\n",
+ (u32)sensor);
+ return 0;
+ }
+
+ if (!dt_has_node_property(node, "ibm,chip-id", NULL)) {
+ prlog(PR_DEBUG, "Could not find chip-id for OCC sensor : %d\n",
+ (u32)sensor);
+ return 0;
+ }
+
+ *chip = dt_get_chip_id(node);
+ return 0;
+}
+
+static void sel_occ_reset(uint8_t sensor, void *context __unused)
+{
+ uint32_t chip;
+ int rc;
+
+ rc = occ_sensor_id_to_chip(sensor, &chip);
+ if (rc) {
+ /**
+ * @fwts-label: SELUnknownOCCReset
+ * @fwts-advice: Likely bug in what sent us the OCC reset.
+ */
+ prlog(PR_ERR, "SEL message to reset an unknown OCC "
+ "(sensor ID 0x%02x)\n", sensor);
+ return;
+ }
+
+ prd_occ_reset(chip);
+}
+
+struct ipmi_sel_handler {
+ uint8_t oem_cmd;
+ void (*fn)(uint8_t data, void *context);
+ void *context;
+ struct list_node node;
+};
+
+int ipmi_sel_register(uint8_t oem_cmd,
+ void (*fn)(uint8_t data, void *context),
+ void *context)
+{
+ struct ipmi_sel_handler *handler;
+
+ list_for_each(&sel_handlers, handler, node) {
+ if (handler->oem_cmd == oem_cmd) {
+ prerror("Handler for SEL command 0x%02x already registered\n",
+ oem_cmd);
+ return -EINVAL;
+ }
+ }
+
+ handler = malloc(sizeof(*handler));
+ if (!handler)
+ return -ENOMEM;
+
+ handler->oem_cmd = oem_cmd;
+ handler->fn = fn;
+ handler->context = context;
+
+ list_add(&sel_handlers, &handler->node);
+
+ return 0;
+}
+
+void ipmi_sel_init(void)
+{
+ int rc;
+
+ /* Already done */
+ if (ipmi_sel_panic_msg.msg != NULL)
+ return;
+
+ memset(&ipmi_sel_panic_msg, 0, sizeof(struct ipmi_sel_panic_msg));
+ ipmi_sel_panic_msg.msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ IPMI_RESERVE_SEL, ipmi_elog_poll,
+ NULL, NULL, IPMI_MAX_REQ_SIZE, 2);
+
+ /* Hackishly register these old-style handlers here for now */
+ /* TODO: Move them to their appropriate source files */
+ rc = ipmi_sel_register(CMD_AMI_POWER, sel_power, NULL);
+ if (rc < 0) {
+ prerror("Failed to register SEL handler for %s",
+ stringify(CMD_AMI_POWER));
+ }
+
+ rc = ipmi_sel_register(CMD_AMI_OCC_RESET, sel_occ_reset, NULL);
+ if (rc < 0) {
+ prerror("Failed to register SEL handler for %s",
+ stringify(CMD_AMI_OCC_RESET));
+ }
+
+ rc = ipmi_sel_register(CMD_AMI_PNOR_ACCESS, sel_pnor, NULL);
+ if (rc < 0) {
+ prerror("Failed to register SEL handler for %s",
+ stringify(CMD_AMI_PNOR_ACCESS));
+ }
+
+ rc = ipmi_sel_register(CMD_HEARTBEAT, sel_heartbeat, NULL);
+ if (rc < 0) {
+ prerror("Failed to register SEL handler for %s",
+ stringify(CMD_HEARTBEAT));
+ }
+}
+
+void ipmi_parse_sel(struct ipmi_msg *msg)
+{
+ struct ipmi_sel_handler *handler;
+ struct oem_sel sel;
+
+ assert(msg->resp_size <= 16);
+
+ memcpy(&sel, msg->data, msg->resp_size);
+
+ /* We do not process system event records */
+ if (sel.type == SEL_RECORD_TYPE_EVENT) {
+ prlog(PR_INFO, "dropping System Event Record SEL\n");
+ return;
+ }
+
+ prlog(PR_DEBUG, "SEL received (%d bytes, netfn %d, cmd %d)\n",
+ msg->resp_size, sel.netfun, sel.cmd);
+
+ /* Only accept OEM SEL messages */
+ if (sel.id[0] != SEL_OEM_ID_0 || sel.id[1] != SEL_OEM_ID_1 ||
+ sel.type != SEL_RECORD_TYPE_OEM) {
+ prlog(PR_WARNING, "unknown SEL %02x%02x (type %02x)\n",
+ sel.id[0], sel.id[1], sel.type);
+ return;
+ }
+
+ list_for_each(&sel_handlers, handler, node) {
+ if (handler->oem_cmd == sel.cmd) {
+ handler->fn(sel.data[0], handler->context);
+ return;
+ }
+ }
+
+ prlog(PR_WARNING, "unknown OEM SEL command %02x received\n", sel.cmd);
+}
diff --git a/roms/skiboot/hw/ipmi/ipmi-sensor.c b/roms/skiboot/hw/ipmi/ipmi-sensor.c
new file mode 100644
index 000000000..857b789e4
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-sensor.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2017 IBM Corp. */
+
+#include <device.h>
+#include <ipmi.h>
+#include <opal.h>
+#include <skiboot.h>
+#include <string.h>
+#include <stdbool.h>
+
+#define IPMI_WRITE_SENSOR (1 << 0)
+
+#define FW_PROGRESS_SENSOR_TYPE 0x0F
+#define BOOT_COUNT_SENSOR_TYPE 0xC3
+
+static int16_t sensors[MAX_IPMI_SENSORS];
+
+static bool sensors_present = false;
+
+struct set_sensor_req {
+ u8 sensor_number;
+ u8 operation;
+ u8 sensor_reading;
+ u8 assertion_mask[2];
+ u8 deassertion_mask[2];
+ u8 event_data[3];
+};
+
+static bool ipmi_sensor_type_present(uint8_t sensor_type)
+{
+ const struct dt_property *type_prop;
+ uint8_t type;
+ struct dt_node *node;
+
+ dt_for_each_compatible(dt_root, node, "ibm,ipmi-sensor") {
+ type_prop = dt_find_property(node, "ipmi-sensor-type");
+ if (!type_prop) {
+ prlog(PR_ERR, "IPMI: sensor doesn't have ipmi-sensor-type\n");
+ continue;
+ }
+
+ type = (uint8_t)dt_property_get_cell(type_prop, 0);
+ if (type == sensor_type)
+ return true;
+ }
+ return false;
+}
+
+uint8_t ipmi_get_sensor_number(uint8_t sensor_type)
+{
+ assert(sensor_type < MAX_IPMI_SENSORS);
+ return sensors[sensor_type];
+}
+
+int ipmi_set_boot_count(void)
+{
+ struct set_sensor_req req;
+ struct ipmi_msg *msg;
+ int boot_count_sensor;
+
+ if (!sensors_present)
+ return OPAL_UNSUPPORTED;
+
+ if (!ipmi_present())
+ return OPAL_CLOSED;
+
+ if (!ipmi_sensor_type_present(BOOT_COUNT_SENSOR_TYPE))
+ return OPAL_HARDWARE;
+
+ boot_count_sensor = sensors[BOOT_COUNT_SENSOR_TYPE];
+
+ if (boot_count_sensor < 0) {
+ prlog(PR_DEBUG, "IPMI: boot count set but not present\n");
+ return OPAL_HARDWARE;
+ }
+
+ memset(&req, 0, sizeof(req));
+
+ req.sensor_number = boot_count_sensor;
+ req.operation = IPMI_WRITE_SENSOR;
+ req.sensor_reading = 0x00;
+ req.assertion_mask[0] = 0x02;
+
+ msg = ipmi_mkmsg_simple(IPMI_SET_SENSOR_READING, &req, sizeof(req));
+ if (!msg)
+ return OPAL_HARDWARE;
+
+ printf("IPMI: Resetting boot count on successful boot\n");
+
+ return ipmi_queue_msg(msg);
+}
+
+int ipmi_set_fw_progress_sensor(uint8_t state)
+{
+ struct ipmi_msg *msg;
+ struct set_sensor_req request;
+ int fw_sensor_num;
+
+ if (!sensors_present)
+ return OPAL_UNSUPPORTED;
+
+ if (!ipmi_present())
+ return OPAL_CLOSED;
+
+ if (!ipmi_sensor_type_present(FW_PROGRESS_SENSOR_TYPE))
+ return OPAL_HARDWARE;
+
+ fw_sensor_num = sensors[FW_PROGRESS_SENSOR_TYPE];
+
+ if (fw_sensor_num < 0) {
+ prlog(PR_DEBUG, "IPMI: fw progress set but not present\n");
+ return OPAL_HARDWARE;
+ }
+
+ memset(&request, 0, sizeof(request));
+
+ request.sensor_number = fw_sensor_num;
+ request.operation = 0xa0; /* Set event data bytes, assertion bits */
+ request.assertion_mask[0] = 0x04; /* Firmware progress offset */
+ request.event_data[0] = 0xc2;
+ request.event_data[1] = state;
+
+ prlog(PR_INFO, "IPMI: setting fw progress sensor %02x to %02x\n",
+ request.sensor_number, request.event_data[1]);
+
+ msg = ipmi_mkmsg_simple(IPMI_SET_SENSOR_READING, &request,
+ sizeof(request));
+ if (!msg)
+ return OPAL_HARDWARE;
+
+ return ipmi_queue_msg(msg);
+}
+
+void ipmi_sensor_init(void)
+{
+ const struct dt_property *type_prop, *num_prop;
+ uint8_t num, type;
+ struct dt_node *n;
+
+ memset(sensors, -1, sizeof(sensors));
+
+ dt_for_each_compatible(dt_root, n, "ibm,ipmi-sensor") {
+ type_prop = dt_find_property(n, "ipmi-sensor-type");
+ if (!type_prop) {
+ prerror("IPMI: sensor doesn't have ipmi-sensor-type\n");
+ continue;
+ }
+
+ num_prop = dt_find_property(n, "reg");
+ if (!num_prop) {
+ prerror("IPMI: sensor doesn't have reg property\n");
+ continue;
+ }
+ num = (uint8_t)dt_property_get_cell(num_prop, 0);
+ type = (uint8_t)dt_property_get_cell(type_prop, 0);
+ assert(type < MAX_IPMI_SENSORS);
+ sensors[type] = num;
+ }
+ sensors_present = true;
+}
diff --git a/roms/skiboot/hw/ipmi/ipmi-watchdog.c b/roms/skiboot/hw/ipmi/ipmi-watchdog.c
new file mode 100644
index 000000000..dc0a9e5b4
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/ipmi-watchdog.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: Apache-2.0
+/*
+ * Copyright 2013-2018 IBM Corp.
+ * Copyright 2018 Google Corp.
+ */
+
+#include <stdlib.h>
+#include <ipmi.h>
+#include <lock.h>
+#include <opal.h>
+#include <device.h>
+#include <timer.h>
+#include <timebase.h>
+#include <pool.h>
+#include <skiboot.h>
+
+#define TIMER_USE_DONT_LOG 0x80
+#define TIMER_USE_DONT_STOP 0x40
+#define TIMER_USE_POST 0x02
+
+/* WDT expiration actions */
+#define WDT_PRETIMEOUT_SMI 0x10
+#define WDT_RESET_ACTION 0x01
+#define WDT_NO_ACTION 0x00
+
+/* IPMI defined custom completion codes for the watchdog */
+#define WDT_CC_OK 0x00
+#define WDT_CC_NOT_INITIALIZED 0x80
+
+/* Flags used for IPMI callbacks */
+#define WDT_SET_DO_RESET 0x01
+#define WDT_RESET_NO_REINIT 0x01
+
+/* How long to set the overall watchdog timeout for. In units of
+ * 100ms. If the timer is not reset within this time the watchdog
+ * expiration action will occur. */
+#define WDT_TIMEOUT 600
+
+/* How often to reset the timer using schedule_timer(). Too short and
+we risk accidentally resetting the system due to opal_run_pollers() not
+being called in time, too short and we waste time resetting the wdt
+more frequently than necessary. */
+#define WDT_MARGIN 300
+
+static struct timer wdt_timer;
+static bool wdt_stopped;
+static bool wdt_ticking;
+
+/* Saved values from the last watchdog set action */
+static uint8_t last_action;
+static uint16_t last_count;
+static uint8_t last_pretimeout;
+
+static void reset_wdt(struct timer *t, void *data, uint64_t now);
+
+static void set_wdt_complete(struct ipmi_msg *msg)
+{
+ const uintptr_t flags = (uintptr_t)msg->user_data;
+
+ if (flags & WDT_SET_DO_RESET) {
+ /* Make sure the reset action does not create a loop and
+ * perform a reset in the case where the BMC send an
+ * uninitialized error. */
+ reset_wdt(NULL, (void *)WDT_RESET_NO_REINIT, 0);
+ }
+
+ ipmi_free_msg(msg);
+}
+
+static void set_wdt(uint8_t action, uint16_t count, uint8_t pretimeout,
+ bool dont_stop, bool do_reset)
+{
+ struct ipmi_msg *ipmi_msg;
+ uintptr_t completion_flags = 0;
+
+ if (do_reset)
+ completion_flags |= WDT_SET_DO_RESET;
+
+ /* Save the values prior to issuing the set operation so that we can
+ * re-initialize the watchdog in error cases. */
+ last_action = action;
+ last_count = count;
+ last_pretimeout = pretimeout;
+
+ ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_SET_WDT,
+ set_wdt_complete, NULL, NULL, 6, 0);
+ if (!ipmi_msg) {
+ prerror("Unable to allocate set wdt message\n");
+ return;
+ }
+ ipmi_msg->error = set_wdt_complete;
+ ipmi_msg->user_data = (void *)completion_flags;
+ ipmi_msg->data[0] = TIMER_USE_POST |
+ TIMER_USE_DONT_LOG |
+ (dont_stop ? TIMER_USE_DONT_STOP : 0);
+ ipmi_msg->data[1] = action; /* Timer Actions */
+ ipmi_msg->data[2] = pretimeout; /* Pre-timeout Interval */
+ ipmi_msg->data[3] = 0; /* Timer Use Flags */
+ ipmi_msg->data[4] = count & 0xff; /* Initial countdown (lsb) */
+ ipmi_msg->data[5] = (count >> 8) & 0xff; /* Initial countdown (msb) */
+ ipmi_queue_msg(ipmi_msg);
+}
+
+static void reset_wdt_complete(struct ipmi_msg *msg)
+{
+ const uintptr_t flags = (uintptr_t)msg->user_data;
+ uint64_t reset_delay_ms = (WDT_TIMEOUT - WDT_MARGIN) * 100;
+
+ if (msg->cc == WDT_CC_NOT_INITIALIZED &&
+ !(flags & WDT_RESET_NO_REINIT)) {
+ /* If our timer was not initialized on the BMC side, we should
+ * perform a single attempt to set it up again. */
+ set_wdt(last_action, last_count, last_pretimeout, true, true);
+ } else if (msg->cc != WDT_CC_OK) {
+ /* Use a short (10s) timeout before performing the next reset
+ * if we encounter an unknown error. This makes sure that we
+ * are able to reset and re-initialize the timer since it might
+ * expire. */
+ reset_delay_ms = 10 * 1000;
+ }
+
+ /* If we are inside of skiboot we need to periodically restart the
+ * timer. Reschedule a reset so it happens before the timeout. */
+ if (wdt_ticking)
+ schedule_timer(&wdt_timer, msecs_to_tb(reset_delay_ms));
+
+ ipmi_free_msg(msg);
+}
+
+static struct ipmi_msg *wdt_reset_mkmsg(void)
+{
+ struct ipmi_msg *ipmi_msg;
+
+ ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_RESET_WDT,
+ reset_wdt_complete, NULL, NULL, 0, 0);
+ if (!ipmi_msg) {
+ prerror("Unable to allocate reset wdt message\n");
+ return NULL;
+ }
+ ipmi_msg->error = reset_wdt_complete;
+
+ return ipmi_msg;
+}
+
+static void sync_reset_wdt(void)
+{
+ struct ipmi_msg *ipmi_msg;
+
+ if ((ipmi_msg = wdt_reset_mkmsg()))
+ ipmi_queue_msg_sync(ipmi_msg);
+}
+
+static void reset_wdt(struct timer *t __unused, void *data,
+ uint64_t now __unused)
+{
+ struct ipmi_msg *ipmi_msg;
+
+ if ((ipmi_msg = wdt_reset_mkmsg())) {
+ ipmi_msg->user_data = data;
+ ipmi_queue_msg_head(ipmi_msg);
+ }
+}
+
+void ipmi_wdt_stop(void)
+{
+ if (!wdt_stopped) {
+ /* Make sure the background reset timer is disabled before
+ * stopping the watchdog. If we issue a reset after disabling
+ * the timer, it will be re-enabled. */
+ wdt_ticking = false;
+ cancel_timer(&wdt_timer);
+
+ /* Configure the watchdog to be disabled and do no action
+ * in case the underlying implementation is buggy and times
+ * out anyway. */
+ wdt_stopped = true;
+ set_wdt(WDT_NO_ACTION, 100, 0, false, false);
+ }
+}
+
+void ipmi_wdt_final_reset(void)
+{
+ /* We can safely stop the timer prior to setting up our final
+ * watchdog timeout since we have enough margin before the
+ * timeout. */
+ wdt_ticking = false;
+ cancel_timer(&wdt_timer);
+
+ /*
+ * We're going to wait a little while before requiring
+ * BOOTKERNEL to have IPMI watchdog support so that people
+ * can catch up in their development environments.
+ * If you still read this after 2018, send a patch!
+ */
+#if 0
+ /* Configure the watchdog and make sure it is still enabled */
+ set_wdt(WDT_RESET_ACTION | WDT_PRETIMEOUT_SMI, WDT_TIMEOUT,
+ WDT_MARGIN/10, true, true);
+ sync_reset_wdt();
+#else
+ set_wdt(WDT_NO_ACTION, 100, 0, false, false);
+#endif
+ ipmi_set_boot_count();
+}
+
+void ipmi_wdt_init(void)
+{
+ init_timer(&wdt_timer, reset_wdt, NULL);
+ set_wdt(WDT_RESET_ACTION, WDT_TIMEOUT, 0, true, false);
+
+ /* Start the WDT. We do it synchronously to make sure it has
+ * started before skiboot continues booting. Otherwise we
+ * could crash before the wdt has actually been started. */
+ wdt_ticking = true;
+ sync_reset_wdt();
+
+ return;
+}
diff --git a/roms/skiboot/hw/ipmi/test/Makefile.check b/roms/skiboot/hw/ipmi/test/Makefile.check
new file mode 100644
index 000000000..ceed1ed39
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/test/Makefile.check
@@ -0,0 +1,34 @@
+# -*-Makefile-*-
+IPMI_TEST := hw/ipmi/test/run-fru
+
+LCOV_EXCLUDE += $(IPMI_TEST:%=%.c)
+
+.PHONY : hw-ipmi-check hw-ipmi-coverage
+hw-ipmi-check: $(IPMI_TEST:%=%-check)
+hw-ipmi-coverage: $(IPMI_TEST:%=%-gcov-run)
+
+check: hw-ipmi-check
+coverage: hw-ipmi-coverage
+
+$(IPMI_TEST:%=%-gcov-run) : %-run: %
+ $(call Q, TEST-COVERAGE ,$< , $<)
+
+$(IPMI_TEST:%=%-check) : %-check: %
+ $(call Q, RUN-TEST ,$(VALGRIND) $<, $<)
+
+$(IPMI_TEST) : % : %.c
+ $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) -O0 -g -I include -I . -o $@ $<, $<)
+
+$(IPMI_TEST:%=%-gcov): %-gcov : %.c %
+ $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(HOSTGCOVCFLAGS) -I include -I . -I libfdt -lgcov -o $@ $<, $<)
+
+$(IPMI_TEST:%=%-gcov): % : $(%.d:-gcov=)
+
+-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/roms/skiboot/hw/ipmi/test/run-fru.c b/roms/skiboot/hw/ipmi/test/run-fru.c
new file mode 100644
index 000000000..fa79c98a1
--- /dev/null
+++ b/roms/skiboot/hw/ipmi/test/run-fru.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define __TEST__
+
+#include "../ipmi-fru.c"
+
+#include <string.h>
+
+int error = 0;
+
+const char version[] = "a-too-long-version-test-string-is-here";
+
+void ipmi_free_msg(struct ipmi_msg __unused *msg)
+{
+}
+
+void ipmi_init_msg(struct ipmi_msg __unused *msg, int __unused interface,
+ uint32_t __unused code,
+ void __unused (*complete)(struct ipmi_msg *),
+ void __unused *user_data, size_t __unused req_size,
+ size_t __unused resp_size)
+{
+}
+
+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",
+ };
+ struct product_info invalid_info = {
+ .manufacturer = (char *) "I",
+ .product = (char *) "skiboot",
+ .part_no = (char *) "hello",
+ .version = (char *) "12345",
+ .serial_no = (char *) "12345",
+ .asset_tag = (char *) "abcd",
+ };
+ struct product_info invalid_info2 = {
+ .manufacturer = (char *) "IBM",
+ .product = (char *) "skiboot",
+ .part_no = (char *) "this is a really long string that's more"
+ "than 32 characters, because it turns out that's invalid.",
+ .version = (char *) "12345",
+ .serial_no = (char *) "12345",
+ .asset_tag = (char *) "abcd",
+ };
+
+ buf = malloc(256);
+
+ len = fru_fill_product_info(buf, &info, 40);
+ assert(len == 40);
+ assert(memcmp(buf, "\001\005\000\303IBM\307skiboot\305hello"
+ "\30512345\30512345\304abcd\301-",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);
+ len = fru_fill_product_info(buf, &invalid_info, 40);
+ assert(len == OPAL_PARAMETER);
+
+ memset(buf, 0, 256);
+ len = fru_fill_product_info(buf, &invalid_info2, 256);
+ assert(len == OPAL_PARAMETER);
+
+ memset(buf, 0, 256);
+ assert(fru_add(buf, 256) > 0);
+ assert(0 == memcmp(&buf[64], "\001\a\000\303IBM\307skiboot\300"
+ "\337a-too-long-version-test-string+\300\300\301"
+ "\0\0\0",54));
+
+
+ memset(buf, 0, 256);
+ assert(fru_add(buf, 1) == OPAL_PARAMETER);
+
+ memset(buf, 0, 256);
+ assert(fru_add(buf, 65) == OPAL_PARAMETER);
+
+ free(buf);
+
+ return 0;
+}