diff options
Diffstat (limited to 'roms/skiboot/hw/ipmi')
-rw-r--r-- | roms/skiboot/hw/ipmi/Makefile.inc | 9 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-attn.c | 100 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-fru.c | 231 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-info.c | 206 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-power.c | 85 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-rtc.c | 127 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-sel.c | 701 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-sensor.c | 160 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/ipmi-watchdog.c | 218 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/test/Makefile.check | 34 | ||||
-rw-r--r-- | roms/skiboot/hw/ipmi/test/run-fru.c | 116 |
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; +} |