diff options
Diffstat (limited to 'roms/skiboot/hw/fsp/fsp.c')
-rw-r--r-- | roms/skiboot/hw/fsp/fsp.c | 2709 |
1 files changed, 2709 insertions, 0 deletions
diff --git a/roms/skiboot/hw/fsp/fsp.c b/roms/skiboot/hw/fsp/fsp.c new file mode 100644 index 000000000..2c5f9d71b --- /dev/null +++ b/roms/skiboot/hw/fsp/fsp.c @@ -0,0 +1,2709 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Base FSP (Flexible Service Processor) Support + * + * FSP is the BMC-like thing in some IBM POWER servers + * + * Copyright 2013-2019 IBM Corp. + */ + +#include <stdarg.h> +#include <processor.h> +#include <io.h> +#include <fsp.h> +#include <lock.h> +#include <interrupts.h> +#include <device.h> +#include <trace.h> +#include <timebase.h> +#include <cpu.h> +#include <errorlog.h> +#include <opal.h> +#include <opal-msg.h> +#include <ccan/list/list.h> + +extern uint32_t hir_trigger; + +DEFINE_LOG_ENTRY(OPAL_RC_FSP_POLL_TIMEOUT, OPAL_PLATFORM_ERR_EVT, OPAL_FSP, + OPAL_PLATFORM_FIRMWARE, OPAL_RECOVERED_ERR_GENERAL, OPAL_NA); + +DEFINE_LOG_ENTRY(OPAL_RC_FSP_MBOX_ERR, OPAL_PLATFORM_ERR_EVT, OPAL_FSP, + OPAL_PLATFORM_FIRMWARE, OPAL_RECOVERED_ERR_GENERAL, OPAL_NA); + +DEFINE_LOG_ENTRY(OPAL_RC_FSP_DISR_HIR_MASK, OPAL_PLATFORM_ERR_EVT, OPAL_FSP, + OPAL_PLATFORM_FIRMWARE, OPAL_RECOVERED_ERR_GENERAL, OPAL_NA); + +/* We make this look like a Surveillance error, even though it really + * isn't one. + */ +DEFINE_LOG_ENTRY(OPAL_INJECTED_HIR, OPAL_MISC_ERR_EVT, OPAL_SURVEILLANCE, + OPAL_SURVEILLANCE_ERR, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_MISCELLANEOUS_INFO_ONLY); + +#define FSP_TRACE_MSG +#define FSP_TRACE_EVENT + +#define FSP_MAX_IOPATH 4 + +enum fsp_path_state { + fsp_path_bad, + fsp_path_backup, + fsp_path_active, +}; + +struct fsp_iopath { + enum fsp_path_state state; + void *fsp_regs; + struct psi *psi; +}; + +enum fsp_mbx_state { + fsp_mbx_idle, /* Mailbox ready to send */ + fsp_mbx_send, /* Mailbox sent, waiting for ack */ + fsp_mbx_crit_op, /* Critical operation in progress */ + fsp_mbx_prep_for_reset, /* Prepare for reset sent */ + fsp_mbx_hir_seq_done, /* HIR sequence done, link forced down */ + fsp_mbx_err, /* Mailbox in error state, waiting for r&r */ + fsp_mbx_rr, /* Mailbox in r&r */ +}; + +struct fsp { + struct fsp *link; + unsigned int index; + enum fsp_mbx_state state; + struct fsp_msg *pending; + + unsigned int iopath_count; + int active_iopath; /* -1: no active IO path */ + struct fsp_iopath iopath[FSP_MAX_IOPATH]; +}; + +enum ipl_state { + ipl_initial = 0x00000000, + ipl_opl_sent = 0x00000001, + ipl_got_continue = 0x00000002, + ipl_got_new_role = 0x00000004, + ipl_got_caps = 0x00000008, + ipl_got_fsp_functional = 0x00000010 +}; +static enum ipl_state ipl_state = ipl_initial; + +static struct fsp *first_fsp; +static struct fsp *active_fsp; +static u16 fsp_curseq = 0x8000; +static __be64 *fsp_tce_table; + +#define FSP_INBOUND_SIZE 0x00100000UL +static void *fsp_inbound_buf = NULL; +static u32 fsp_inbound_off; + +static struct lock fsp_lock = LOCK_UNLOCKED; +static struct lock fsp_poll_lock = LOCK_UNLOCKED; + +static u64 fsp_cmdclass_resp_bitmask; +static u64 timeout_timer; + +static u64 fsp_hir_timeout; + +#define FSP_CRITICAL_OP_TIMEOUT 128 +#define FSP_DRCR_CLEAR_TIMEOUT 128 + +/* LID numbers. For now we hijack some of pHyp's own until i figure + * out the whole business with the MasterLID + */ +#define KERNEL_LID_PHYP 0x80a00701 +#define KERNEL_LID_OPAL 0x80f00101 +#define INITRAMFS_LID_OPAL 0x80f00102 + +/* + * We keep track on last logged values for some things to print only on + * value changes, but also to relieve pressure on the tracer which + * doesn't do a very good job at detecting repeats when called from + * many different CPUs + */ +static u32 disr_last_print; +static u32 drcr_last_print; +static u32 hstate_last_print; + +void fsp_handle_resp(struct fsp_msg *msg); + +struct fsp_cmdclass { + int timeout; + bool busy; + struct list_head msgq; + struct list_head clientq; + struct list_head rr_queue; /* To queue up msgs during R/R */ + u64 timesent; +}; + +static struct fsp_cmdclass fsp_cmdclass_rr; + +static struct fsp_cmdclass fsp_cmdclass[FSP_MCLASS_LAST - FSP_MCLASS_FIRST + 1] += { +#define DEF_CLASS(_cl, _to) [_cl - FSP_MCLASS_FIRST] = { .timeout = _to } + DEF_CLASS(FSP_MCLASS_SERVICE, 16), + DEF_CLASS(FSP_MCLASS_PCTRL_MSG, 16), + DEF_CLASS(FSP_MCLASS_PCTRL_ABORTS, 16), + DEF_CLASS(FSP_MCLASS_ERR_LOG, 16), + DEF_CLASS(FSP_MCLASS_CODE_UPDATE, 40), + DEF_CLASS(FSP_MCLASS_FETCH_SPDATA, 16), + DEF_CLASS(FSP_MCLASS_FETCH_HVDATA, 16), + DEF_CLASS(FSP_MCLASS_NVRAM, 16), + DEF_CLASS(FSP_MCLASS_MBOX_SURV, 2), + DEF_CLASS(FSP_MCLASS_RTC, 16), + DEF_CLASS(FSP_MCLASS_SMART_CHIP, 20), + DEF_CLASS(FSP_MCLASS_INDICATOR, 180), + DEF_CLASS(FSP_MCLASS_HMC_INTFMSG, 16), + DEF_CLASS(FSP_MCLASS_HMC_VT, 16), + DEF_CLASS(FSP_MCLASS_HMC_BUFFERS, 16), + DEF_CLASS(FSP_MCLASS_SHARK, 16), + DEF_CLASS(FSP_MCLASS_MEMORY_ERR, 16), + DEF_CLASS(FSP_MCLASS_CUOD_EVENT, 16), + DEF_CLASS(FSP_MCLASS_HW_MAINT, 16), + DEF_CLASS(FSP_MCLASS_VIO, 16), + DEF_CLASS(FSP_MCLASS_SRC_MSG, 16), + DEF_CLASS(FSP_MCLASS_DATA_COPY, 16), + DEF_CLASS(FSP_MCLASS_TONE, 16), + DEF_CLASS(FSP_MCLASS_VIRTUAL_NVRAM, 16), + DEF_CLASS(FSP_MCLASS_TORRENT, 16), + DEF_CLASS(FSP_MCLASS_NODE_PDOWN, 16), + DEF_CLASS(FSP_MCLASS_DIAG, 16), + DEF_CLASS(FSP_MCLASS_PCIE_LINK_TOPO, 16), + DEF_CLASS(FSP_MCLASS_OCC, 16), + DEF_CLASS(FSP_MCLASS_TRUSTED_BOOT, 2), + DEF_CLASS(FSP_MCLASS_HBRT, 2), +}; + +static void fsp_trace_msg(struct fsp_msg *msg, u8 dir __unused) +{ + union trace fsp __unused; +#ifdef FSP_TRACE_MSG + size_t len = offsetof(struct trace_fsp_msg, data[msg->dlen]); + + fsp.fsp_msg.dlen = msg->dlen; + fsp.fsp_msg.word0 = cpu_to_be32(msg->word0); + fsp.fsp_msg.word1 = cpu_to_be32(msg->word1); + fsp.fsp_msg.dir = dir; + memcpy(fsp.fsp_msg.data, msg->data.bytes, msg->dlen); + trace_add(&fsp, TRACE_FSP_MSG, len); +#endif /* FSP_TRACE_MSG */ + assert(msg->dlen <= sizeof(fsp.fsp_msg.data)); +} + +static struct fsp *fsp_get_active(void) +{ + /* XXX Handle transition between FSPs */ + return active_fsp; +} + +static u64 fsp_get_class_bit(u8 class) +{ + /* Alias classes CE and CF as the FSP has a single queue */ + if (class == FSP_MCLASS_IPL) + class = FSP_MCLASS_SERVICE; + + return 1ul << (class - FSP_MCLASS_FIRST); +} + +static struct fsp_cmdclass *__fsp_get_cmdclass(u8 class) +{ + struct fsp_cmdclass *ret; + + /* RR class is special */ + if (class == FSP_MCLASS_RR_EVENT) + return &fsp_cmdclass_rr; + + /* Bound check */ + if (class < FSP_MCLASS_FIRST || class > FSP_MCLASS_LAST) + return NULL; + + /* Alias classes CE and CF as the FSP has a single queue */ + if (class == FSP_MCLASS_IPL) + class = FSP_MCLASS_SERVICE; + + ret = &fsp_cmdclass[class - FSP_MCLASS_FIRST]; + + /* Unknown class */ + if (ret->timeout == 0) + return NULL; + + return ret; +} + +static struct fsp_cmdclass *fsp_get_cmdclass(struct fsp_msg *msg) +{ + u8 c = msg->word0 & 0xff; + + return __fsp_get_cmdclass(c); +} + +static struct fsp_msg *__fsp_allocmsg(void) +{ + return zalloc(sizeof(struct fsp_msg)); +} + +struct fsp_msg *fsp_allocmsg(bool alloc_response) +{ + struct fsp_msg *msg; + + msg = __fsp_allocmsg(); + if (!msg) + return NULL; + if (alloc_response) { + msg->resp = __fsp_allocmsg(); + if (!msg->resp) { + free(msg); + return NULL; + } + } + + return msg; +} + +void __fsp_freemsg(struct fsp_msg *msg) +{ + free(msg); +} + +void fsp_freemsg(struct fsp_msg *msg) +{ + if (msg && msg->resp) + __fsp_freemsg(msg->resp); + __fsp_freemsg(msg); +} + +void fsp_cancelmsg(struct fsp_msg *msg) +{ + bool need_unlock = false; + struct fsp_cmdclass* cmdclass = fsp_get_cmdclass(msg); + + if (!fsp_in_rr()) { + prerror("FSP: Message cancel allowed only when" + "FSP is in reset\n"); + return; + } + + if (!cmdclass) + return; + + /* Recursive locking */ + need_unlock = lock_recursive(&fsp_lock); + + list_del(&msg->link); + msg->state = fsp_msg_cancelled; + + if (need_unlock) + unlock(&fsp_lock); +} + +static void fsp_wreg(struct fsp *fsp, u32 reg, u32 val) +{ + struct fsp_iopath *iop; + + if (fsp->active_iopath < 0) + return; + iop = &fsp->iopath[fsp->active_iopath]; + if (iop->state == fsp_path_bad) + return; + out_be32(iop->fsp_regs + reg, val); +} + +static u32 fsp_rreg(struct fsp *fsp, u32 reg) +{ + struct fsp_iopath *iop; + + if (fsp->active_iopath < 0) + return 0xffffffff; + iop = &fsp->iopath[fsp->active_iopath]; + if (iop->state == fsp_path_bad) + return 0xffffffff; + return in_be32(iop->fsp_regs + reg); +} + +static void fsp_reg_dump(void) +{ +#define FSP_DUMP_ONE(x) \ + prlog(PR_DEBUG, " %20s: %x\n", #x, fsp_rreg(fsp, x)); + + struct fsp *fsp = fsp_get_active(); + + if (!fsp) + return; + + prlog(PR_DEBUG, "FSP #%d: Register dump (state=%d)\n", + fsp->index, fsp->state); + FSP_DUMP_ONE(FSP_DRCR_REG); + FSP_DUMP_ONE(FSP_DISR_REG); + FSP_DUMP_ONE(FSP_MBX1_HCTL_REG); + FSP_DUMP_ONE(FSP_MBX1_FCTL_REG); + FSP_DUMP_ONE(FSP_MBX2_HCTL_REG); + FSP_DUMP_ONE(FSP_MBX2_FCTL_REG); + FSP_DUMP_ONE(FSP_SDES_REG); + FSP_DUMP_ONE(FSP_HDES_REG); + FSP_DUMP_ONE(FSP_HDIR_REG); + FSP_DUMP_ONE(FSP_HDIM_SET_REG); + FSP_DUMP_ONE(FSP_PDIR_REG); + FSP_DUMP_ONE(FSP_PDIM_SET_REG); + FSP_DUMP_ONE(FSP_SCRATCH0_REG); + FSP_DUMP_ONE(FSP_SCRATCH1_REG); + FSP_DUMP_ONE(FSP_SCRATCH2_REG); + FSP_DUMP_ONE(FSP_SCRATCH3_REG); +} + +static void fsp_notify_rr_state(u32 state) +{ + struct fsp_client *client, *next; + struct fsp_cmdclass *cmdclass = __fsp_get_cmdclass(FSP_MCLASS_RR_EVENT); + + assert(cmdclass); + list_for_each_safe(&cmdclass->clientq, client, next, link) + client->message(state, NULL); +} + +static void fsp_reset_cmdclass(void) +{ + int i; + struct fsp_msg *msg; + + /* + * The FSP is in reset and hence we can't expect any response + * to outstanding messages that we've already sent. Clear the + * bitmap to reflect that. + */ + fsp_cmdclass_resp_bitmask = 0; + for (i = 0; i <= (FSP_MCLASS_LAST - FSP_MCLASS_FIRST); i++) { + struct fsp_cmdclass *cmdclass = &fsp_cmdclass[i]; + cmdclass->busy = false; + cmdclass->timesent = 0; + + /* Make sure the message queue is empty */ + while(!list_empty(&cmdclass->msgq)) { + msg = list_pop(&cmdclass->msgq, struct fsp_msg, + link); + list_add_tail(&cmdclass->rr_queue, &msg->link); + } + } +} + +static bool fsp_in_hir(struct fsp *fsp) +{ + switch (fsp->state) { + case fsp_mbx_crit_op: + case fsp_mbx_prep_for_reset: + return true; + default: + return false; + } +} + +static bool fsp_in_reset(struct fsp *fsp) +{ + switch (fsp->state) { + case fsp_mbx_hir_seq_done: /* FSP reset triggered */ + case fsp_mbx_err: /* Will be reset soon */ + case fsp_mbx_rr: /* Mbx activity stopped pending reset */ + return true; + default: + return false; + } +} + +bool fsp_in_rr(void) +{ + struct fsp *fsp = fsp_get_active(); + struct fsp_iopath *iop; + + if (fsp->active_iopath < 0) + return true; + + iop = &fsp->iopath[fsp->active_iopath]; + + if (fsp_in_reset(fsp) || fsp_in_hir(fsp) || !(psi_check_link_active(iop->psi))) + return true; + + return false; +} + +static bool fsp_hir_state_timeout(void) +{ + u64 now = mftb(); + + if (tb_compare(now, fsp_hir_timeout) == TB_AAFTERB) + return true; + + return false; +} + +static void fsp_set_hir_timeout(u32 seconds) +{ + u64 now = mftb(); + fsp_hir_timeout = now + secs_to_tb(seconds); +} + +static bool fsp_crit_op_in_progress(struct fsp *fsp) +{ + u32 disr = fsp_rreg(fsp, FSP_DISR_REG); + + if (disr & FSP_DISR_CRIT_OP_IN_PROGRESS) + return true; + + return false; +} + +/* Notify the FSP that it will be reset soon by writing to the DRCR */ +static void fsp_prep_for_reset(struct fsp *fsp) +{ + u32 drcr; + + /* + * Its possible that the FSP went into reset by itself between the + * time the HIR is triggered and we get here. Check and bail out if so. + */ + if (fsp_in_rr()) + return; + + drcr = fsp_rreg(fsp, FSP_DRCR_REG); + + prlog(PR_TRACE, "FSP: Writing reset to DRCR\n"); + drcr_last_print = drcr; + fsp_wreg(fsp, FSP_DRCR_REG, (drcr | FSP_PREP_FOR_RESET_CMD)); + fsp->state = fsp_mbx_prep_for_reset; + fsp_set_hir_timeout(FSP_DRCR_CLEAR_TIMEOUT); +} + +static void fsp_hir_poll(struct fsp *fsp, struct psi *psi) +{ + u32 drcr; + + if (fsp_in_reset(fsp) || !(psi_check_link_active(psi))) + return; + + switch (fsp->state) { + case fsp_mbx_crit_op: + if (fsp_crit_op_in_progress(fsp)) { + if (fsp_hir_state_timeout()) + prerror("FSP: Critical operation timeout\n"); + /* XXX What do do next? Check with FSP folks */ + } else { + fsp_prep_for_reset(fsp); + } + break; + case fsp_mbx_prep_for_reset: + drcr = fsp_rreg(fsp, FSP_DRCR_REG); + + if (drcr != drcr_last_print) { + prlog(PR_TRACE, "FSP: DRCR changed, old = %x," + " new = %x\n", + drcr_last_print, drcr); + drcr_last_print = drcr; + } + + if (drcr & FSP_DRCR_ACK_MASK) { + if (fsp_hir_state_timeout()) { + prerror("FSP: Ack timeout. Triggering reset\n"); + psi_reset_fsp(psi); + fsp->state = fsp_mbx_hir_seq_done; + } + } else { + prlog(PR_TRACE, "FSP: DRCR ack received." + " Triggering reset\n"); + psi_reset_fsp(psi); + fsp->state = fsp_mbx_hir_seq_done; + } + break; + default: + break; + } +} + +/* + * This is the main entry for the host initiated reset case. + * This gets called when: + * a. Surveillance ack is not received in 120 seconds + * b. A mailbox command doesn't get a response within the stipulated time. + */ +static void __fsp_trigger_reset(void) +{ + struct fsp *fsp = fsp_get_active(); + u32 disr; + + /* Already in one of the error processing states */ + if (fsp_in_hir(fsp) || fsp_in_reset(fsp)) + return; + + prerror("FSP: fsp_trigger_reset() entry\n"); + + drcr_last_print = 0; + /* + * Check if we are allowed to reset the FSP. We aren't allowed to + * reset the FSP if the FSP_DISR_DBG_IN_PROGRESS is set. + */ + disr = fsp_rreg(fsp, FSP_DISR_REG); + if (disr & FSP_DISR_DBG_IN_PROGRESS) { + prerror("FSP: Host initiated reset disabled\n"); + return; + } + + /* + * Check if some critical operation is in progress as indicated + * by FSP_DISR_CRIT_OP_IN_PROGRESS. Timeout is 128 seconds + */ + if (fsp_crit_op_in_progress(fsp)) { + prlog(PR_NOTICE, "FSP: Critical operation in progress\n"); + fsp->state = fsp_mbx_crit_op; + fsp_set_hir_timeout(FSP_CRITICAL_OP_TIMEOUT); + } else + fsp_prep_for_reset(fsp); +} + +static uint32_t fsp_hir_reason_plid; + +void fsp_trigger_reset(uint32_t plid) +{ + lock(&fsp_lock); + fsp_hir_reason_plid = plid; + __fsp_trigger_reset(); + unlock(&fsp_lock); +} + +/* + * Called when we trigger a HIR or when the FSP tells us via the DISR's + * RR bit that one is impending. We should therefore stop all mbox activity. + */ +static void fsp_start_rr(struct fsp *fsp) +{ + struct fsp_iopath *iop; + + if (fsp->state == fsp_mbx_rr) + return; + + /* We no longer have an active path on that FSP */ + if (fsp->active_iopath >= 0) { + iop = &fsp->iopath[fsp->active_iopath]; + iop->state = fsp_path_bad; + fsp->active_iopath = -1; + } + fsp->state = fsp_mbx_rr; + disr_last_print = 0; + hstate_last_print = 0; + + /* + * Mark all command classes as non-busy and clear their + * timeout, then flush all messages in our staging queue + */ + fsp_reset_cmdclass(); + + /* Notify clients. We have to drop the lock here */ + unlock(&fsp_lock); + fsp_notify_rr_state(FSP_RESET_START); + lock(&fsp_lock); + + /* + * Unlike earlier, we don't trigger the PSI link polling + * from this point. We wait for the PSI interrupt to tell + * us the FSP is really down and then start the polling there. + */ +} + +/* + * Called on normal/quick shutdown to give up the PSI link + */ +void fsp_reset_links(void) +{ + struct fsp *fsp = fsp_get_active(); + struct fsp_iopath *iop; + + if (!fsp) + return; + + /* Already in one of the error states? */ + if (fsp_in_hir(fsp) || fsp_in_reset(fsp)) + return; + + iop = &fsp->iopath[fsp->active_iopath]; + prlog(PR_NOTICE, "FSP #%d: Host initiated shutdown." + " Giving up the PSI link\n", fsp->index); + psi_disable_link(iop->psi); + return; +} + +static void fsp_trace_event(struct fsp *fsp, u32 evt, + u32 data0, u32 data1, u32 data2, u32 data3) +{ + union trace tfsp __unused; +#ifdef FSP_TRACE_EVENT + size_t len = sizeof(struct trace_fsp_event); + + tfsp.fsp_evt.event = cpu_to_be16(evt); + tfsp.fsp_evt.fsp_state = cpu_to_be16(fsp->state); + tfsp.fsp_evt.data[0] = cpu_to_be32(data0); + tfsp.fsp_evt.data[1] = cpu_to_be32(data1); + tfsp.fsp_evt.data[2] = cpu_to_be32(data2); + tfsp.fsp_evt.data[3] = cpu_to_be32(data3); + trace_add(&tfsp, TRACE_FSP_EVENT, len); +#endif /* FSP_TRACE_EVENT */ +} + +static void fsp_handle_errors(struct fsp *fsp) +{ + u32 hstate; + struct fsp_iopath *iop; + struct psi *psi; + u32 disr; + + if (fsp->active_iopath < 0) { + prerror("FSP #%d: fsp_handle_errors() with no active IOP\n", + fsp->index); + return; + } + + iop = &fsp->iopath[fsp->active_iopath]; + if (!iop->psi) { + prerror("FSP: Active IOP with no PSI link !\n"); + return; + } + psi = iop->psi; + + /* + * If the link is not up, start R&R immediately, we do call + * psi_disable_link() in this case as while the link might + * not be up, it might still be enabled and the PSI layer + * "active" bit still set + */ + if (!psi_check_link_active(psi)) { + /* Start R&R process */ + fsp_trace_event(fsp, TRACE_FSP_EVT_LINK_DOWN, 0, 0, 0, 0); + prerror("FSP #%d: Link down, starting R&R\n", fsp->index); + + fsp_start_rr(fsp); + return; + } + + /* Link is up, check for other conditions */ + disr = fsp_rreg(fsp, FSP_DISR_REG); + + /* If in R&R, log values */ + if (disr != disr_last_print) { + fsp_trace_event(fsp, TRACE_FSP_EVT_DISR_CHG, disr, 0, 0, 0); + + prlog(PR_TRACE, "FSP #%d: DISR stat change = 0x%08x\n", + fsp->index, disr); + disr_last_print = disr; + } + + /* On a deferred mbox error, trigger a HIR + * Note: We may never get here since the link inactive case is handled + * above and the other case is when the iop->psi is NULL, which is + * quite rare. + */ + if (fsp->state == fsp_mbx_err) { + uint32_t plid; + plid = log_simple_error(&e_info(OPAL_RC_FSP_MBOX_ERR), + "FSP #%d: Triggering HIR on mbx_err\n", + fsp->index); + fsp_trigger_reset(plid); + return; + } + + /* + * If we get here as part of normal flow, the FSP is telling + * us that there will be an impending R&R, so we stop all mbox + * activity. The actual link down trigger is via a PSI + * interrupt that may arrive in due course. + */ + if (disr & FSP_DISR_FSP_IN_RR) { + /* + * If we get here with DEBUG_IN_PROGRESS also set, the + * FSP is in debug and we should *not* reset it now + */ + if (disr & FSP_DISR_DBG_IN_PROGRESS) + return; + + /* + * When the linux comes back up, we still see that bit + * set for a bit, so just move on, nothing to see here + */ + if (fsp->state == fsp_mbx_rr) + return; + + if (fsp_dpo_pending) { + /* + * If we are about to process a reset when DPO + * is pending, its possible that the host has + * gone down, and OPAL is on its way down and + * hence will not see the subsequent PSI interrupt. + * So, just give up the link here. + */ + prlog(PR_NOTICE, "FSP #%d: FSP reset with DPO pending." + " Giving up PSI link\n", + fsp->index); + psi_disable_link(psi); + } else { + prlog(PR_NOTICE, "FSP #%d: FSP in Reset." + " Waiting for PSI interrupt\n", + fsp->index); + } + fsp_start_rr(fsp); + } + + /* + * However, if any of Unit Check or Runtime Termintated or + * Flash Terminated bits is also set, the FSP is asking us + * to trigger a HIR so it can try to recover via the DRCR route. + */ + if (disr & FSP_DISR_HIR_TRIGGER_MASK) { + const char *reason = "Unknown FSP_DISR_HIR_TRIGGER"; + uint32_t plid; + fsp_trace_event(fsp, TRACE_FSP_EVT_SOFT_RR, disr, 0, 0, 0); + + if (disr & FSP_DISR_FSP_UNIT_CHECK) + reason = "DISR Unit Check set"; + else if (disr & FSP_DISR_FSP_RUNTIME_TERM) + reason = "DISR Runtime Terminate set"; + else if (disr & FSP_DISR_FSP_FLASH_TERM) + reason = "DISR Flash Terminate set"; + + plid = log_simple_error(&e_info(OPAL_RC_FSP_DISR_HIR_MASK), + "FSP: %s. Triggering host initiated " + "reset.", reason); + + /* Clear all interrupt conditions */ + fsp_wreg(fsp, FSP_HDIR_REG, FSP_DBIRQ_ALL); + + /* Make sure this happened */ + fsp_rreg(fsp, FSP_HDIR_REG); + + fsp_trigger_reset(plid); + return; + } + + /* + * We detect an R&R complete indication, acknolwedge it + */ + if (disr & FSP_DISR_FSP_RR_COMPLETE) { + /* + * Acking this bit doens't make it go away immediately, so + * only do it while still in R&R state + */ + if (fsp->state == fsp_mbx_rr) { + fsp_trace_event(fsp, TRACE_FSP_EVT_RR_COMPL, 0,0,0,0); + + prlog(PR_NOTICE, "FSP #%d: Detected R&R complete," + " acking\n", fsp->index); + + /* Clear HDATA area */ + fsp_wreg(fsp, FSP_MBX1_HDATA_AREA, 0xff); + + /* Ack it (XDN) and clear HPEND & counts */ + fsp_wreg(fsp, FSP_MBX1_HCTL_REG, + FSP_MBX_CTL_PTS | + FSP_MBX_CTL_XDN | + FSP_MBX_CTL_HPEND | + FSP_MBX_CTL_HCSP_MASK | + FSP_MBX_CTL_DCSP_MASK); + + /* + * Mark the mbox as usable again so we can process + * incoming messages + */ + fsp->state = fsp_mbx_idle; + + /* Also clear R&R complete bit in DISR */ + fsp_wreg(fsp, FSP_DISR_REG, FSP_DISR_FSP_RR_COMPLETE); + + psi_enable_fsp_interrupt(psi); + } + } + + /* + * XXX + * + * Here we detect a number of errors, should we initiate + * and R&R ? + */ + + hstate = fsp_rreg(fsp, FSP_HDES_REG); + if (hstate != hstate_last_print) { + fsp_trace_event(fsp, TRACE_FSP_EVT_HDES_CHG, hstate, 0, 0, 0); + + prlog(PR_DEBUG, "FSP #%d: HDES stat change = 0x%08x\n", + fsp->index, hstate); + hstate_last_print = hstate; + } + + if (hstate == 0xffffffff) + return; + + /* Clear errors */ + fsp_wreg(fsp, FSP_HDES_REG, FSP_DBERRSTAT_CLR1); + + /* + * Most of those errors shouldn't have happened, we just clear + * the error state and return. In the long run, we might want + * to start retrying commands, switching FSPs or links, etc... + * + * We currently don't set our mailbox to a permanent error state. + */ + if (hstate & FSP_DBERRSTAT_ILLEGAL1) + prerror("FSP #%d: Illegal command error !\n", fsp->index); + + if (hstate & FSP_DBERRSTAT_WFULL1) + prerror("FSP #%d: Write to a full mbox !\n", fsp->index); + + if (hstate & FSP_DBERRSTAT_REMPTY1) + prerror("FSP #%d: Read from an empty mbox !\n", fsp->index); + + if (hstate & FSP_DBERRSTAT_PAR1) + prerror("FSP #%d: Parity error !\n", fsp->index); +} + +/* + * This is called by fsp_post_msg() to check if the mbox + * is in a state that allows sending of a message + * + * Due to the various "interesting" contexts fsp_post_msg() + * can be called from, including recursive locks from lock + * error messages or console code, this should avoid doing + * anything more complex than checking a bit of state. + * + * Specifically, we cannot initiate an R&R and call back into + * clients etc... from this function. + * + * The best we can do is to se the mbox in error state and + * handle it later during a poll or interrupts. + */ +static bool fsp_check_can_send(struct fsp *fsp) +{ + struct fsp_iopath *iop; + struct psi *psi; + + /* Look for FSP in non-idle state */ + if (fsp->state != fsp_mbx_idle) + return false; + + /* Look for an active IO path */ + if (fsp->active_iopath < 0) + goto mbox_error; + iop = &fsp->iopath[fsp->active_iopath]; + if (!iop->psi) { + prerror("FSP: Active IOP with no PSI link !\n"); + goto mbox_error; + } + psi = iop->psi; + + /* Check if link has gone down. This will be handled later */ + if (!psi_check_link_active(psi)) { + prerror("FSP #%d: Link seems to be down on send\n", fsp->index); + goto mbox_error; + } + + /* XXX Do we want to check for other error conditions ? */ + return true; + + /* + * An error of some case occurred, we'll handle it later + * from a more normal "poll" context + */ + mbox_error: + fsp->state = fsp_mbx_err; + return false; +} + +static bool fsp_post_msg(struct fsp *fsp, struct fsp_msg *msg) +{ + u32 ctl, reg; + int i, wlen; + + prlog(PR_INSANE, "FSP #%d: fsp_post_msg (w0: 0x%08x w1: 0x%08x)\n", + fsp->index, msg->word0, msg->word1); + + /* Note: We used to read HCTL here and only modify some of + * the bits in it. This was bogus, because we would write back + * the incoming bits as '1' and clear them, causing fsp_poll() + * to then miss them. Let's just start with 0, which is how + * I suppose the HW intends us to do. + */ + + /* Set ourselves as busy */ + fsp->pending = msg; + fsp->state = fsp_mbx_send; + msg->state = fsp_msg_sent; + + /* We trace after setting the mailbox state so that if the + * tracing recurses, it ends up just queuing the message up + */ + fsp_trace_msg(msg, TRACE_FSP_MSG_OUT); + + /* Build the message in the mailbox */ + reg = FSP_MBX1_HDATA_AREA; + fsp_wreg(fsp, reg, msg->word0); reg += 4; + fsp_wreg(fsp, reg, msg->word1); reg += 4; + wlen = (msg->dlen + 3) >> 2; + for (i = 0; i < wlen; i++) { + fsp_wreg(fsp, reg, fsp_msg_get_data_word(msg, i)); + reg += 4; + } + + /* Write the header */ + fsp_wreg(fsp, FSP_MBX1_HHDR0_REG, (msg->dlen + 8) << 16); + + /* Write the control register */ + ctl = 4 << FSP_MBX_CTL_HCHOST_SHIFT; + ctl |= (msg->dlen + 8) << FSP_MBX_CTL_DCHOST_SHIFT; + ctl |= FSP_MBX_CTL_PTS | FSP_MBX_CTL_SPPEND; + prlog(PR_INSANE, " new ctl: %08x\n", ctl); + fsp_wreg(fsp, FSP_MBX1_HCTL_REG, ctl); + + return true; +} + +static void fsp_poke_queue(struct fsp_cmdclass *cmdclass) +{ + struct fsp *fsp = fsp_get_active(); + struct fsp_msg *msg; + + if (!fsp) + return; + if (!fsp_check_can_send(fsp)) + return; + + /* From here to the point where fsp_post_msg() sets fsp->state + * to !idle we must not cause any re-entrancy (no debug or trace) + * in a code path that may hit fsp_post_msg() (it's ok to do so + * if we are going to bail out), as we are committed to calling + * fsp_post_msg() and so a re-entrancy could cause us to do a + * double-send into the mailbox. + */ + if (cmdclass->busy || list_empty(&cmdclass->msgq)) + return; + + msg = list_top(&cmdclass->msgq, struct fsp_msg, link); + assert(msg); + cmdclass->busy = true; + + if (!fsp_post_msg(fsp, msg)) { + prerror("FSP #%d: Failed to send message\n", fsp->index); + cmdclass->busy = false; + return; + } +} + +static void __fsp_fillmsg(struct fsp_msg *msg, u32 cmd_sub_mod, + u8 add_words, va_list list) +{ + bool response = !!(cmd_sub_mod & 0x1000000); + u8 cmd = (cmd_sub_mod >> 16) & 0xff; + u8 sub = (cmd_sub_mod >> 8) & 0xff; + u8 mod = cmd_sub_mod & 0xff; + int i; + + msg->word0 = cmd & 0xff; + msg->word1 = mod << 8 | sub; + msg->response = response; + msg->dlen = add_words << 2; + + for (i = 0; i < add_words; i++) + fsp_msg_set_data_word(msg, i, va_arg(list, unsigned int)); +} + +void fsp_fillmsg(struct fsp_msg *msg, u32 cmd_sub_mod, u32 add_words, ...) +{ + va_list list; + + va_start(list, add_words); + __fsp_fillmsg(msg, cmd_sub_mod, add_words, list); + va_end(list); +} + +struct fsp_msg *fsp_mkmsg(u32 cmd_sub_mod, u32 add_words, ...) +{ + struct fsp_msg *msg = fsp_allocmsg(!!(cmd_sub_mod & 0x1000000)); + va_list list; + + if (!msg) { + prerror("FSP: Failed to allocate struct fsp_msg\n"); + return NULL; + } + + va_start(list, add_words); + __fsp_fillmsg(msg, cmd_sub_mod, add_words, list); + va_end(list); + + return msg; +} + +/* + * IMPORTANT NOTE: This is *guaranteed* to not call the completion + * routine recusrively for *any* fsp message, either the + * queued one or a previous one. Thus it is *ok* to call + * this function with a lock held which will itself be + * taken by the completion function. + * + * Any change to this implementation must respect this + * rule. This will be especially true of things like + * reset/reload and error handling, if we fail to queue + * we must just return an error, not call any completion + * from the scope of fsp_queue_msg(). + */ +int fsp_queue_msg(struct fsp_msg *msg, void (*comp)(struct fsp_msg *msg)) +{ + struct fsp_cmdclass *cmdclass; + struct fsp *fsp = fsp_get_active(); + bool need_unlock; + u16 seq; + int rc = 0; + + if (!fsp || !msg) + return -1; + + /* Recursive locking */ + need_unlock = lock_recursive(&fsp_lock); + + /* Grab a new sequence number */ + seq = fsp_curseq; + fsp_curseq = fsp_curseq + 1; + if (fsp_curseq == 0) + fsp_curseq = 0x8000; + msg->word0 = (msg->word0 & 0xffff) | seq << 16; + + /* Set completion */ + msg->complete = comp; + + /* Clear response state */ + if (msg->resp) + msg->resp->state = fsp_msg_unused; + + /* Queue the message in the appropriate queue */ + cmdclass = fsp_get_cmdclass(msg); + if (!cmdclass) { + prerror("FSP: Invalid msg in fsp_queue_msg w0/1=0x%08x/%08x\n", + msg->word0, msg->word1); + rc = -1; + goto unlock; + } + + msg->state = fsp_msg_queued; + + /* + * If we have initiated or about to initiate a reset/reload operation, + * we stash the message on the R&R backup queue. Otherwise, queue it + * normally and poke the HW + */ + if (fsp_in_hir(fsp) || fsp_in_reset(fsp)) + list_add_tail(&cmdclass->rr_queue, &msg->link); + else { + list_add_tail(&cmdclass->msgq, &msg->link); + fsp_poke_queue(cmdclass); + } + + unlock: + if (need_unlock) + unlock(&fsp_lock); + + return rc; +} + +/* WARNING: This will drop the FSP lock !!! */ +static void fsp_complete_msg(struct fsp_msg *msg) +{ + struct fsp_cmdclass *cmdclass = fsp_get_cmdclass(msg); + void (*comp)(struct fsp_msg *msg); + + assert(cmdclass); + + prlog(PR_INSANE, " completing msg, word0: 0x%08x\n", msg->word0); + + comp = msg->complete; + list_del_from(&cmdclass->msgq, &msg->link); + cmdclass->busy = false; + msg->state = fsp_msg_done; + + unlock(&fsp_lock); + if (comp) + (*comp)(msg); + lock(&fsp_lock); +} + +/* WARNING: This will drop the FSP lock !!! */ +static void fsp_complete_send(struct fsp *fsp) +{ + struct fsp_msg *msg = fsp->pending; + struct fsp_cmdclass *cmdclass = fsp_get_cmdclass(msg); + + assert(msg); + assert(cmdclass); + + fsp->pending = NULL; + + prlog(PR_INSANE, " completing send, word0: 0x%08x, resp: %d\n", + msg->word0, msg->response); + + if (msg->response) { + u64 setbit = fsp_get_class_bit(msg->word0 & 0xff); + msg->state = fsp_msg_wresp; + fsp_cmdclass_resp_bitmask |= setbit; + cmdclass->timesent = mftb(); + } else + fsp_complete_msg(msg); +} + +static void fsp_alloc_inbound(struct fsp_msg *msg) +{ + u16 func_id = fsp_msg_get_data_word(msg, 0) & 0xffff; + u32 len = fsp_msg_get_data_word(msg, 1); + u32 tce_token = 0, act_len = 0; + u8 rc = 0; + void *buf; + struct fsp_msg *resp; + + prlog(PR_DEBUG, "FSP: Allocate inbound buffer func: %04x len: %d\n", + func_id, len); + + lock(&fsp_lock); + if ((fsp_inbound_off + len) > FSP_INBOUND_SIZE) { + prerror("FSP: Out of space in buffer area !\n"); + rc = 0xeb; + goto reply; + } + + if (!fsp_inbound_buf) { + fsp_inbound_buf = memalign(TCE_PSIZE, FSP_INBOUND_SIZE); + if (!fsp_inbound_buf) { + prerror("FSP: could not allocate fsp_inbound_buf!\n"); + rc = 0xeb; + goto reply; + } + } + + buf = fsp_inbound_buf + fsp_inbound_off; + tce_token = PSI_DMA_INBOUND_BUF + fsp_inbound_off; + len = (len + TCE_MASK) & ~TCE_MASK; + fsp_inbound_off += len; + fsp_tce_map(tce_token, buf, len); + prlog(PR_DEBUG, "FSP: -> buffer at 0x%p, TCE: 0x%08x, alen: 0x%x\n", + buf, tce_token, len); + act_len = len; + + reply: + unlock(&fsp_lock); + + resp = fsp_mkmsg(FSP_RSP_ALLOC_INBOUND | rc, 3, 0, tce_token, act_len); + if (!resp) { + prerror("FSP: response message allocation failed\n"); + return; + } + if (fsp_queue_msg(resp, fsp_freemsg)) { + fsp_freemsg(resp); + prerror("FSP: Failed to queue response message\n"); + return; + } +} + +void *fsp_inbound_buf_from_tce(u32 tce_token) +{ + u32 offset = tce_token - PSI_DMA_INBOUND_BUF; + + if (tce_token < PSI_DMA_INBOUND_BUF || offset >= fsp_inbound_off) { + prerror("FSP: TCE token 0x%x out of bounds\n", tce_token); + return NULL; + } + return fsp_inbound_buf + offset; +} + +static void fsp_repost_queued_msgs_post_rr(void) +{ + struct fsp_msg *msg; + int i; + + for (i = 0; i <= (FSP_MCLASS_LAST - FSP_MCLASS_FIRST); i++) { + struct fsp_cmdclass *cmdclass = &fsp_cmdclass[i]; + bool poke = false; + + while(!list_empty(&cmdclass->rr_queue)) { + msg = list_pop(&cmdclass->rr_queue, + struct fsp_msg, link); + list_add_tail(&cmdclass->msgq, &msg->link); + poke = true; + } + if (poke) + fsp_poke_queue(cmdclass); + } +} + +static bool fsp_local_command(u32 cmd_sub_mod, struct fsp_msg *msg) +{ + u32 cmd = 0; + u32 rsp_data = 0; + struct fsp_msg *resp; + + switch(cmd_sub_mod) { + case FSP_CMD_CONTINUE_IPL: + /* We get a CONTINUE_IPL as a response to OPL */ + prlog(PR_NOTICE, "FSP: Got CONTINUE_IPL !\n"); + ipl_state |= ipl_got_continue; + return true; + + case FSP_CMD_HV_STATE_CHG: + prlog(PR_NOTICE, "FSP: Got HV state change request to %d\n", + msg->data.bytes[0]); + + /* Send response synchronously for now, we might want to + * deal with that sort of stuff asynchronously if/when + * we add support for auto-freeing of messages + */ + resp = fsp_mkmsg(FSP_RSP_HV_STATE_CHG, 0); + if (!resp) + prerror("FSP: Failed to allocate HV state response\n"); + else { + if (fsp_queue_msg(resp, fsp_freemsg)) { + fsp_freemsg(resp); + prerror("FSP: Failed to queue HV state resp\n"); + } + } + return true; + + case FSP_CMD_SP_NEW_ROLE: + /* FSP is assuming a new role */ + prlog(PR_INFO, "FSP: FSP assuming new role\n"); + resp = fsp_mkmsg(FSP_RSP_SP_NEW_ROLE, 0); + if (!resp) + prerror("FSP: Failed to allocate SP role response\n"); + else { + if (fsp_queue_msg(resp, fsp_freemsg)) { + fsp_freemsg(resp); + prerror("FSP: Failed to queue SP role resp\n"); + } + } + ipl_state |= ipl_got_new_role; + return true; + + case FSP_CMD_SP_QUERY_CAPS: + prlog(PR_INFO, "FSP: FSP query capabilities\n"); + /* XXX Do something saner. For now do a synchronous + * response and hard code our capabilities + */ + resp = fsp_mkmsg(FSP_RSP_SP_QUERY_CAPS, 4, 0x3ff80000, 0, 0, 0); + if (!resp) + prerror("FSP: Failed to allocate CAPS response\n"); + else { + if (fsp_queue_msg(resp, fsp_freemsg)) { + fsp_freemsg(resp); + prerror("FSP: Failed to queue CAPS resp\n"); + } + } + ipl_state |= ipl_got_caps; + return true; + case FSP_CMD_FSP_FUNCTNAL: + prlog(PR_INFO, "FSP: Got FSP Functional\n"); + ipl_state |= ipl_got_fsp_functional; + return true; + case FSP_CMD_ALLOC_INBOUND: + fsp_alloc_inbound(msg); + return true; + case FSP_CMD_SP_RELOAD_COMP: + if (msg->data.bytes[3] & PPC_BIT8(0)) { + fsp_fips_dump_notify(fsp_msg_get_data_word(msg, 1), + fsp_msg_get_data_word(msg, 2)); + + if (msg->data.bytes[3] & PPC_BIT8(1)) + prlog(PR_DEBUG, " PLID is %x\n", + fsp_msg_get_data_word(msg, 3)); + } + if (msg->data.bytes[3] & PPC_BIT8(2)) { + prlog(PR_INFO, "FSP: SP Reset/Reload was NOT done\n"); + } else { + prlog(PR_INFO, "FSP: SP says Reset/Reload complete\n"); + /* Notify clients that the FSP is back up */ + fsp_notify_rr_state(FSP_RELOAD_COMPLETE); + fsp_repost_queued_msgs_post_rr(); + } + return true; + case FSP_CMD_CLOSE_HMC_INTF: + /* Close the HMC interface */ + /* Though Sapphire does not support a HMC connection, the FSP + * sends this message when it is trying to open any new + * hypervisor session. So returning an error 0x51. + */ + cmd = FSP_RSP_CLOSE_HMC_INTF | FSP_STAUS_INVALID_HMC_ID; + rsp_data = msg->data.bytes[0] << 24 | msg->data.bytes[1] << 16; + rsp_data &= 0xffff0000; + resp = fsp_mkmsg(cmd, 1, rsp_data); + if (!resp) + prerror("FSP: Failed to allocate HMC close response\n"); + else { + if (fsp_queue_msg(resp, fsp_freemsg)) { + fsp_freemsg(resp); + prerror("FSP: Failed to queue HMC close resp\n"); + } + } + return true; + case FSP_CMD_GET_HIR_PLID: + /* Get Platform Log Id with reason for Host Initiated Reset */ + prlog(PR_DEBUG, "FSP: Sending PLID 0x%x as HIR reason\n", + fsp_hir_reason_plid); + resp = fsp_mkmsg(FSP_RSP_GET_HIR_PLID, 1, fsp_hir_reason_plid); + if (!resp) + prerror("FSP: Failed to allocate GET_HIR_PLID response\n"); + else { + if (fsp_queue_msg(resp, fsp_freemsg)) { + fsp_freemsg(resp); + prerror("FSP: Failed to queue GET_HIR_PLID resp\n"); + } + } + fsp_hir_reason_plid = 0; + return true; + } + return false; +} + + +/* This is called without the FSP lock */ +static void fsp_handle_command(struct fsp_msg *msg) +{ + struct fsp_cmdclass *cmdclass = fsp_get_cmdclass(msg); + struct fsp_client *client, *next; + struct fsp_msg *resp; + u32 cmd_sub_mod; + + if (!cmdclass) { + prerror("FSP: Got message for unknown class %x\n", + msg->word0 & 0xff); + goto free; + } + + cmd_sub_mod = (msg->word0 & 0xff) << 16; + cmd_sub_mod |= (msg->word1 & 0xff) << 8; + cmd_sub_mod |= (msg->word1 >> 8) & 0xff; + + /* Some commands are handled locally */ + if (fsp_local_command(cmd_sub_mod, msg)) + goto free; + + /* The rest go to clients */ + list_for_each_safe(&cmdclass->clientq, client, next, link) { + if (client->message(cmd_sub_mod, msg)) + goto free; + } + + prerror("FSP: Unhandled message %06x\n", cmd_sub_mod); + + /* We don't know whether the message expected some kind of + * response, so we send one anyway + */ + resp = fsp_mkmsg((cmd_sub_mod & 0xffff00) | 0x008020, 0); + if (!resp) + prerror("FSP: Failed to allocate default response\n"); + else { + if (fsp_queue_msg(resp, fsp_freemsg)) { + fsp_freemsg(resp); + prerror("FSP: Failed to queue default response\n"); + } + } + + free: + fsp_freemsg(msg); +} + +static void __fsp_fill_incoming(struct fsp *fsp, struct fsp_msg *msg, + int dlen, u32 w0, u32 w1) +{ + unsigned int wlen, i, reg; + + msg->dlen = dlen - 8; + msg->word0 = w0; + msg->word1 = w1; + wlen = (dlen + 3) >> 2; + reg = FSP_MBX1_FDATA_AREA + 8; + for (i = 0; i < wlen; i++) { + fsp_msg_set_data_word(msg, i, fsp_rreg(fsp, reg)); + reg += 4; + } + + /* Ack it (XDN) and clear HPEND & counts */ + fsp_wreg(fsp, FSP_MBX1_HCTL_REG, + FSP_MBX_CTL_PTS | + FSP_MBX_CTL_XDN | + FSP_MBX_CTL_HPEND | + FSP_MBX_CTL_HCSP_MASK | + FSP_MBX_CTL_DCSP_MASK); + + fsp_trace_msg(msg, TRACE_FSP_MSG_IN); +} + +static void __fsp_drop_incoming(struct fsp *fsp) +{ + /* Ack it (XDN) and clear HPEND & counts */ + fsp_wreg(fsp, FSP_MBX1_HCTL_REG, + FSP_MBX_CTL_PTS | + FSP_MBX_CTL_XDN | + FSP_MBX_CTL_HPEND | + FSP_MBX_CTL_HCSP_MASK | + FSP_MBX_CTL_DCSP_MASK); +} + +/* WARNING: This will drop the FSP lock */ +static void fsp_handle_incoming(struct fsp *fsp) +{ + struct fsp_msg *msg; + u32 h0, w0, w1; + unsigned int dlen; + bool special_response = false; + + h0 = fsp_rreg(fsp, FSP_MBX1_FHDR0_REG); + dlen = (h0 >> 16) & 0xff; + + w0 = fsp_rreg(fsp, FSP_MBX1_FDATA_AREA); + w1 = fsp_rreg(fsp, FSP_MBX1_FDATA_AREA + 4); + + prlog(PR_INSANE, " Incoming: w0: 0x%08x, w1: 0x%08x, dlen: %d\n", + w0, w1, dlen); + + /* Some responses are expected out of band */ + if ((w0 & 0xff) == FSP_MCLASS_HMC_INTFMSG && + ((w1 & 0xff) == 0x8a || ((w1 & 0xff) == 0x8b))) + special_response = true; + + /* Check for response bit */ + if (w1 & 0x80 && !special_response) { + struct fsp_cmdclass *cmdclass = __fsp_get_cmdclass(w0 & 0xff); + struct fsp_msg *req; + + if (!cmdclass) { + prerror("FSP: Got response for unknown class %x\n", + w0 & 0xff); + __fsp_drop_incoming(fsp); + return; + } + + if (!cmdclass->busy || list_empty(&cmdclass->msgq)) { + prerror("FSP #%d: Got orphan response! w0 = 0x%08x w1 = 0x%08x\n", + fsp->index, w0, w1); + __fsp_drop_incoming(fsp); + return; + } + req = list_top(&cmdclass->msgq, struct fsp_msg, link); + + /* Check if the response seems to match the message */ + if (req->state != fsp_msg_wresp || + (req->word0 & 0xff) != (w0 & 0xff) || + (req->word1 & 0xff) != (w1 & 0x7f)) { + __fsp_drop_incoming(fsp); + prerror("FSP #%d: Response doesn't match pending msg. w0 = 0x%08x w1 = 0x%08x\n", + fsp->index, w0, w1); + return; + } else { + u64 resetbit = ~fsp_get_class_bit(req->word0 & 0xff); + fsp_cmdclass_resp_bitmask &= resetbit; + cmdclass->timesent = 0; + } + + /* Allocate response if needed XXX We need to complete + * the original message with some kind of error here ? + */ + if (!req->resp) { + req->resp = __fsp_allocmsg(); + if (!req->resp) { + __fsp_drop_incoming(fsp); + prerror("FSP #%d: Failed to allocate response\n", + fsp->index); + return; + } + } + + /* Populate and complete (will drop the lock) */ + req->resp->state = fsp_msg_response; + __fsp_fill_incoming(fsp, req->resp, dlen, w0, w1); + fsp_complete_msg(req); + return; + } + + /* Allocate an incoming message */ + msg = __fsp_allocmsg(); + if (!msg) { + __fsp_drop_incoming(fsp); + prerror("FSP #%d: Failed to allocate incoming msg\n", + fsp->index); + return; + } + msg->state = fsp_msg_incoming; + __fsp_fill_incoming(fsp, msg, dlen, w0, w1); + + /* Handle FSP commands. This can recurse into fsp_queue_msg etc.. */ + unlock(&fsp_lock); + fsp_handle_command(msg); + lock(&fsp_lock); +} + +static void fsp_check_queues(struct fsp *fsp) +{ + int i; + + /* XXX In the long run, we might want to have a queue of + * classes waiting to be serviced to speed this up, either + * that or a bitmap. + */ + for (i = 0; i <= (FSP_MCLASS_LAST - FSP_MCLASS_FIRST); i++) { + struct fsp_cmdclass *cmdclass = &fsp_cmdclass[i]; + + if (fsp->state != fsp_mbx_idle) + break; + if (cmdclass->busy || list_empty(&cmdclass->msgq)) + continue; + fsp_poke_queue(cmdclass); + } +} + +static void __fsp_poll(bool interrupt) +{ + struct fsp_iopath *iop; + struct fsp *fsp = fsp_get_active(); + u32 ctl, hdir = 0; + bool psi_irq; + + /* + * The tracer isn't terribly efficient at detecting dups + * especially when coming from multiple CPUs so we do our + * own change-detection locally + */ + static u32 hdir_last_trace; + static u32 ctl_last_trace; + static bool psi_irq_last_trace; + static bool irq_last_trace; + + if (!fsp) + return; + + /* Crazy interrupt handling scheme: + * + * In order to avoid "losing" interrupts when polling the mbox + * we only clear interrupt conditions when called as a result of + * an interrupt. + * + * That way, if a poll clears, for example, the HPEND condition, + * the interrupt remains, causing a dummy interrupt later on + * thus allowing the OS to be notified of a state change (ie it + * doesn't need every poll site to monitor every state change). + * + * However, this scheme is complicated by the fact that we need + * to clear the interrupt condition after we have cleared the + * original condition in HCTL, and we might have long stale + * interrupts which we do need to eventually get rid of. However + * clearing interrupts in such a way is racy, so we need to loop + * and re-poll HCTL after having done so or we might miss an + * event. It's a latency risk, but unlikely and probably worth it. + */ + + again: + if (fsp->active_iopath < 0) { + /* That should never happen */ + if (interrupt && (fsp->state != fsp_mbx_rr)) + prerror("FSP: Interrupt with no working IO path\n"); + return; + } + iop = &fsp->iopath[fsp->active_iopath]; + + /* Check for error state and handle R&R completion */ + fsp_handle_errors(fsp); + + /* Handle host initiated resets */ + if (fsp_in_hir(fsp)) { + fsp_hir_poll(fsp, iop->psi); + return; + } + + /* + * The above might have triggered and R&R, check that we + * are still functional + */ + if ((fsp->active_iopath < 0) || fsp_in_hir(fsp)) + return; + iop = &fsp->iopath[fsp->active_iopath]; + + /* Read interrupt status (we may or may not use it) */ + hdir = fsp_rreg(fsp, FSP_HDIR_REG); + + /* Read control now as well so we can trace them */ + ctl = fsp_rreg(fsp, FSP_MBX1_HCTL_REG); + + /* Ditto with PSI irq state */ + psi_irq = psi_poll_fsp_interrupt(iop->psi); + + /* Trace it if anything changes */ + if (hdir != hdir_last_trace || ctl != ctl_last_trace || + interrupt != irq_last_trace || psi_irq != psi_irq_last_trace) { + fsp_trace_event(fsp, TRACE_FSP_EVT_POLL_IRQ, + interrupt, hdir, ctl, psi_irq); + + hdir_last_trace = hdir; + ctl_last_trace = ctl; + irq_last_trace = interrupt; + psi_irq_last_trace = psi_irq; + } + + /* + * We *MUST* ignore the MBOX2 bits here. While MBOX2 cannot generate + * interrupt, it might still latch some bits here (and we found cases + * where the MBOX2 XUP would be set). If that happens, clearing HDIR + * never works (the bit gets set again immediately) because we don't + * clear the condition in HTCL2 and thus we loop forever. + */ + hdir &= FSP_DBIRQ_MBOX1; + + /* + * Sanity check: If an interrupt is pending and we are in polling + * mode, check that the PSI side is also pending. If some bit is + * set, just clear and move on. + */ + if (hdir && !interrupt && !psi_irq) { + prerror("FSP: WARNING ! HDIR 0x%08x but no PSI irq !\n", hdir); + fsp_wreg(fsp, FSP_HDIR_REG, hdir); + } + + /* + * We should never have the mbox in error state here unless it + * was fine until some printf inside fsp_handle_errors() caused + * the console to poke the FSP which detected a branch new error + * in the process. Let's be safe rather than sorry and handle that + * here + */ + if (fsp_in_hir(fsp) || fsp->state == fsp_mbx_err) { + prerror("FSP: Late error state detection\n"); + goto again; + } + + /* + * If we are in an R&R state with an active IO path, we + * shouldn't be getting interrupts. If we do, just clear + * the condition and print a message + */ + if (fsp->state == fsp_mbx_rr) { + if (interrupt) { + prerror("FSP: Interrupt in RR state [HDIR=0x%08x]\n", + hdir); + fsp_wreg(fsp, FSP_HDIR_REG, hdir); + } + return; + } + + /* Poll FSP CTL */ + if (ctl & (FSP_MBX_CTL_XUP | FSP_MBX_CTL_HPEND)) + prlog(PR_INSANE, "FSP #%d: poll, ctl: %x\n", fsp->index, ctl); + + /* Do we have a pending message waiting to complete ? */ + if (ctl & FSP_MBX_CTL_XUP) { + fsp_wreg(fsp, FSP_MBX1_HCTL_REG, FSP_MBX_CTL_XUP); + if (fsp->state == fsp_mbx_send) { + /* mbox is free */ + fsp->state = fsp_mbx_idle; + + /* Complete message (will break the lock) */ + fsp_complete_send(fsp); + + /* Lock can have been broken, so ctl is now + * potentially invalid, let's recheck + */ + goto again; + } else { + prerror("FSP #%d: Got XUP with no pending message !\n", + fsp->index); + } + } + + if (fsp->state == fsp_mbx_send) { + /* XXX Handle send timeouts!!! */ + } + + /* Is there an incoming message ? This will break the lock as well */ + if (ctl & FSP_MBX_CTL_HPEND) + fsp_handle_incoming(fsp); + + /* Note: Lock may have been broken above, thus ctl might be invalid + * now, don't use it any further. + */ + + /* Check for something else to send */ + if (fsp->state == fsp_mbx_idle) + fsp_check_queues(fsp); + + /* Clear interrupts, and recheck HCTL if any occurred */ + if (interrupt && hdir) { + fsp_wreg(fsp, FSP_HDIR_REG, hdir); + goto again; + } +} + +void fsp_interrupt(void) +{ + lock(&fsp_lock); + __fsp_poll(true); + unlock(&fsp_lock); +} + + +int fsp_sync_msg(struct fsp_msg *msg, bool autofree) +{ + int rc; + + rc = fsp_queue_msg(msg, NULL); + if (rc) + goto bail; + + while(fsp_msg_busy(msg)) { + if (fsp_in_rr()) { + fsp_cancelmsg(msg); + rc = -1; + goto bail; + } + cpu_relax(); + opal_run_pollers(); + } + + switch(msg->state) { + case fsp_msg_done: + rc = 0; + break; + case fsp_msg_timeout: + rc = -1; /* XXX to improve */ + break; + default: + rc = -1; /* Should not happen... (assert ?) */ + } + + if (msg->resp) + rc = (msg->resp->word1 >> 8) & 0xff; + bail: + if (autofree) + fsp_freemsg(msg); + return rc; +} + +void fsp_register_client(struct fsp_client *client, u8 msgclass) +{ + struct fsp_cmdclass *cmdclass = __fsp_get_cmdclass(msgclass); + + if (!fsp_present()) + return; + assert(cmdclass); + list_add_tail(&cmdclass->clientq, &client->link); +} + +void fsp_unregister_client(struct fsp_client *client, u8 msgclass) +{ + struct fsp_cmdclass *cmdclass = __fsp_get_cmdclass(msgclass); + + if (!fsp_present()) + return; + assert(cmdclass); + list_del_from(&cmdclass->clientq, &client->link); +} + +static int fsp_init_mbox(struct fsp *fsp) +{ + unsigned int i; + u32 reg; + + /* + * Note: The documentation contradicts itself as to + * whether the HDIM bits should be set or cleared to + * enable interrupts + * + * This seems to work... + */ + + /* Mask all interrupts */ + fsp_wreg(fsp, FSP_HDIM_CLR_REG, FSP_DBIRQ_ALL); + + /* Clear all errors */ + fsp_wreg(fsp, FSP_HDES_REG, FSP_DBERRSTAT_CLR1 | FSP_DBERRSTAT_CLR2); + + /* Initialize data area as the doco says */ + for (i = 0; i < 0x40; i += 4) + fsp_wreg(fsp, FSP_MBX1_HDATA_AREA + i, 0); + + /* + * Clear whatever crap may remain in HDCR. Do not write XDN as that + * would be interpreted incorrectly as an R&R completion which + * we aren't ready to send yet ! + */ + fsp_wreg(fsp, FSP_MBX1_HCTL_REG, FSP_MBX_CTL_XUP | FSP_MBX_CTL_HPEND | + FSP_MBX_CTL_HCSP_MASK | FSP_MBX_CTL_DCSP_MASK | + FSP_MBX_CTL_PTS); + + /* Clear all pending interrupts */ + fsp_wreg(fsp, FSP_HDIR_REG, FSP_DBIRQ_ALL); + + /* Enable all mbox1 interrupts */ + fsp_wreg(fsp, FSP_HDIM_SET_REG, FSP_DBIRQ_MBOX1); + + /* Decode what FSP we are connected to */ + reg = fsp_rreg(fsp, FSP_SCRATCH0_REG); + if (reg & PPC_BIT32(0)) { /* Is it a valid connection */ + if (reg & PPC_BIT32(3)) + prlog(PR_INFO, "FSP: Connected to FSP-B\n"); + else + prlog(PR_INFO, "FSP: Connected to FSP-A\n"); + } + + return 0; +} + +/* We use a single fixed TCE table for all PSI interfaces */ +static void fsp_init_tce_table(void) +{ + fsp_tce_table = (__be64 *)PSI_TCE_TABLE_BASE; + + memset(fsp_tce_table, 0, PSI_TCE_TABLE_SIZE); +} + +void fsp_tce_map(u32 offset, void *addr, u32 size) +{ + u64 raddr = (u64)addr; + + assert(!(offset & TCE_MASK)); + assert(!(raddr & TCE_MASK)); + assert(!(size & TCE_MASK)); + + size >>= TCE_SHIFT; + offset >>= TCE_SHIFT; + + while(size--) { + fsp_tce_table[offset++] = cpu_to_be64(raddr | 0x3); + raddr += TCE_PSIZE; + } +} + +void fsp_tce_unmap(u32 offset, u32 size) +{ + assert(!(offset & TCE_MASK)); + assert(!(size & TCE_MASK)); + + size >>= TCE_SHIFT; + offset >>= TCE_SHIFT; + + while(size--) + fsp_tce_table[offset++] = 0; +} + +static struct fsp *fsp_find_by_index(int index) +{ + struct fsp *fsp = first_fsp; + + do { + if (fsp->index == index) + return fsp; + } while (fsp->link != first_fsp); + + return NULL; +} + +static void fsp_init_links(struct dt_node *fsp_node) +{ + const struct dt_property *linksprop; + int i, index; + struct fsp *fsp; + struct fsp_iopath *fiop; + + linksprop = dt_find_property(fsp_node, "ibm,psi-links"); + assert(linksprop); + + index = dt_prop_get_u32(fsp_node, "reg"); + fsp = fsp_find_by_index(index); + if (!fsp) { + prerror("FSP: FSP with index %d not found\n", index); + return; + } + + fsp->state = fsp_mbx_idle; + + /* Iterate all links */ + for (i = 0; i < fsp->iopath_count; i++) { + u64 reg; + u32 link; + + link = dt_property_get_cell(linksprop, i); + fiop = &fsp->iopath[i]; + fiop->psi = psi_find_link(link); + if (fiop->psi == NULL) { + prerror("FSP #%d: Couldn't find PSI link\n", + fsp->index); + continue; + } + + prlog(PR_DEBUG, "FSP #%d: Found PSI HB link to chip %d\n", + fsp->index, link); + + psi_fsp_link_in_use(fiop->psi); + + /* Get the FSP register window */ + reg = in_be64(fiop->psi->regs + PSIHB_FSPBAR); + fiop->fsp_regs = (void *)(reg | (1ULL << 63) | + dt_prop_get_u32(fsp_node, "reg-offset")); + } +} + +static void fsp_update_links_states(struct fsp *fsp) +{ + struct fsp_iopath *fiop; + unsigned int i; + + /* Iterate all links */ + for (i = 0; i < fsp->iopath_count; i++) { + fiop = &fsp->iopath[i]; + if (!fiop->psi) + fiop->state = fsp_path_bad; + else if (fiop->psi->active) { + fsp->active_iopath = i; + fiop->state = fsp_path_active; + } else + fiop->state = fsp_path_backup; + } + + if (fsp->active_iopath >= 0) { + if (!active_fsp || (active_fsp != fsp)) + active_fsp = fsp; + + fsp_inbound_off = 0; + fiop = &fsp->iopath[fsp->active_iopath]; + psi_init_for_fsp(fiop->psi); + fsp_init_mbox(fsp); + } +} + +void fsp_reinit_fsp(void) +{ + struct fsp *fsp; + + /* Notify all FSPs to check for an updated link state */ + for (fsp = first_fsp; fsp; fsp = fsp->link) + fsp_update_links_states(fsp); +} + +static void fsp_create_fsp(struct dt_node *fsp_node) +{ + const struct dt_property *linksprop; + struct fsp *fsp; + int count, index; + + index = dt_prop_get_u32(fsp_node, "reg"); + prlog(PR_INFO, "FSP #%d: Found in device-tree, setting up...\n", + index); + + linksprop = dt_find_property(fsp_node, "ibm,psi-links"); + if (!linksprop || linksprop->len < 4) { + prerror("FSP #%d: No links !\n", index); + return; + } + + fsp = zalloc(sizeof(struct fsp)); + if (!fsp) { + prerror("FSP #%d: Can't allocate memory !\n", index); + return; + } + + fsp->index = index; + fsp->active_iopath = -1; + + count = linksprop->len / 4; + prlog(PR_DEBUG, "FSP #%d: Found %d IO PATH\n", index, count); + if (count > FSP_MAX_IOPATH) { + prerror("FSP #%d: WARNING, limited to %d IO PATH\n", + index, FSP_MAX_IOPATH); + count = FSP_MAX_IOPATH; + } + fsp->iopath_count = count; + + fsp->link = first_fsp; + first_fsp = fsp; + + fsp_init_links(fsp_node); + fsp_update_links_states(fsp); + + if (fsp->active_iopath >= 0) + psi_enable_fsp_interrupt(fsp->iopath[fsp->active_iopath].psi); +} + +static void fsp_opal_poll(void *data __unused) +{ + /* Test the host initiated reset */ + if (hir_trigger == 0xdeadbeef) { + uint32_t plid = log_simple_error(&e_info(OPAL_INJECTED_HIR), + "SURV: Injected HIR, initiating FSP R/R\n"); + fsp_trigger_reset(plid); + hir_trigger = 0; + } + + if (try_lock(&fsp_lock)) { + __fsp_poll(false); + unlock(&fsp_lock); + } +} + +int fsp_fatal_msg(struct fsp_msg *msg) +{ + int rc = 0; + + rc = fsp_queue_msg(msg, NULL); + if (rc) + return rc; + + while(fsp_msg_busy(msg)) { + if (fsp_in_rr()) { + fsp_cancelmsg(msg); + return -1; + } + + cpu_relax(); + fsp_opal_poll(NULL); + } + + switch(msg->state) { + case fsp_msg_done: + rc = 0; + break; + case fsp_msg_timeout: + rc = -1; /* XXX to improve */ + break; + default: + rc = -1; /* Should not happen... (assert ?) */ + } + + if (msg->resp) + rc = (msg->resp->word1 >> 8) & 0xff; + + return rc; +} + +static bool fsp_init_one(const char *compat) +{ + struct dt_node *fsp_node; + bool inited = false; + + dt_for_each_compatible(dt_root, fsp_node, compat) { + if (!inited) { + int i; + + /* Initialize the per-class msg queues */ + for (i = 0; + i <= (FSP_MCLASS_LAST - FSP_MCLASS_FIRST); i++) { + list_head_init(&fsp_cmdclass[i].msgq); + list_head_init(&fsp_cmdclass[i].clientq); + list_head_init(&fsp_cmdclass[i].rr_queue); + } + + /* Init the queues for RR notifier cmdclass */ + list_head_init(&fsp_cmdclass_rr.msgq); + list_head_init(&fsp_cmdclass_rr.clientq); + list_head_init(&fsp_cmdclass_rr.rr_queue); + + /* Register poller */ + opal_add_poller(fsp_opal_poll, NULL); + + inited = true; + } + + /* Create the FSP data structure */ + fsp_create_fsp(fsp_node); + } + + return inited; +} + +void fsp_init(void) +{ + prlog(PR_DEBUG, "FSP: Looking for FSP...\n"); + + fsp_init_tce_table(); + + if (!fsp_init_one("ibm,fsp1") && !fsp_init_one("ibm,fsp2")) { + prlog(PR_DEBUG, "FSP: No FSP on this machine\n"); + return; + } +} + +bool fsp_present(void) +{ + return first_fsp != NULL; +} + +static void fsp_timeout_poll(void *data __unused) +{ + u64 now = mftb(); + u64 timeout_val = 0; + u64 cmdclass_resp_bitmask = fsp_cmdclass_resp_bitmask; + struct fsp_cmdclass *cmdclass = NULL; + struct fsp_msg *req = NULL; + u32 index = 0; + + if (timeout_timer == 0) + timeout_timer = now + secs_to_tb(30); + + /* The lowest granularity for a message timeout is 30 secs. + * So every 30secs, check if there is any message + * waiting for a response from the FSP + */ + if (tb_compare(now, timeout_timer) == TB_ABEFOREB) + return; + if (!try_lock(&fsp_poll_lock)) + return; + if (tb_compare(now, timeout_timer) == TB_ABEFOREB) { + unlock(&fsp_poll_lock); + return; + } + + while (cmdclass_resp_bitmask) { + u64 time_sent = 0; + u64 time_to_comp = 0; + + if (!(cmdclass_resp_bitmask & 0x1)) + goto next_bit; + + cmdclass = &fsp_cmdclass[index]; + timeout_val = secs_to_tb((cmdclass->timeout) * 60); + time_sent = cmdclass->timesent; + time_to_comp = now - cmdclass->timesent; + + /* Now check if the response has timed out */ + if (tb_compare(time_to_comp, timeout_val) == TB_AAFTERB) { + u32 w0, w1; + enum fsp_msg_state mstate; + + /* Take the FSP lock now and re-check */ + lock(&fsp_lock); + if (!(fsp_cmdclass_resp_bitmask & (1ull << index)) || + time_sent != cmdclass->timesent) { + unlock(&fsp_lock); + goto next_bit; + } + req = list_top(&cmdclass->msgq, struct fsp_msg, link); + if (!req) { + printf("FSP: Timeout state mismatch on class %d\n", + index); + fsp_cmdclass_resp_bitmask &= ~(1ull << index); + cmdclass->timesent = 0; + unlock(&fsp_lock); + goto next_bit; + } + w0 = req->word0; + w1 = req->word1; + mstate = req->state; + prlog(PR_WARNING, "FSP: Response from FSP timed out," + " cmd = %x subcmd = %x mod = %x state: %d\n", + w0 & 0xff, w1 & 0xff, (w1 >> 8) & 0xff, mstate); + fsp_reg_dump(); + fsp_cmdclass_resp_bitmask &= ~(1ull << index); + cmdclass->timesent = 0; + if (req->resp) { + req->resp->state = fsp_msg_timeout; + req->resp->word1 = (FSP_STATUS_BUSY << 8) | + (req->resp->word1 & 0xff); + } + fsp_complete_msg(req); + __fsp_trigger_reset(); + unlock(&fsp_lock); + fsp_hir_reason_plid = log_simple_error( + &e_info(OPAL_RC_FSP_POLL_TIMEOUT), + "FSP: Response from FSP timed out," + " cmd = %x subcmd = %x mod = %x state: %d\n", + w0 & 0xff, w1 & 0xff, (w1 >> 8) & 0xff, mstate); + } + next_bit: + cmdclass_resp_bitmask = cmdclass_resp_bitmask >> 1; + index++; + } + unlock(&fsp_poll_lock); +} + +void fsp_opl(void) +{ + struct dt_node *iplp; + + if (!fsp_present()) + return; + + /* Send OPL */ + ipl_state |= ipl_opl_sent; + fsp_sync_msg(fsp_mkmsg(FSP_CMD_OPL, 0), true); + while(!(ipl_state & ipl_got_continue)) { + opal_run_pollers(); + cpu_relax(); + } + + /* Send continue ACK */ + fsp_sync_msg(fsp_mkmsg(FSP_CMD_CONTINUE_ACK, 0), true); + + /* Wait for various FSP messages */ + prlog(PR_INFO, "INIT: Waiting for FSP to advertise new role...\n"); + while(!(ipl_state & ipl_got_new_role)) { + cpu_relax(); + opal_run_pollers(); + } + prlog(PR_INFO, "INIT: Waiting for FSP to request capabilities...\n"); + while(!(ipl_state & ipl_got_caps)) { + cpu_relax(); + opal_run_pollers(); + } + + /* Initiate the timeout poller */ + opal_add_poller(fsp_timeout_poll, NULL); + + /* Tell FSP we are in standby */ + prlog(PR_INFO, "INIT: Sending HV Functional: Standby...\n"); + fsp_sync_msg(fsp_mkmsg(FSP_CMD_HV_FUNCTNAL, 1, 0x01000000), true); + + /* Wait for FSP functional */ + prlog(PR_INFO, "INIT: Waiting for FSP functional\n"); + while(!(ipl_state & ipl_got_fsp_functional)) { + cpu_relax(); + opal_run_pollers(); + } + + /* Tell FSP we are in running state */ + prlog(PR_INFO, "INIT: Sending HV Functional: Runtime...\n"); + fsp_sync_msg(fsp_mkmsg(FSP_CMD_HV_FUNCTNAL, 1, 0x02000000), true); + + /* + * For the factory reset case, FSP sends us the PCI Bus + * Reset request. We don't have to do anything special with + * PCI bus numbers here; just send the Power Down message + * with modifier 0x02 to FSP. + */ + iplp = dt_find_by_path(dt_root, "ipl-params/ipl-params"); + if (iplp && dt_find_property(iplp, "pci-busno-reset-ipl")) { + prlog(PR_DEBUG, "INIT: PCI Bus Reset requested." + " Sending Power Down\n"); + fsp_sync_msg(fsp_mkmsg(FSP_CMD_POWERDOWN_PCIRS, 0), true); + } + + /* + * Tell FSP we are in running state with all partitions. + * + * This is need otherwise the FSP will not reset it's reboot count + * on failures. Ideally we should send that when we know the + * OS is up but we don't currently have a very good way to do + * that so this will do as a stop-gap + */ + prlog(PR_NOTICE, "INIT: Sending HV Functional: Runtime all partitions\n"); + fsp_sync_msg(fsp_mkmsg(FSP_CMD_HV_FUNCTNAL, 1, 0x04000000), true); +} + +uint32_t fsp_adjust_lid_side(uint32_t lid_no) +{ + struct dt_node *iplp; + const char *side = NULL; + + iplp = dt_find_by_path(dt_root, "ipl-params/ipl-params"); + if (iplp) + side = dt_prop_get_def(iplp, "cec-ipl-side", NULL); + if (!side || !strcmp(side, "temp")) + lid_no |= ADJUST_T_SIDE_LID_NO; + return lid_no; +} + +struct fsp_fetch_lid_item { + enum resource_id id; + uint32_t idx; + + uint32_t lid; + uint32_t lid_no; + uint64_t bsize; + uint32_t offset; + void *buffer; + size_t *length; + size_t remaining; + size_t chunk_requested; + struct list_node link; + int result; +}; + +/* + * We have a queue of things to fetch + * when fetched, it moves to fsp_fetched_lid until we're asked if it + * has been fetched, in which case it's free()d. + * + * Everything is protected with fsp_fetch_lock. + * + * We use PSI_DMA_FETCH TCE entry for this fetching queue. If something + * is in the fsp_fetch_lid_queue, it means we're using this TCE entry! + * + * If we add the first entry to fsp_fetch_lid_queue, we trigger fetching! + */ +static LIST_HEAD(fsp_fetch_lid_queue); +static LIST_HEAD(fsp_fetched_lid); +static struct lock fsp_fetch_lock = LOCK_UNLOCKED; + +/* + * Asynchronous fsp fetch data call + * + * Note: + * buffer = PSI DMA address space + */ +int fsp_fetch_data_queue(uint8_t flags, uint16_t id, uint32_t sub_id, + uint32_t offset, void *buffer, size_t *length, + void (*comp)(struct fsp_msg *msg)) +{ + struct fsp_msg *msg; + uint32_t chunk = *length; + + if (!comp) + return OPAL_PARAMETER; + + msg = fsp_mkmsg(FSP_CMD_FETCH_SP_DATA, 0x6, flags << 16 | id, + sub_id, offset, 0, buffer, chunk); + if (!msg) { + prerror("FSP: allocation failed!\n"); + return OPAL_INTERNAL_ERROR; + } + if (fsp_queue_msg(msg, comp)) { + fsp_freemsg(msg); + prerror("FSP: Failed to queue fetch data message\n"); + return OPAL_INTERNAL_ERROR; + } + return OPAL_SUCCESS; +} + +#define CAPP_IDX_VENICE_DD10 0x100ea +#define CAPP_IDX_VENICE_DD20 0x200ea +#define CAPP_IDX_MURANO_DD20 0x200ef +#define CAPP_IDX_MURANO_DD21 0x201ef +#define CAPP_IDX_NAPLES_DD10 0x100d3 +#define CAPP_IDX_NIMBUS_DD10 0x100d1 +#define CAPP_IDX_NIMBUS_DD20 0x200d1 +#define CAPP_IDX_NIMBUS_DD21 0x201d1 +#define CAPP_IDX_NIMBUS_DD22 0x202d1 +#define CAPP_IDX_NIMBUS_DD23 0x203d1 + +#define IMA_CATALOG_NIMBUS 0x4e0200 +#define IMA_CATALOG_P10_DD1 0x800100 +#define IMA_CATALOG_P10_DD2 0x800200 + + +static struct { + enum resource_id id; + uint32_t idx; + uint32_t lid_no; +} fsp_lid_map[] = { + { RESOURCE_ID_KERNEL, RESOURCE_SUBID_NONE, KERNEL_LID_OPAL }, + { RESOURCE_ID_INITRAMFS,RESOURCE_SUBID_NONE, INITRAMFS_LID_OPAL }, + { RESOURCE_ID_IMA_CATALOG,IMA_CATALOG_NIMBUS, 0x80f00103 }, + { RESOURCE_ID_CAPP, CAPP_IDX_MURANO_DD20, 0x80a02002 }, + { RESOURCE_ID_CAPP, CAPP_IDX_MURANO_DD21, 0x80a02001 }, + { RESOURCE_ID_CAPP, CAPP_IDX_VENICE_DD10, 0x80a02003 }, + { RESOURCE_ID_CAPP, CAPP_IDX_VENICE_DD20, 0x80a02004 }, + { RESOURCE_ID_CAPP, CAPP_IDX_NAPLES_DD10, 0x80a02005 }, + { RESOURCE_ID_CAPP, CAPP_IDX_NIMBUS_DD10, 0x80a02006 }, + { RESOURCE_ID_CAPP, CAPP_IDX_NIMBUS_DD20, 0x80a02007 }, + { RESOURCE_ID_CAPP, CAPP_IDX_NIMBUS_DD21, 0x80a02007 }, + { RESOURCE_ID_CAPP, CAPP_IDX_NIMBUS_DD22, 0x80a02007 }, + { RESOURCE_ID_CAPP, CAPP_IDX_NIMBUS_DD23, 0x80a02007 }, + { RESOURCE_ID_IMA_CATALOG,IMA_CATALOG_P10_DD1, 0x80f00103 }, + { RESOURCE_ID_IMA_CATALOG,IMA_CATALOG_P10_DD2, 0x80f00103 }, +}; + +static void fsp_start_fetching_next_lid(void); +static void fsp_fetch_lid_next_chunk(struct fsp_fetch_lid_item *last); + +static void fsp_fetch_lid_complete(struct fsp_msg *msg) +{ + struct fsp_fetch_lid_item *last; + uint32_t woffset, wlen; + uint8_t rc; + + lock(&fsp_fetch_lock); + last = list_top(&fsp_fetch_lid_queue, struct fsp_fetch_lid_item, link); + fsp_tce_unmap(PSI_DMA_FETCH, last->bsize); + + woffset = fsp_msg_get_data_word(msg->resp, 1); + wlen = fsp_msg_get_data_word(msg->resp, 2); + rc = (msg->resp->word1 >> 8) & 0xff; + + /* Fall back to a PHYP LID for kernel loads */ + if (rc && last->lid_no == KERNEL_LID_OPAL) { + const char *ltype = dt_prop_get_def(dt_root, "lid-type", NULL); + if (!ltype || strcmp(ltype, "opal")) { + prerror("Failed to load in OPAL mode...\n"); + last->result = OPAL_PARAMETER; + last = list_pop(&fsp_fetch_lid_queue, + struct fsp_fetch_lid_item, link); + list_add_tail(&fsp_fetched_lid, &last->link); + fsp_start_fetching_next_lid(); + unlock(&fsp_fetch_lock); + return; + } + printf("Trying to load as PHYP LID...\n"); + last->lid = KERNEL_LID_PHYP; + /* Retry with different LID */ + fsp_fetch_lid_next_chunk(last); + } + + if (rc !=0 && rc != 2) { + last->result = -EIO; + last = list_pop(&fsp_fetch_lid_queue, struct fsp_fetch_lid_item, link); + prerror("FSP LID %08x load ERROR %d\n", last->lid_no, rc); + list_add_tail(&fsp_fetched_lid, &last->link); + fsp_start_fetching_next_lid(); + unlock(&fsp_fetch_lock); + return; + } + + /* + * As per documentation, rc=2 means end of file not reached and + * rc=1 means we reached end of file. But it looks like we always + * get rc=0 irrespective of whether end of file is reached or not. + * The old implementation (fsp_sync_msg) used to rely on + * (wlen < chunk) to decide whether we reached end of file. + * + * Ideally FSP folks should be fix their code as per documentation. + * but until they do, adding the old check (hack) here again. + * + * Without this hack some systems would load partial lid and won't + * be able to boot into petitboot kernel. + */ + if (rc == 0 && (wlen < last->chunk_requested)) + last->result = OPAL_SUCCESS; + + fsp_freemsg(msg); + + last->remaining -= wlen; + *(last->length) += wlen; + last->buffer += wlen; + last->offset += wlen; + + prlog(PR_DEBUG, "FSP: LID %x Chunk read -> rc=0x%02x off: %08x" + " twritten: %08x\n", last->lid, rc, woffset, wlen); + + fsp_fetch_lid_next_chunk(last); + + unlock(&fsp_fetch_lock); +} + +static void fsp_fetch_lid_next_chunk(struct fsp_fetch_lid_item *last) +{ + uint64_t baddr; + uint64_t balign, boff; + uint32_t chunk; + uint32_t taddr; + struct fsp_msg *msg; + uint8_t flags = 0; + uint16_t id = FSP_DATASET_NONSP_LID; + uint32_t sub_id; + + assert(lock_held_by_me(&fsp_fetch_lock)); + + if (last->remaining == 0 || last->result == OPAL_SUCCESS) { + last->result = OPAL_SUCCESS; + last = list_pop(&fsp_fetch_lid_queue, + struct fsp_fetch_lid_item, link); + list_add_tail(&fsp_fetched_lid, &last->link); + fsp_start_fetching_next_lid(); + return; + } + + baddr = (uint64_t)last->buffer; + balign = baddr & ~TCE_MASK; + boff = baddr & TCE_MASK; + + chunk = last->remaining; + if (chunk > (PSI_DMA_FETCH_SIZE - boff)) + chunk = PSI_DMA_FETCH_SIZE - boff; + last->bsize = ((boff + chunk) + TCE_MASK) & ~TCE_MASK; + last->chunk_requested = chunk; + + prlog(PR_DEBUG, "FSP: LID %08x chunk 0x%08x bytes balign=%llx" + " boff=%llx bsize=%llx\n", + last->lid_no, chunk, balign, boff, last->bsize); + + fsp_tce_map(PSI_DMA_FETCH, (void *)balign, last->bsize); + taddr = PSI_DMA_FETCH + boff; + + sub_id = last->lid; + + msg = fsp_mkmsg(FSP_CMD_FETCH_SP_DATA, 6, + flags << 16 | id, sub_id, last->offset, + 0, taddr, chunk); + + if (fsp_queue_msg(msg, fsp_fetch_lid_complete)) { + fsp_freemsg(msg); + prerror("FSP: Failed to queue fetch data message\n"); + last->result = OPAL_INTERNAL_ERROR; + last = list_pop(&fsp_fetch_lid_queue, + struct fsp_fetch_lid_item, link); + list_add_tail(&fsp_fetched_lid, &last->link); + } + last->result = OPAL_BUSY; +} + +static void fsp_start_fetching_next_lid(void) +{ + struct fsp_fetch_lid_item *last; + + assert(lock_held_by_me(&fsp_fetch_lock)); + + last = list_top(&fsp_fetch_lid_queue, struct fsp_fetch_lid_item, link); + + if (last == NULL) + return; + + /* If we're not already fetching */ + if (last->result == OPAL_EMPTY) + fsp_fetch_lid_next_chunk(last); +} + +int fsp_start_preload_resource(enum resource_id id, uint32_t idx, + void *buf, size_t *size) +{ + struct fsp_fetch_lid_item *resource; + uint32_t lid_no = 0; + int i; + + resource = malloc(sizeof(struct fsp_fetch_lid_item)); + assert(resource != NULL); + + resource->id = id; + resource->idx = idx; + + resource->offset = 0; + resource->buffer = buf; + resource->remaining = *size; + *size = 0; + resource->length = size; + resource->result = OPAL_EMPTY; + + for (i = 0; i < ARRAY_SIZE(fsp_lid_map); i++) { + if (id != fsp_lid_map[i].id) + continue; + + if (fsp_lid_map[i].idx == idx) { + lid_no = fsp_lid_map[i].lid_no; + break; + } + } + if (lid_no == 0) + return OPAL_PARAMETER; + + printf("Trying to load OPAL LID %08x...\n", lid_no); + resource->lid_no = lid_no; + resource->lid = fsp_adjust_lid_side(lid_no); + + lock(&fsp_fetch_lock); + list_add_tail(&fsp_fetch_lid_queue, &resource->link); + fsp_start_fetching_next_lid(); + unlock(&fsp_fetch_lock); + + return OPAL_SUCCESS; +} + +int fsp_resource_loaded(enum resource_id id, uint32_t idx) +{ + struct fsp_fetch_lid_item *resource = NULL; + struct fsp_fetch_lid_item *r; + int rc = OPAL_BUSY; + + lock(&fsp_fetch_lock); + list_for_each(&fsp_fetched_lid, r, link) { + if (r->id == id && r->idx == idx) { + resource = r; + break; + } + } + + if (resource) { + rc = resource->result; + list_del(&resource->link); + free(resource); + } + unlock(&fsp_fetch_lock); + + return rc; +} + +static int fsp_lid_loaded(uint32_t lid_no) +{ + struct fsp_fetch_lid_item *resource = NULL; + struct fsp_fetch_lid_item *r; + int rc = OPAL_BUSY; + + lock(&fsp_fetch_lock); + list_for_each(&fsp_fetched_lid, r, link) { + if (r->lid_no == lid_no) { + resource = r; + break; + } + } + + if (resource) { + rc = resource->result; + if (rc == OPAL_SUCCESS) { + list_del(&resource->link); + free(resource); + } + } + unlock(&fsp_fetch_lock); + + return rc; +} + +int fsp_preload_lid(uint32_t lid_no, char *buf, size_t *size) +{ + struct fsp_fetch_lid_item *resource; + int r = OPAL_SUCCESS; + + resource = malloc(sizeof(struct fsp_fetch_lid_item)); + assert(resource != NULL); + + resource->id = -1; + resource->idx = -1; + + resource->offset = 0; + resource->buffer = buf; + resource->remaining = *size; + *size = 0; + resource->length = size; + resource->result = OPAL_EMPTY; + + if (lid_no == 0) + return OPAL_PARAMETER; + + printf("Trying to load LID %08x from FSP\n", lid_no); + resource->lid_no = lid_no; + resource->lid = fsp_adjust_lid_side(lid_no); + + lock(&fsp_fetch_lock); + list_add_tail(&fsp_fetch_lid_queue, &resource->link); + fsp_start_fetching_next_lid(); + unlock(&fsp_fetch_lock); + + return r; +} + +int fsp_wait_lid_loaded(uint32_t lid_no) +{ + int r; + int waited = 0; + + r = fsp_lid_loaded(lid_no); + + while(r == OPAL_BUSY) { + opal_run_pollers(); + time_wait_nopoll(msecs_to_tb(5)); + waited+=5; + cpu_relax(); + r = fsp_lid_loaded(lid_no); + } + + prlog(PR_DEBUG, "FSP: fsp_wait_lid_loaded %x %u ms\n", lid_no, waited); + + return r; +} + +void fsp_used_by_console(void) +{ + fsp_lock.in_con_path = true; + + /* + * Some other processor might hold it without having + * disabled the console locally so let's make sure that + * is over by taking/releasing the lock ourselves + */ + lock(&fsp_lock); + unlock(&fsp_lock); +} |