/* Copyright 2013-2016 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. */ /* * 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. */ #include #include #include #include #include #include #include #include #include #include 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(uint64_t *opal_elog_id, uint64_t *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 = head->plid; *opal_elog_size = 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(uint64_t *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((void *)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); }