aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hw/fsp/fsp-elog-read.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hw/fsp/fsp-elog-read.c')
-rw-r--r--roms/skiboot/hw/fsp/fsp-elog-read.c608
1 files changed, 608 insertions, 0 deletions
diff --git a/roms/skiboot/hw/fsp/fsp-elog-read.c b/roms/skiboot/hw/fsp/fsp-elog-read.c
new file mode 100644
index 000000000..bd23ffbe8
--- /dev/null
+++ b/roms/skiboot/hw/fsp/fsp-elog-read.c
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * This code will enable retrieving of error log from FSP -> Sapphire in
+ * sequence.
+ * Here, FSP would send next log only when Sapphire sends a new log notification
+ * response to FSP. On Completion of reading the log from FSP,
+ * OPAL_EVENT_ERROR_LOG_AVAIL is signaled. This will remain raised until a call
+ * to opal_elog_read() is made and OPAL_SUCCESS is returned. Upon which, the
+ * operation is complete and the event is cleared. This is READ action from FSP.
+ *
+ * Copyright 2013-2017 IBM Corp.
+ */
+
+/*
+ * Design of READ error log :
+ * When we receive a new error log entry notification from FSP, we queue it into
+ * the "pending" list. If the "pending" list is not empty, then we start
+ * fetching log from FSP.
+ *
+ * When Linux reads a log entry, we dequeue it from the "pending" list and
+ * enqueue it to another "processed" list. At this point, if the "pending"
+ * list is not empty, we continue to fetch the next log.
+ *
+ * When Linux calls opal_resend_pending_logs(), we fetch the log corresponding
+ * to the head of the pending list and move it to the processed list, and
+ * continue this process until the pending list is empty. If the pending list
+ * was empty earlier and is currently non-empty, we initiate an error log fetch.
+ *
+ * When Linux acks an error log, we remove it from processed list.
+ */
+
+#include <errno.h>
+#include <fsp.h>
+#include <fsp-elog.h>
+#include <lock.h>
+#include <opal-api.h>
+#include <psi.h>
+#include <skiboot.h>
+
+/*
+ * Maximum number of entries that are pre-allocated
+ * to keep track of pending elogs to be fetched.
+ */
+#define ELOG_READ_MAX_RECORD 128
+
+/* Structure to maintain log-id, log-size, pending and processed list. */
+struct fsp_log_entry {
+ uint32_t log_id;
+ size_t log_size;
+ struct list_node link;
+};
+
+static LIST_HEAD(elog_read_pending);
+static LIST_HEAD(elog_read_processed);
+static LIST_HEAD(elog_read_free);
+/*
+ * Lock is used to protect overwriting of processed and pending list
+ * and also used while updating state of each log.
+ */
+static struct lock elog_read_lock = LOCK_UNLOCKED;
+
+#define ELOG_READ_BUFFER_SIZE 0x00004000
+/* Log buffer to copy FSP log for read */
+static void *elog_read_buffer;
+static uint32_t elog_head_id; /* FSP entry ID */
+static size_t elog_head_size; /* Actual FSP log size */
+static uint32_t elog_read_retries; /* Bad response status count */
+
+/* Initialize the state of the log */
+static enum elog_head_state elog_read_from_fsp_head_state = ELOG_STATE_NONE;
+
+static bool elog_enabled = false;
+
+/* Need forward declaration because of circular dependency. */
+static void fsp_elog_queue_fetch(void);
+
+/*
+ * Check the response message for mbox acknowledgement
+ * command send to FSP.
+ */
+static void fsp_elog_ack_complete(struct fsp_msg *msg)
+{
+ uint8_t val;
+
+ val = (msg->resp->word1 >> 8) & 0xff;
+ if (val != 0)
+ prerror("ELOG: Acknowledgement error\n");
+
+ fsp_freemsg(msg);
+}
+
+/* Send error log PHYP acknowledgement to FSP with entry ID. */
+static int64_t fsp_send_elog_ack(uint32_t log_id)
+{
+ struct fsp_msg *ack_msg;
+
+ ack_msg = fsp_mkmsg(FSP_CMD_ERRLOG_PHYP_ACK, 1, log_id);
+ if (!ack_msg) {
+ prerror("ELOG: Failed to allocate ack message\n");
+ return OPAL_INTERNAL_ERROR;
+ }
+
+ if (fsp_queue_msg(ack_msg, fsp_elog_ack_complete)) {
+ fsp_freemsg(ack_msg);
+ ack_msg = NULL;
+ prerror("ELOG: Error queueing elog ack complete\n");
+ return OPAL_INTERNAL_ERROR;
+ }
+
+ return OPAL_SUCCESS;
+}
+
+/* Retrieve error log from FSP with TCE for the data transfer. */
+static void fsp_elog_check_and_fetch_head(void)
+{
+ lock(&elog_read_lock);
+ if (elog_read_from_fsp_head_state != ELOG_STATE_NONE ||
+ list_empty(&elog_read_pending)) {
+ unlock(&elog_read_lock);
+ return;
+ }
+
+ elog_read_retries = 0;
+ /* Start fetching first entry from the pending list */
+ fsp_elog_queue_fetch();
+ unlock(&elog_read_lock);
+}
+
+void elog_set_head_state(bool opal_logs, enum elog_head_state state)
+{
+ static enum elog_head_state opal_logs_state = ELOG_STATE_NONE;
+ static enum elog_head_state fsp_logs_state = ELOG_STATE_NONE;
+
+ /* ELOG disabled */
+ if (!elog_enabled)
+ return;
+
+ if (opal_logs)
+ opal_logs_state = state;
+ else
+ fsp_logs_state = state;
+
+ if (fsp_logs_state == ELOG_STATE_FETCHED_DATA ||
+ opal_logs_state == ELOG_STATE_FETCHED_DATA)
+ opal_update_pending_evt(OPAL_EVENT_ERROR_LOG_AVAIL,
+ OPAL_EVENT_ERROR_LOG_AVAIL);
+ else
+ opal_update_pending_evt(OPAL_EVENT_ERROR_LOG_AVAIL, 0);
+}
+
+/* This function should be called with the lock held. */
+static inline void fsp_elog_set_head_state(enum elog_head_state state)
+{
+ elog_set_head_state(false, state);
+ elog_read_from_fsp_head_state = state;
+}
+
+/*
+ * When, we try maximum time of fetching log from FSP
+ * we call following function to delete log from the
+ * pending list and update the state to fetch next log.
+ *
+ * This function should be called with the lock held.
+ */
+static void fsp_elog_fetch_failure(uint8_t fsp_status)
+{
+ struct fsp_log_entry *log_data;
+
+ /* Read top list and delete the node */
+ log_data = list_top(&elog_read_pending, struct fsp_log_entry, link);
+ if (!log_data) {
+ /**
+ * @fwts-label ElogFetchFailureInconsistent
+ * @fwts-advice Inconsistent state between OPAL and FSP
+ * in code path for handling failure of fetching error log
+ * from FSP. Likely a bug in interaction between FSP and OPAL.
+ */
+ prlog(PR_ERR, "%s: Inconsistent internal list state !\n",
+ __func__);
+ } else {
+ list_del(&log_data->link);
+ list_add(&elog_read_free, &log_data->link);
+ prerror("ELOG: received invalid data: %x FSP status: 0x%x\n",
+ log_data->log_id, fsp_status);
+ }
+
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+}
+
+/* Read response value from FSP for fetch sp data mbox command */
+static void fsp_elog_read_complete(struct fsp_msg *read_msg)
+{
+ uint8_t val;
+
+ lock(&elog_read_lock);
+ val = (read_msg->resp->word1 >> 8) & 0xff;
+ fsp_freemsg(read_msg);
+ if (elog_read_from_fsp_head_state == ELOG_STATE_REJECTED) {
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+ goto elog_read_out;
+ }
+
+ switch (val) {
+ case FSP_STATUS_SUCCESS:
+ fsp_elog_set_head_state(ELOG_STATE_FETCHED_DATA);
+ break;
+
+ case FSP_STATUS_DMA_ERROR:
+ if (elog_read_retries++ < MAX_RETRIES) {
+ /*
+ * For a error response value from FSP, we try to
+ * send fetch sp data mbox command again for three
+ * times if response from FSP is still not valid
+ * we send generic error response to FSP.
+ */
+ fsp_elog_queue_fetch();
+ break;
+ }
+
+ fsp_elog_fetch_failure(val);
+ break;
+
+ default:
+ fsp_elog_fetch_failure(val);
+ }
+
+elog_read_out:
+ unlock(&elog_read_lock);
+
+ /* Check if a new log needs fetching */
+ fsp_elog_check_and_fetch_head();
+}
+
+/* Read error log from FSP through mbox commands */
+static void fsp_elog_queue_fetch(void)
+{
+ int rc;
+ uint8_t flags = 0;
+ struct fsp_log_entry *entry;
+
+ entry = list_top(&elog_read_pending, struct fsp_log_entry, link);
+ if (!entry) {
+ /**
+ * @fwts-label ElogQueueInconsistent
+ * @fwts-advice Bug in interaction between FSP and OPAL. We
+ * expected there to be a pending read from FSP but the list
+ * was empty.
+ */
+ prlog(PR_ERR, "%s: Inconsistent internal list state !\n",
+ __func__);
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+ return;
+ }
+
+ fsp_elog_set_head_state(ELOG_STATE_FETCHING);
+ elog_head_id = entry->log_id;
+ elog_head_size = entry->log_size;
+ rc = fsp_fetch_data_queue(flags, FSP_DATASET_ERRLOG, elog_head_id,
+ 0, (void *)PSI_DMA_ERRLOG_READ_BUF,
+ &elog_head_size, fsp_elog_read_complete);
+ if (rc) {
+ prerror("ELOG: failed to queue read message: %d\n", rc);
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+ }
+}
+
+/* OPAL interface for PowerNV to read log size and log ID from Sapphire. */
+static int64_t fsp_opal_elog_info(__be64 *opal_elog_id,
+ __be64 *opal_elog_size, __be64 *elog_type)
+{
+ struct fsp_log_entry *log_data;
+
+ /* Copy type of the error log */
+ *elog_type = cpu_to_be64(ELOG_TYPE_PEL);
+
+ /* Check if any OPAL log needs to be reported to the host */
+ if (opal_elog_info(opal_elog_id, opal_elog_size))
+ return OPAL_SUCCESS;
+
+ lock(&elog_read_lock);
+ if (elog_read_from_fsp_head_state != ELOG_STATE_FETCHED_DATA) {
+ unlock(&elog_read_lock);
+ return OPAL_WRONG_STATE;
+ }
+
+ log_data = list_top(&elog_read_pending, struct fsp_log_entry, link);
+ if (!log_data) {
+ /**
+ * @fwts-label ElogInfoInconsistentState
+ * @fwts-advice We expected there to be an entry in the list
+ * of error logs for the error log we're fetching information
+ * for. There wasn't. This means there's a bug.
+ */
+ prlog(PR_ERR, "%s: Inconsistent internal list state !\n",
+ __func__);
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+ unlock(&elog_read_lock);
+ return OPAL_WRONG_STATE;
+ }
+
+ *opal_elog_id = cpu_to_be64(log_data->log_id);
+ *opal_elog_size = cpu_to_be64(log_data->log_size);
+ fsp_elog_set_head_state(ELOG_STATE_HOST_INFO);
+ unlock(&elog_read_lock);
+ return OPAL_SUCCESS;
+}
+
+/* OPAL interface for PowerNV to read log from Sapphire. */
+static int64_t fsp_opal_elog_read(void *buffer, uint64_t opal_elog_size,
+ uint64_t opal_elog_id)
+{
+ int size = opal_elog_size;
+ struct fsp_log_entry *log_data;
+
+ /* Check if any OPAL log needs to be reported to the PowerNV */
+ if (opal_elog_read(buffer, opal_elog_size, opal_elog_id))
+ return OPAL_SUCCESS;
+
+ /*
+ * Read top entry from list.
+ * As we know always top record of the list is fetched from FSP
+ */
+ lock(&elog_read_lock);
+ if (elog_read_from_fsp_head_state != ELOG_STATE_HOST_INFO) {
+ unlock(&elog_read_lock);
+ return OPAL_WRONG_STATE;
+ }
+
+ log_data = list_top(&elog_read_pending, struct fsp_log_entry, link);
+ if (!log_data) {
+ /**
+ * @fwts-label ElogReadInconsistentState
+ * @fwts-advice Inconsistent state while reading error log
+ * from FSP. Bug in OPAL and FSP interaction.
+ */
+ prlog(PR_ERR, "%s: Inconsistent internal list state !\n",
+ __func__);
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+ unlock(&elog_read_lock);
+ return OPAL_WRONG_STATE;
+ }
+
+ /* Check log ID and then read log from buffer */
+ if (opal_elog_id != log_data->log_id) {
+ unlock(&elog_read_lock);
+ return OPAL_PARAMETER;
+ }
+
+ /* Do not copy more than actual log size */
+ if (opal_elog_size > log_data->log_size)
+ size = log_data->log_size;
+
+ memset(buffer, 0, opal_elog_size);
+ memcpy(buffer, elog_read_buffer, size);
+
+ /*
+ * Once log is read from linux move record from pending
+ * to processed list and delete record from pending list
+ * and change state of the log to fetch next record.
+ */
+ list_del(&log_data->link);
+ list_add(&elog_read_processed, &log_data->link);
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+ unlock(&elog_read_lock);
+
+ /* Read error log from FSP */
+ fsp_elog_check_and_fetch_head();
+
+ return OPAL_SUCCESS;
+}
+
+/* Set state of the log head before fetching the log. */
+static void elog_reject_head(void)
+{
+ if (elog_read_from_fsp_head_state == ELOG_STATE_FETCHING)
+ fsp_elog_set_head_state(ELOG_STATE_REJECTED);
+ else
+ fsp_elog_set_head_state(ELOG_STATE_NONE);
+}
+
+/* OPAL interface for PowerNV to send ack to FSP with log ID */
+static int64_t fsp_opal_elog_ack(uint64_t ack_id)
+{
+ int rc = 0;
+ struct fsp_log_entry *record, *next_record;
+
+ if (opal_elog_ack(ack_id))
+ return rc;
+
+ /* Send acknowledgement to FSP */
+ rc = fsp_send_elog_ack(ack_id);
+ if (rc != OPAL_SUCCESS) {
+ prerror("ELOG: failed to send acknowledgement: %d\n", rc);
+ return rc;
+ }
+
+ lock(&elog_read_lock);
+ list_for_each_safe(&elog_read_processed, record, next_record, link) {
+ if (record->log_id != ack_id)
+ continue;
+
+ list_del(&record->link);
+ list_add(&elog_read_free, &record->link);
+ unlock(&elog_read_lock);
+ return rc;
+ }
+
+ list_for_each_safe(&elog_read_pending, record, next_record, link) {
+ if (record->log_id != ack_id)
+ continue;
+ /*
+ * It means PowerNV has sent ACK without reading actual data.
+ * Because of this elog_read_from_fsp_head_state may be
+ * stuck in wrong state (ELOG_STATE_HOST_INFO) and not able
+ * to send remaining ELOGs to PowerNV. Hence reset ELOG state
+ * and start sending remaining ELOGs.
+ */
+ list_del(&record->link);
+ list_add(&elog_read_free, &record->link);
+ elog_reject_head();
+ unlock(&elog_read_lock);
+ fsp_elog_check_and_fetch_head();
+ return rc;
+ }
+
+ unlock(&elog_read_lock);
+ return OPAL_PARAMETER;
+}
+
+/*
+ * Once Linux kexec's it ask to resend all logs which
+ * are not acknowledged from Linux.
+ */
+static void fsp_opal_resend_pending_logs(void)
+{
+ struct fsp_log_entry *entry;
+
+ lock(&elog_read_lock);
+ elog_enabled = true;
+ unlock(&elog_read_lock);
+
+ /* Check if any Sapphire logs are pending. */
+ opal_resend_pending_logs();
+
+ lock(&elog_read_lock);
+ /*
+ * If processed list is not empty add all record from
+ * processed list to pending list at head of the list
+ * and delete records from processed list.
+ */
+ while (!list_empty(&elog_read_processed)) {
+ entry = list_pop(&elog_read_processed,
+ struct fsp_log_entry, link);
+ list_add(&elog_read_pending, &entry->link);
+ }
+
+ unlock(&elog_read_lock);
+
+ /* Read error log from FSP */
+ elog_reject_head();
+ fsp_elog_check_and_fetch_head();
+}
+
+/* Disable ELOG event flag until PowerNV is ready to receive event */
+static bool opal_kexec_elog_notify(void *data __unused)
+{
+ lock(&elog_read_lock);
+ elog_enabled = false;
+ opal_update_pending_evt(OPAL_EVENT_ERROR_LOG_AVAIL, 0);
+ unlock(&elog_read_lock);
+
+ return true;
+}
+
+/* FSP elog notify function */
+static bool fsp_elog_msg(uint32_t cmd_sub_mod, struct fsp_msg *msg)
+{
+ int rc = 0;
+ struct fsp_log_entry *record;
+ uint32_t log_id;
+ uint32_t log_size;
+
+ if (cmd_sub_mod != FSP_CMD_ERRLOG_NOTIFICATION)
+ return false;
+
+ log_id = fsp_msg_get_data_word(msg, 0);
+ log_size = fsp_msg_get_data_word(msg, 1);
+
+ prlog(PR_TRACE, "ELOG: Notified of log 0x%08x (size: %d)\n",
+ log_id, log_size);
+
+ /* Make sure we don't cross read buffer size */
+ if (log_size > ELOG_READ_BUFFER_SIZE) {
+ log_size = ELOG_READ_BUFFER_SIZE;
+ printf("ELOG: Truncated log (0x%08x) to 0x%x\n",
+ log_id, log_size);
+ }
+
+ /* Take a lock until we take out the node from elog_read_free */
+ lock(&elog_read_lock);
+ if (!list_empty(&elog_read_free)) {
+ /* Create a new entry in the pending list. */
+ record = list_pop(&elog_read_free, struct fsp_log_entry, link);
+ record->log_id = log_id;
+ record->log_size = log_size;
+ list_add_tail(&elog_read_pending, &record->link);
+ unlock(&elog_read_lock);
+
+ /* Send response back to FSP for a new elog notify message. */
+ rc = fsp_queue_msg(fsp_mkmsg(FSP_RSP_ERRLOG_NOTIFICATION,
+ 1, log_id), fsp_freemsg);
+ if (rc)
+ prerror("ELOG: Failed to queue errlog notification"
+ " response: %d\n", rc);
+
+ /* Read error log from FSP */
+ fsp_elog_check_and_fetch_head();
+
+ } else {
+ prlog(PR_TRACE, "ELOG: Log entry 0x%08x discarded\n", log_id);
+
+ /* Unlock if elog_read_free is empty. */
+ unlock(&elog_read_lock);
+
+ rc = fsp_queue_msg(fsp_mkmsg(FSP_RSP_ERRLOG_NOTIFICATION,
+ 1, log_id), fsp_freemsg);
+ if (rc)
+ prerror("ELOG: Failed to queue errlog notification"
+ " response: %d\n", rc);
+
+ /*
+ * If list is full with max record then we send discarded by
+ * phyp (condition full) ack to FSP.
+ *
+ * At some point in the future, we'll get notified again.
+ * This is largely up to FSP as to when they tell us about
+ * the log again.
+ */
+ rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_ERRLOG_PHYP_ACK | 0x02,
+ 1, log_id), fsp_freemsg);
+ if (rc)
+ prerror("ELOG: Failed to queue errlog ack"
+ " response: %d\n", rc);
+ }
+
+ return true;
+}
+
+static struct fsp_client fsp_get_elog_notify = {
+ .message = fsp_elog_msg,
+};
+
+/* Pre-allocate memory for reading error log from FSP */
+static int init_elog_read_free_list(uint32_t num_entries)
+{
+ struct fsp_log_entry *entry;
+ int i;
+
+ entry = zalloc(sizeof(struct fsp_log_entry) * num_entries);
+ if (!entry)
+ goto out_err;
+
+ for (i = 0; i < num_entries; ++i) {
+ list_add_tail(&elog_read_free, &entry->link);
+ entry++;
+ }
+
+ return 0;
+
+out_err:
+ return -ENOMEM;
+}
+
+/* FSP elog read init function */
+void fsp_elog_read_init(void)
+{
+ int val = 0;
+
+ if (!fsp_present())
+ return;
+
+ elog_read_buffer = memalign(TCE_PSIZE, ELOG_READ_BUFFER_SIZE);
+ if (!elog_read_buffer) {
+ prerror("FSP: could not allocate FSP ELOG_READ_BUFFER!\n");
+ return;
+ }
+
+ /* Map TCEs */
+ fsp_tce_map(PSI_DMA_ERRLOG_READ_BUF, elog_read_buffer,
+ PSI_DMA_ERRLOG_READ_BUF_SZ);
+
+ /* Pre allocate memory for 128 record */
+ val = init_elog_read_free_list(ELOG_READ_MAX_RECORD);
+ if (val != 0)
+ return;
+
+ /* Register error log class D2 */
+ fsp_register_client(&fsp_get_elog_notify, FSP_MCLASS_ERR_LOG);
+
+ /* Register for sync on PowerNV reboot call */
+ opal_add_host_sync_notifier(opal_kexec_elog_notify, NULL);
+
+ /* Register OPAL interface */
+ opal_register(OPAL_ELOG_READ, fsp_opal_elog_read, 3);
+ opal_register(OPAL_ELOG_ACK, fsp_opal_elog_ack, 1);
+ opal_register(OPAL_ELOG_RESEND, fsp_opal_resend_pending_logs, 0);
+ opal_register(OPAL_ELOG_SIZE, fsp_opal_elog_info, 3);
+}