new file mode 100644
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "fw/msg_cfm.h"
+#include "fw/msg_rx.h"
+#include "recovery.h"
+#include "reg/reg_ipc.h"
+#include "chip.h"
+#include "hw_assert.h"
+#include "config.h"
+#include "coredump.h"
+
+static void cl_check_exception(struct cl_hw *cl_hw)
+{
+ /* Check if Tensilica exception occurred */
+ int i;
+ struct cl_ipc_exception_struct *data =
+ (struct cl_ipc_exception_struct *)cl_hw->ipc_env->shared;
+
+ if (data->pattern != IPC_EXCEPTION_PATTERN)
+ return;
+
+ cl_dbg_err(cl_hw, "######################### firmware tensilica exception:\n");
+ cl_dbg_err(cl_hw, "................................. type: ");
+
+ switch (data->type) {
+ case 0:
+ cl_dbg_err(cl_hw, "EXCEPTION_ILLEGALINSTRUCTION\n");
+ break;
+ case 2:
+ cl_dbg_err(cl_hw, "EXCEPTION_INSTRUCTIONFETCHERROR\n");
+ break;
+ case 3:
+ cl_dbg_err(cl_hw, "EXCEPTION_LOADSTOREERROR\n");
+ break;
+ case 6:
+ cl_dbg_err(cl_hw, "EXCEPTION_INTEGERDIVIDEBYZERO\n");
+ break;
+ case 7:
+ cl_dbg_err(cl_hw, "EXCEPTION_SPECULATION\n");
+ break;
+ case 8:
+ cl_dbg_err(cl_hw, "EXCEPTION_PRIVILEGED\n");
+ break;
+ case 9:
+ cl_dbg_err(cl_hw, "EXCEPTION_UNALIGNED\n");
+ break;
+ case 16:
+ cl_dbg_err(cl_hw, "EXCEPTION_INSTTLBMISS\n");
+ break;
+ case 17:
+ cl_dbg_err(cl_hw, "EXCEPTION_INSTTLBMULTIHIT\n");
+ break;
+ case 18:
+ cl_dbg_err(cl_hw, "EXCEPTION_INSTFETCHPRIVILEGE\n");
+ break;
+ case 20:
+ cl_dbg_err(cl_hw, "EXCEPTION_INSTFETCHPROHIBITED\n");
+ break;
+ case 24:
+ cl_dbg_err(cl_hw, "EXCEPTION_LOADSTORETLBMISS\n");
+ break;
+ case 25:
+ cl_dbg_err(cl_hw, "EXCEPTION_LOADSTORETLBMULTIHIT\n");
+ break;
+ case 26:
+ cl_dbg_err(cl_hw, "EXCEPTION_LOADSTOREPRIVILEGE\n");
+ break;
+ case 28:
+ cl_dbg_err(cl_hw, "EXCEPTION_LOADPROHIBITED\n");
+ break;
+ default:
+ cl_dbg_err(cl_hw, "unknown\n");
+ break;
+ }
+
+ cl_dbg_err(cl_hw, "................................. EPC: %08X\n", data->epc);
+ cl_dbg_err(cl_hw, "................................. EXCSAVE: %08X\n", data->excsave);
+ cl_dbg_err(cl_hw, "..........................BACKTRACE-PC.........................\n");
+
+ for (i = 0; i < IPC_BACKTRACT_DEPTH; i++)
+ cl_dbg_err(cl_hw, "PC#%d: 0x%08X\n", i, data->backtrace.pc[i]);
+}
+
+static u16 cl_msg_cfm_clear_bit(u16 cfm)
+{
+ if (cfm < MM_REQ_CFM_MAX)
+ return ((cfm - 1) >> 1);
+
+ return ((cfm - 1 - FIRST_MSG(TASK_DBG) + MM_REQ_CFM_MAX) >> 1);
+}
+
+u16 cl_msg_cfm_set_bit(u16 req)
+{
+ if (req < MM_REQ_CFM_MAX)
+ return (req >> 1);
+
+ return ((req - FIRST_MSG(TASK_DBG) + MM_REQ_CFM_MAX) >> 1);
+}
+
+int cl_msg_cfm_wait(struct cl_hw *cl_hw, u16 bit, u16 req_id)
+{
+ /*
+ * Start a timeout to stop on the main waiting queue,
+ * and then check the result.
+ */
+ struct cl_chip *chip = cl_hw->chip;
+ int timeout = 0, error = 0;
+ int max_timeout = 0;
+
+ if (!cl_hw->msg_calib_timeout)
+ max_timeout = CL_MSG_CFM_TIMEOUT_JIFFIES;
+ else
+ max_timeout = CL_MSG_CFM_TIMEOUT_CALIB_JIFFIES;
+
+ /* Wait for confirmation message */
+ timeout = wait_event_timeout(cl_hw->wait_queue,
+ !CFM_TEST_BIT(bit, &cl_hw->cfm_flags),
+ max_timeout);
+
+ if (timeout == 0) {
+ /*
+ * Timeout occurred!
+ * Make sure that confirmation wasn't received after the timeout.
+ */
+ if (CFM_TEST_BIT(bit, &cl_hw->cfm_flags)) {
+ cl_dbg_verbose(cl_hw, "[WARN] Timeout occurred - %s\n",
+ MSG_ID_STR(req_id));
+ error = -ETIMEDOUT;
+ }
+ }
+
+ if (error) {
+ struct cl_irq_stats *irq_stats = &chip->irq_stats;
+ unsigned long now = jiffies, flags;
+ u32 status, raw_status;
+
+ /*
+ * The interrupt was not handled in time, lets try to handle it safely.
+ * The spin lock protects us from the following race scenarios:
+ * 1) atomic read of the IPC status register,
+ * 2) execution on the msg handler twice from different context.
+ * 3) disable context switch from the same core.
+ */
+ spin_lock_irqsave(&chip->isr_lock, flags);
+
+ status = ipc_xmac_2_host_status_get(chip);
+ raw_status = ipc_xmac_2_host_raw_status_get(chip);
+
+ cl_dbg_verbose(cl_hw,
+ "[INFO] status=0x%x, raw_status=0x%x, last_isr_statuses=0x%x, "
+ "last_rx=%ums, last_tx=%ums, last_isr=%ums\n",
+ status,
+ raw_status,
+ irq_stats->last_isr_statuses,
+ jiffies_to_msecs(now - irq_stats->last_rx),
+ jiffies_to_msecs(now - irq_stats->last_tx),
+ jiffies_to_msecs(now - irq_stats->last_isr));
+
+ if (status & cl_hw->ipc_e2a_irq.msg) {
+ /*
+ * WORKAROUND #1: In some cases the kernel is losing sync with the
+ * interrupt handler and the reason is still unknown.
+ * It seems that disabling master interrupt for a couple of cycles and
+ * then re-enabling it restores the sync with the cl interrupt handler.
+ */
+ ipc_host_global_int_en_set(chip, 0);
+
+ /* Acknowledge the MSG interrupt */
+ ipc_xmac_2_host_ack_set(cl_hw->chip, cl_hw->ipc_e2a_irq.msg);
+
+ /*
+ * Unlock before calling cl_msg_rx_tasklet() because
+ * spin_unlock_irqrestore() disables interrupts, but in
+ * cl_msg_rx_tasklet() there might be several places that
+ * use spin_unlock_bh() which enables soft-irqs.
+ */
+ spin_unlock_irqrestore(&chip->isr_lock, flags);
+
+ /*
+ * Call the tasklet handler (it also gives the CPU that
+ * is mapped to the cl_interrupt few cycle to recover)
+ */
+ cl_msg_rx_tasklet((unsigned long)cl_hw);
+
+ /* Re-enable master interrupts */
+ ipc_host_global_int_en_set(chip, 1);
+ } else {
+ /*
+ * WORKAROUND #2: Try to call the handler unconditioanly.
+ * Maybe we cleared the "cl_hw->ipc_e2a_irq.msg" without handling it.
+ */
+
+ /*
+ * Unlock before calling cl_msg_rx_tasklet() because
+ * spin_unlock_irqrestore() disables interrupts, but in
+ * cl_msg_rx_tasklet() there might be several places
+ * that use spin_unlock_bh() which enables soft-irqs.
+ */
+ spin_unlock_irqrestore(&chip->isr_lock, flags);
+
+ /* Call the tasklet handler */
+ cl_msg_rx_tasklet((unsigned long)cl_hw);
+ }
+
+ /* Did the workarounds work? */
+ if (CFM_TEST_BIT(bit, &cl_hw->cfm_flags)) {
+ cl_dbg_verbose(cl_hw, "[ERR] Failed to recover from timeout\n");
+ } else {
+ cl_dbg_verbose(cl_hw, "[INFO] Managed to recover from timeout\n");
+ error = 0;
+ goto exit;
+ }
+
+ /* Failed handling the message */
+ CFM_CLEAR_BIT(bit, &cl_hw->cfm_flags);
+
+ cl_check_exception(cl_hw);
+
+ cl_hw_assert_check(cl_hw);
+
+ if (!strcmp(chip->conf->ce_ela_mode, "XTDEBUG") ||
+ !strcmp(chip->conf->ce_ela_mode, "XTDEBUG_STD")) {
+ /*
+ * TODO: Special debug hack: collect debug info & skip restart
+ * "wait4cfm" string is expected by debug functionality
+ */
+ goto exit;
+ }
+
+ if (!test_bit(CL_DEV_HW_RESTART, &cl_hw->drv_flags) &&
+ !test_bit(CL_DEV_SW_RESTART, &cl_hw->drv_flags) &&
+ test_bit(CL_DEV_STARTED, &cl_hw->drv_flags) &&
+ !cl_hw->is_stop_context) {
+ /* Unlock msg mutex before restarting */
+ mutex_unlock(&cl_hw->msg_tx_mutex);
+
+ if (cl_coredump_is_scheduled(cl_hw))
+ set_bit(CL_DEV_FW_ERROR, &cl_hw->drv_flags);
+ else
+ cl_recovery_start(cl_hw, RECOVERY_WAIT4CFM);
+
+ return error;
+ }
+ }
+
+exit:
+ /* Unlock msg mutex */
+ mutex_unlock(&cl_hw->msg_tx_mutex);
+
+ return error;
+}
+
+static void cl_msg_cfm_assign_params(struct cl_hw *cl_hw, struct cl_ipc_e2a_msg *msg)
+{
+ u32 *param;
+ u16 msg_id = le16_to_cpu(msg->id);
+ u16 msg_len = le16_to_cpu(msg->param_len);
+
+ /* A message sent in background is not allowed to assign confirmation parameters */
+ if (cl_hw->msg_background) {
+ cl_dbg_verbose(cl_hw,
+ "Background message can't assign confirmation parameters (%s)\n",
+ MSG_ID_STR(msg_id));
+ return;
+ }
+
+ if (msg->param_len) {
+ param = kzalloc(msg_len, GFP_ATOMIC);
+ if (param) {
+ memcpy(param, msg->param, msg_len);
+ if (cl_hw->msg_cfm_params[msg_id])
+ cl_dbg_err(cl_hw, "msg_cfm_params is not NULL for %s\n",
+ MSG_ID_STR(msg_id));
+ cl_hw->msg_cfm_params[msg_id] = param;
+ } else {
+ cl_dbg_err(cl_hw, "param allocation failed\n");
+ }
+ } else {
+ u16 dummy_dest_id = le16_to_cpu(msg->dummy_dest_id);
+ u16 dummy_src_id = le16_to_cpu(msg->dummy_src_id);
+
+ cl_dbg_err(cl_hw, "msg->param_len is 0 [%u,%u,%u]\n",
+ msg_id, dummy_dest_id, dummy_src_id);
+ }
+}
+
+void cl_msg_cfm_assign_and_clear(struct cl_hw *cl_hw, struct cl_ipc_e2a_msg *msg)
+{
+ u16 bit = cl_msg_cfm_clear_bit(msg->id);
+
+ if (CFM_TEST_BIT(bit, &cl_hw->cfm_flags)) {
+ cl_msg_cfm_assign_params(cl_hw, msg);
+ CFM_CLEAR_BIT(bit, &cl_hw->cfm_flags);
+ } else {
+ cl_dbg_verbose(cl_hw, "Msg ID not set in cfm_flags (%s)\n", MSG_ID_STR(msg->id));
+ }
+}
+
+void cl_msg_cfm_clear(struct cl_hw *cl_hw, struct cl_ipc_e2a_msg *msg)
+{
+ u16 bit = cl_msg_cfm_clear_bit(msg->id);
+
+ if (!CFM_TEST_AND_CLEAR_BIT(bit, &cl_hw->cfm_flags))
+ cl_dbg_verbose(cl_hw, "Msg ID not set in cfm_flags (%s)\n", MSG_ID_STR(msg->id));
+}
+
+void cl_msg_cfm_simulate_timeout(struct cl_hw *cl_hw)
+{
+ u16 bit = cl_msg_cfm_set_bit(DBG_SET_MOD_FILTER_REQ);
+
+ mutex_lock(&cl_hw->msg_tx_mutex);
+ CFM_SET_BIT(bit, &cl_hw->cfm_flags);
+ cl_msg_cfm_wait(cl_hw, bit, DBG_STR_SHIFT(DBG_SET_MOD_FILTER_REQ));
+}
+