diff options
author | Alistair Popple <alistair@popple.id.au> | 2014-11-13 17:16:05 +1100 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2014-12-02 18:43:26 +1100 |
commit | dd173e4beaf1da11db3bcfb2ccdfe8a623f26e53 (patch) | |
tree | f9524e195ecb3ee70032a204089260bc61706588 | |
parent | f8a0bb99bb6879124f153a161339bbfdef2049bf (diff) | |
download | skiboot-dd173e4beaf1da11db3bcfb2ccdfe8a623f26e53.zip skiboot-dd173e4beaf1da11db3bcfb2ccdfe8a623f26e53.tar.gz skiboot-dd173e4beaf1da11db3bcfb2ccdfe8a623f26e53.tar.bz2 |
esel: Add a logging backend for bmc based machines
This patch adds a backend for bmc based machines running AMI firmware
supporting the OEM extended sel commands. Errors are logged in pel
format to the bmc.
Signed-off-by: Alistair Popple <alistair@popple.id.au>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
-rw-r--r-- | external/read_esel.sh | 55 | ||||
-rw-r--r-- | hw/ipmi/Makefile.inc | 2 | ||||
-rw-r--r-- | hw/ipmi/ipmi-sel.c | 150 | ||||
-rw-r--r-- | include/ipmi.h | 8 | ||||
-rw-r--r-- | platforms/astbmc/common.c | 2 | ||||
-rw-r--r-- | platforms/astbmc/palmetto.c | 2 |
6 files changed, 218 insertions, 1 deletions
diff --git a/external/read_esel.sh b/external/read_esel.sh new file mode 100644 index 0000000..492fbce --- /dev/null +++ b/external/read_esel.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Copyright 2013-2014 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e + +BMC_HOST=$1 +RECORD_ID=$2 + +BMC_USER=admin +BMC_PASS=admin + +if [ -z "$BMC_HOST" -o -z "$RECORD_ID" ]; then + echo "Usage: $0 <bmc hostname> <record id>" + echo "Example: $0 bmc 0xa > pel.bin" + echo '' + echo 'Record ids can be found using ipmitool with the "sel list" command. Records with' + echo 'a description of "OEM record df" contain extended SEL information (in PEL' + echo 'format) which can be extracted with this tool.' + exit -1 +fi + +# Convert a number into 2 hex-bytes in little-endian order +function conv_le { + echo $(for i in $(printf %04x $1 | grep -o .. | tac); do echo -n "0x$i "; done) +} + +function conv_native { + echo -n "0x${2}${1}" +} + +record=$(conv_le $2) +offset=0 +progress=0 + +while [ $progress = 0 ]; do + result=$(ipmitool -H ${BMC_HOST} -I lan -U ${BMC_USER} -P ${BMC_PASS} raw 0x32 0xf1 ${record} $(conv_le ${offset})) + len=$(conv_native $(echo ${result} | cut -d " " -f 1-2)) + progress=$(($(echo ${result} | cut -d " " -f 3))) + data="$data "$(echo -n ${result} | cut -d " " -f 6-) + offset=$(($offset + ${#data}/3)) +done + +echo -n ${data} | cut -d " " -f 1-$(($len)) | xxd -r -p diff --git a/hw/ipmi/Makefile.inc b/hw/ipmi/Makefile.inc index 2d9f41f..02670d7 100644 --- a/hw/ipmi/Makefile.inc +++ b/hw/ipmi/Makefile.inc @@ -1,5 +1,5 @@ SUBDIRS += hw/ipmi -IPMI_OBJS = ipmi-rtc.o ipmi-power.o ipmi-opal.o ipmi-fru.o +IPMI_OBJS = ipmi-rtc.o ipmi-power.o ipmi-opal.o ipmi-fru.o ipmi-sel.o IPMI = hw/ipmi/built-in.o $(IPMI): $(IPMI_OBJS:%=hw/ipmi/%) diff --git a/hw/ipmi/ipmi-sel.c b/hw/ipmi/ipmi-sel.c new file mode 100644 index 0000000..d6f7f47 --- /dev/null +++ b/hw/ipmi/ipmi-sel.c @@ -0,0 +1,150 @@ +/* Copyright 2013-2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <ipmi.h> +#include <device.h> +#include <opal.h> +#include <lock.h> +#include <errorlog.h> +#include <pel.h> + +/* As far as I can tell the size of PEL record is unbounded (due to + * the possible presence of the user defined section). We chose this + * size because it's what FSP uses, but we could probably reduce + * it. */ +#define MAX_PEL_SIZE 0x10000 + +#define ESEL_HDR_SIZE 7 + +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_free_msg(msg); +} + +/* Goes through the required steps to add a complete eSEL: + * + * 1. Get a reservation + * 2. 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 char pel_buf[MAX_PEL_SIZE]; + static size_t pel_size; + static int index = 0; + static unsigned int reservation_id = 0; + static unsigned int record_id = 0; + struct errorlog *elog_buf = (struct errorlog *) msg->user_data; + + if (msg->cmd == IPMI_CMD(IPMI_RESERVE_SEL)) { + 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_free_msg(msg); + return; + } + + pel_size = create_pel_log(elog_buf, pel_buf, MAX_PEL_SIZE); + 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 (index >= pel_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; + index = 0; + opal_elog_complete(elog_buf, true); + ipmi_free_msg(msg); + return; + } + + msg->cmd = IPMI_CMD(IPMI_PARTIAL_ADD_ESEL); + msg->netfn = IPMI_NETFN(IPMI_PARTIAL_ADD_ESEL) << 2; + msg->resp_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] = index & 0xff; + msg->data[5] = (index >> 8) & 0xff; + + if ((pel_size - index) < (IPMI_MAX_REQ_SIZE - ESEL_HDR_SIZE)) { + /* Last data to send */ + msg->data[6] = 1; + msg->req_size = pel_size - index + ESEL_HDR_SIZE; + } else { + msg->data[6] = 0; + msg->req_size = IPMI_MAX_REQ_SIZE; + } + + memcpy(&msg->data[ESEL_HDR_SIZE], &pel_buf[index], msg->req_size - ESEL_HDR_SIZE); + 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; + + /* 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_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_RESERVE_SEL, ipmi_elog_poll, + elog_buf, NULL, IPMI_MAX_REQ_SIZE, 2); + if (!msg) { + opal_elog_complete(elog_buf, false); + return OPAL_RESOURCE; + } + msg->error = ipmi_elog_error; + + msg->req_size = 0; + ipmi_queue_msg(msg); + + return 0; +} diff --git a/include/ipmi.h b/include/ipmi.h index 38770ab..50de293 100644 --- a/include/ipmi.h +++ b/include/ipmi.h @@ -83,15 +83,19 @@ #define IPMI_NETFN_CHASSIS 0x00 #define IPMI_NETFN_STORAGE 0x0a #define IPMI_NETFN_APP 0x06 +#define IPMI_NETFN_OEM 0x32 #define IPMI_WRITE_FRU IPMI_CODE(IPMI_NETFN_STORAGE, 0x12) #define IPMI_GET_SEL_INFO IPMI_CODE(IPMI_NETFN_STORAGE, 0x40) +#define IPMI_RESERVE_SEL IPMI_CODE(IPMI_NETFN_STORAGE, 0x42) #define IPMI_GET_SEL_TIME IPMI_CODE(IPMI_NETFN_STORAGE, 0x48) #define IPMI_SET_SEL_TIME IPMI_CODE(IPMI_NETFN_STORAGE, 0x49) #define IPMI_CHASSIS_CONTROL IPMI_CODE(IPMI_NETFN_CHASSIS, 0x02) #define IPMI_SET_POWER_STATE IPMI_CODE(IPMI_NETFN_APP, 0x06) #define IPMI_GET_POWER_STATE IPMI_CODE(IPMI_NETFN_APP, 0x07) +#define IPMI_PARTIAL_ADD_ESEL IPMI_CODE(IPMI_NETFN_OEM, 0xf0) + /* * IPMI response codes. */ @@ -187,4 +191,8 @@ void ipmi_opal_init(void); /* Populate fru data */ void ipmi_fru_init(uint8_t fru_dev_id); +/* Commit an error log to the bmc using the OEM add eSEL commands */ +struct errorlog; +int ipmi_elog_commit(struct errorlog *elog_buf); + #endif diff --git a/platforms/astbmc/common.c b/platforms/astbmc/common.c index d6b5b07..26f90f5 100644 --- a/platforms/astbmc/common.c +++ b/platforms/astbmc/common.c @@ -24,6 +24,7 @@ #include <ast.h> #include <ipmi.h> #include <bt.h> +#include <errorlog.h> #include "astbmc.h" @@ -53,6 +54,7 @@ void astbmc_init(void) ipmi_rtc_init(); ipmi_opal_init(); ipmi_fru_init(0x01); + elog_init(); /* As soon as IPMI is up, inform BMC we are in "S0" */ ipmi_set_power_state(IPMI_PWR_SYS_S0_WORKING, IPMI_PWR_NOCHANGE); diff --git a/platforms/astbmc/palmetto.c b/platforms/astbmc/palmetto.c index 99eb962..9c8be9f 100644 --- a/platforms/astbmc/palmetto.c +++ b/platforms/astbmc/palmetto.c @@ -19,6 +19,7 @@ #include <device.h> #include <console.h> #include <chip.h> +#include <ipmi.h> #include "astbmc.h" @@ -49,4 +50,5 @@ DECLARE_PLATFORM(palmetto) = { .external_irq = astbmc_ext_irq, .cec_power_down = astbmc_ipmi_power_down, .cec_reboot = astbmc_ipmi_reboot, + .elog_commit = ipmi_elog_commit, }; |