diff options
Diffstat (limited to 'roms/skiboot/hw/fsp/fsp-elog-write.c')
-rw-r--r-- | roms/skiboot/hw/fsp/fsp-elog-write.c | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/roms/skiboot/hw/fsp/fsp-elog-write.c b/roms/skiboot/hw/fsp/fsp-elog-write.c new file mode 100644 index 000000000..7b26a1867 --- /dev/null +++ b/roms/skiboot/hw/fsp/fsp-elog-write.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * This code will enable generation and pushing of error log from Sapphire + * to FSP. + * Critical events from Sapphire that needs to be reported will be pushed + * on to FSP after converting the error log to Platform Error Log(PEL) format. + * This is termed as write action to FSP. + * + * Copyright 2013-2016 IBM Corp. + */ + +#include <cpu.h> +#include <errno.h> +#include <fsp.h> +#include <fsp-elog.h> +#include <lock.h> +#include <opal-api.h> +#include <pel.h> +#include <pool.h> +#include <skiboot.h> +#include <timebase.h> + +static LIST_HEAD(elog_write_to_fsp_pending); +static LIST_HEAD(elog_write_to_host_pending); +static LIST_HEAD(elog_write_to_host_processed); + +static struct lock elog_write_lock = LOCK_UNLOCKED; +static struct lock elog_panic_write_lock = LOCK_UNLOCKED; +static struct lock elog_write_to_host_lock = LOCK_UNLOCKED; + +#define ELOG_WRITE_TO_FSP_BUFFER_SIZE 0x00004000 +/* Log buffer to copy OPAL log for write to FSP. */ +static void *elog_write_to_fsp_buffer; + +#define ELOG_PANIC_WRITE_BUFFER_SIZE 0x00004000 +static void *elog_panic_write_buffer; + +#define ELOG_WRITE_TO_HOST_BUFFER_SIZE 0x00004000 +static void *elog_write_to_host_buffer; + +static uint32_t elog_write_retries; + +/* Manipulate this only with write_lock held */ +static uint32_t elog_plid_fsp_commit = -1; +static enum elog_head_state elog_write_to_host_head_state = ELOG_STATE_NONE; + +/* Need forward declaration because of circular dependency */ +static int opal_send_elog_to_fsp(void); + +static void remove_elog_head_entry(void) +{ + struct errorlog *head, *entry; + + lock(&elog_write_lock); + if (!list_empty(&elog_write_to_fsp_pending)) { + head = list_top(&elog_write_to_fsp_pending, + struct errorlog, link); + if (head->plid == elog_plid_fsp_commit) { + entry = list_pop(&elog_write_to_fsp_pending, + struct errorlog, link); + opal_elog_complete(entry, + elog_write_retries < MAX_RETRIES); + /* Reset the counter */ + elog_plid_fsp_commit = -1; + } + } + + elog_write_retries = 0; + unlock(&elog_write_lock); +} + +static void opal_fsp_write_complete(struct fsp_msg *read_msg) +{ + uint8_t val; + + val = (read_msg->resp->word1 >> 8) & 0xff; + fsp_freemsg(read_msg); + + switch (val) { + case FSP_STATUS_SUCCESS: + remove_elog_head_entry(); + break; + default: + if (elog_write_retries++ >= MAX_RETRIES) { + remove_elog_head_entry(); + prerror("ELOG: Error in writing to FSP (0x%x)!\n", val); + } + + break; + } + + if (opal_send_elog_to_fsp() != OPAL_SUCCESS) + prerror("ELOG: Error sending elog to FSP !\n"); +} + +/* Write PEL format hex dump of the log to FSP */ +static int64_t fsp_opal_elog_write(size_t opal_elog_size) +{ + struct fsp_msg *elog_msg; + + elog_msg = fsp_mkmsg(FSP_CMD_CREATE_ERRLOG, 3, opal_elog_size, + 0, PSI_DMA_ERRLOG_WRITE_BUF); + if (!elog_msg) { + prerror("ELOG: Failed to create message for WRITE to FSP\n"); + return OPAL_INTERNAL_ERROR; + } + + if (fsp_queue_msg(elog_msg, opal_fsp_write_complete)) { + fsp_freemsg(elog_msg); + elog_msg = NULL; + prerror("FSP: Error queueing elog update\n"); + return OPAL_INTERNAL_ERROR; + } + + return OPAL_SUCCESS; +} + +/* This should be called with elog_write_to_host_lock lock */ +static inline void fsp_elog_write_set_head_state(enum elog_head_state state) +{ + elog_set_head_state(true, state); + elog_write_to_host_head_state = state; +} + +bool opal_elog_info(__be64 *opal_elog_id, __be64 *opal_elog_size) +{ + struct errorlog *head; + bool rc = false; + + lock(&elog_write_to_host_lock); + if (elog_write_to_host_head_state == ELOG_STATE_FETCHED_DATA) { + head = list_top(&elog_write_to_host_pending, + struct errorlog, link); + if (!head) { + /** + * @fwts-label ElogListInconsistent + * @fwts-advice Bug in interaction between FSP and + * OPAL. The state maintained by OPAL didn't match + * what the FSP sent. + */ + prlog(PR_ERR, + "%s: Inconsistent internal list state !\n", + __func__); + fsp_elog_write_set_head_state(ELOG_STATE_NONE); + } else { + *opal_elog_id = cpu_to_be64(head->plid); + *opal_elog_size = cpu_to_be64(head->log_size); + fsp_elog_write_set_head_state(ELOG_STATE_HOST_INFO); + rc = true; + } + } + + unlock(&elog_write_to_host_lock); + return rc; +} + +static void opal_commit_elog_in_host(void) +{ + struct errorlog *buf; + + lock(&elog_write_to_host_lock); + if (!list_empty(&elog_write_to_host_pending) && + (elog_write_to_host_head_state == ELOG_STATE_NONE)) { + buf = list_top(&elog_write_to_host_pending, + struct errorlog, link); + buf->log_size = create_pel_log(buf, + (char *)elog_write_to_host_buffer, + ELOG_WRITE_TO_HOST_BUFFER_SIZE); + fsp_elog_write_set_head_state(ELOG_STATE_FETCHED_DATA); + } + + unlock(&elog_write_to_host_lock); +} + +bool opal_elog_read(void *buffer, uint64_t opal_elog_size, + uint64_t opal_elog_id) +{ + struct errorlog *log_data; + bool rc = false; + + lock(&elog_write_to_host_lock); + if (elog_write_to_host_head_state == ELOG_STATE_HOST_INFO) { + log_data = list_top(&elog_write_to_host_pending, + struct errorlog, link); + if (!log_data) { + fsp_elog_write_set_head_state(ELOG_STATE_NONE); + unlock(&elog_write_to_host_lock); + return rc; + } + + if ((opal_elog_id != log_data->plid) && + (opal_elog_size != log_data->log_size)) { + unlock(&elog_write_to_host_lock); + return rc; + } + + memcpy(buffer, elog_write_to_host_buffer, opal_elog_size); + list_del(&log_data->link); + list_add(&elog_write_to_host_processed, &log_data->link); + fsp_elog_write_set_head_state(ELOG_STATE_NONE); + rc = true; + } + + unlock(&elog_write_to_host_lock); + opal_commit_elog_in_host(); + return rc; +} + +bool opal_elog_ack(uint64_t ack_id) +{ + bool rc = false; + struct errorlog *log_data; + struct errorlog *record, *next_record; + + lock(&elog_write_to_host_lock); + if (!list_empty(&elog_write_to_host_processed)) { + list_for_each_safe(&elog_write_to_host_processed, record, + next_record, link) { + if (record->plid != ack_id) + continue; + + list_del(&record->link); + opal_elog_complete(record, true); + rc = true; + } + } + + if ((!rc) && (!list_empty(&elog_write_to_host_pending))) { + log_data = list_top(&elog_write_to_host_pending, + struct errorlog, link); + if (ack_id == log_data->plid) + fsp_elog_write_set_head_state(ELOG_STATE_NONE); + + list_for_each_safe(&elog_write_to_host_pending, record, + next_record, link) { + if (record->plid != ack_id) + continue; + + list_del(&record->link); + opal_elog_complete(record, true); + rc = true; + unlock(&elog_write_to_host_lock); + opal_commit_elog_in_host(); + return rc; + } + } + + unlock(&elog_write_to_host_lock); + return rc; +} + +void opal_resend_pending_logs(void) +{ + struct errorlog *record; + + lock(&elog_write_to_host_lock); + while (!list_empty(&elog_write_to_host_processed)) { + record = list_pop(&elog_write_to_host_processed, + struct errorlog, link); + list_add_tail(&elog_write_to_host_pending, &record->link); + } + + fsp_elog_write_set_head_state(ELOG_STATE_NONE); + unlock(&elog_write_to_host_lock); + opal_commit_elog_in_host(); +} + +static inline u64 get_elog_timeout(void) +{ + return (mftb() + secs_to_tb(ERRORLOG_TIMEOUT_INTERVAL)); +} + +static int opal_send_elog_to_fsp(void) +{ + struct errorlog *head; + int rc = OPAL_SUCCESS; + + /* + * Convert entry to PEL and push it down to FSP. + * Then we wait for the ack from FSP. + */ + lock(&elog_write_lock); + if (!list_empty(&elog_write_to_fsp_pending)) { + head = list_top(&elog_write_to_fsp_pending, + struct errorlog, link); + /* Error needs to be committed, update the time out value */ + head->elog_timeout = get_elog_timeout(); + + elog_plid_fsp_commit = head->plid; + head->log_size = create_pel_log(head, + (char *)elog_write_to_fsp_buffer, + ELOG_WRITE_TO_FSP_BUFFER_SIZE); + rc = fsp_opal_elog_write(head->log_size); + unlock(&elog_write_lock); + return rc; + } + + unlock(&elog_write_lock); + return rc; +} + +static int opal_push_logs_sync_to_fsp(struct errorlog *buf) +{ + struct fsp_msg *elog_msg; + int opal_elog_size = 0; + int rc = OPAL_SUCCESS; + + lock(&elog_panic_write_lock); + + /* Error needs to be committed, update the time out value */ + buf->elog_timeout = get_elog_timeout(); + + opal_elog_size = create_pel_log(buf, + (char *)elog_panic_write_buffer, + ELOG_PANIC_WRITE_BUFFER_SIZE); + + elog_msg = fsp_mkmsg(FSP_CMD_CREATE_ERRLOG, 3, opal_elog_size, + 0, PSI_DMA_ELOG_PANIC_WRITE_BUF); + if (!elog_msg) { + prerror("ELOG: PLID: 0x%x Failed to create message for WRITE " + "to FSP\n", buf->plid); + unlock(&elog_panic_write_lock); + opal_elog_complete(buf, false); + return OPAL_INTERNAL_ERROR; + } + + if (fsp_sync_msg(elog_msg, false)) { + fsp_freemsg(elog_msg); + rc = OPAL_INTERNAL_ERROR; + } else { + rc = (elog_msg->resp->word1 >> 8) & 0xff; + fsp_freemsg(elog_msg); + } + + unlock(&elog_panic_write_lock); + if (rc != OPAL_SUCCESS) + opal_elog_complete(buf, false); + else + opal_elog_complete(buf, true); + + return rc; +} + +int elog_fsp_commit(struct errorlog *buf) +{ + int rc = OPAL_SUCCESS; + + if (buf->event_severity == OPAL_ERROR_PANIC) { + rc = opal_push_logs_sync_to_fsp(buf); + return rc; + } + + lock(&elog_write_lock); + if (list_empty(&elog_write_to_fsp_pending)) { + list_add_tail(&elog_write_to_fsp_pending, &buf->link); + unlock(&elog_write_lock); + rc = opal_send_elog_to_fsp(); + return rc; + } + + list_add_tail(&elog_write_to_fsp_pending, &buf->link); + unlock(&elog_write_lock); + return rc; +} + +static void elog_append_write_to_host(struct errorlog *buf) +{ + lock(&elog_write_to_host_lock); + if (list_empty(&elog_write_to_host_pending)) { + list_add(&elog_write_to_host_pending, &buf->link); + unlock(&elog_write_to_host_lock); + opal_commit_elog_in_host(); + } else { + list_add_tail(&elog_write_to_host_pending, &buf->link); + unlock(&elog_write_to_host_lock); + } +} + +static void elog_timeout_poll(void *data __unused) +{ + uint64_t now; + struct errorlog *head, *entry; + + lock(&elog_write_lock); + if (list_empty(&elog_write_to_fsp_pending)) { + unlock(&elog_write_lock); + return; + } + + head = list_top(&elog_write_to_fsp_pending, struct errorlog, link); + now = mftb(); + if ((tb_compare(now, head->elog_timeout) == TB_AAFTERB) || + (tb_compare(now, head->elog_timeout) == TB_AEQUALB)) { + entry = list_pop(&elog_write_to_fsp_pending, + struct errorlog, link); + unlock(&elog_write_lock); + elog_append_write_to_host(entry); + } else { + unlock(&elog_write_lock); + } +} + +/* FSP elog init function */ +void fsp_elog_write_init(void) +{ + if (!fsp_present()) + return; + + elog_panic_write_buffer = memalign(TCE_PSIZE, + ELOG_PANIC_WRITE_BUFFER_SIZE); + if (!elog_panic_write_buffer) { + prerror("FSP: could not allocate ELOG_PANIC_WRITE_BUFFER!\n"); + return; + } + + elog_write_to_fsp_buffer = memalign(TCE_PSIZE, + ELOG_WRITE_TO_FSP_BUFFER_SIZE); + if (!elog_write_to_fsp_buffer) { + prerror("FSP: could not allocate ELOG_WRITE_BUFFER!\n"); + return; + } + + elog_write_to_host_buffer = memalign(TCE_PSIZE, + ELOG_WRITE_TO_HOST_BUFFER_SIZE); + if (!elog_write_to_host_buffer) { + prerror("FSP: could not allocate ELOG_WRITE_TO_HOST_BUFFER!\n"); + return; + } + + /* Map TCEs */ + fsp_tce_map(PSI_DMA_ELOG_PANIC_WRITE_BUF, elog_panic_write_buffer, + PSI_DMA_ELOG_PANIC_WRITE_BUF_SZ); + + fsp_tce_map(PSI_DMA_ERRLOG_WRITE_BUF, elog_write_to_fsp_buffer, + PSI_DMA_ERRLOG_WRITE_BUF_SZ); + + elog_init(); + + /* Add a poller */ + opal_add_poller(elog_timeout_poll, NULL); +} |