new file mode 100644
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "coredump.h"
+#include "recovery.h"
+#include "mib.h"
+#include "ela.h"
+#ifdef CONFIG_CL_PCIE
+#include "bus/pci/ipc.h"
+#endif
+#include "chip.h"
+#include "config.h"
+
+#include "fw/fw_dbg.h"
+
+#include <linux/devcoredump.h>
+
+static int cl_coredump_generate(struct cl_hw *cl_hw)
+{
+ struct cl_coredump *dump;
+
+ dump = cl_fw_dbg_prepare_coredump(cl_hw);
+ if (!dump)
+ return -ENODATA;
+
+ dev_coredumpv(cl_hw->chip->dev, dump, le32_to_cpu(dump->len),
+ GFP_KERNEL);
+
+ return 0;
+}
+
+static void cl_coredump_done(struct cl_hw *cl_hw)
+{
+ /*
+ * Print MIB counters only if watchdog is disabled,
+ * otherwise the dump of prints effects the recovery
+ */
+ if (cl_hw->conf->ce_fw_watchdog_mode == FW_WD_DISABLE)
+ cl_mib_cntrs_dump(cl_hw);
+
+ if (!test_bit(CL_DEV_STARTED, &cl_hw->drv_flags))
+ return;
+
+ /*
+ * Assuming firmware cannot request next dump before we release the host buffer
+ * so no need to sync the following against error_ind()
+ */
+ cl_hw->debugfs.trace_prst = false;
+#ifdef CONFIG_CL_PCIE
+ cl_ipc_dbginfobuf_push(cl_hw->ipc_env, cl_hw->dbginfo.dma_addr);
+#endif
+ if (cl_hw->dbginfo.buf->u.dump.general_data.error_type == DBG_ERROR_FATAL ||
+ cl_hw->assert_info.restart_needed) {
+ cl_dbg_err(cl_hw, "Starting recovery due to unrecoverable assert\n");
+ cl_recovery_start(cl_hw, RECOVERY_UNRECOVERABLE_ASSERT);
+ }
+}
+
+static void cl_coredump_work(struct work_struct *ws)
+{
+ struct cl_debugfs *debugfs = container_of(ws, struct cl_debugfs, coredump_work);
+ struct cl_hw *cl_hw = container_of(debugfs, struct cl_hw, debugfs);
+ unsigned long flags;
+
+ debugfs->coredump_call_tstamp = jiffies;
+
+ cl_coredump_generate(cl_hw);
+ if (cl_ela_is_on(cl_hw->chip)) {
+ cl_ela_lcu_reset(cl_hw->chip);
+ cl_ela_lcu_apply_config(cl_hw->chip);
+ }
+
+ spin_lock_irqsave(&debugfs->coredump_lock, flags);
+ if (!debugfs->unregistering)
+ cl_coredump_done(cl_hw);
+ debugfs->coredump_scheduled = false;
+ spin_unlock_irqrestore(&debugfs->coredump_lock, flags);
+}
+
+int cl_coredump_trigger(struct cl_hw *cl_hw)
+{
+ struct cl_debugfs *debugfs = &cl_hw->debugfs;
+ unsigned long flags;
+ unsigned long curr_time = jiffies;
+ unsigned int diff_time = jiffies_to_msecs(curr_time - debugfs->coredump_call_tstamp);
+
+ if (diff_time < cl_hw->conf->ci_coredump_diff_time_ms) {
+#ifdef CONFIG_CL_PCIE
+ cl_ipc_dbginfobuf_push(cl_hw->ipc_env, cl_hw->dbginfo.dma_addr);
+#endif
+ cl_dbg_verbose(cl_hw,
+ "Skip coredump - time from previous call=%u m-sec\n",
+ diff_time);
+ return -1;
+ }
+
+ spin_lock_irqsave(&debugfs->coredump_lock, flags);
+ if (debugfs->coredump_scheduled) {
+ spin_unlock_irqrestore(&debugfs->coredump_lock, flags);
+ cl_dbg_verbose(cl_hw, ": Already scheduled\n");
+ return -EBUSY;
+ }
+
+ if (debugfs->unregistering) {
+ spin_unlock_irqrestore(&debugfs->coredump_lock, flags);
+ cl_dbg_verbose(cl_hw, ": unregistering\n");
+ return -ENOENT;
+ }
+
+ debugfs->coredump_scheduled = true;
+ debugfs->trace_prst = true;
+ ktime_get_real_ts64(&cl_hw->dbginfo.trigger_tstamp);
+
+ schedule_work(&debugfs->coredump_work);
+ spin_unlock_irqrestore(&debugfs->coredump_lock, flags);
+
+ return 0;
+}
+
+bool cl_coredump_recovery(struct cl_hw *cl_hw, int reason)
+{
+ struct cl_debugfs *debugfs = &cl_hw->debugfs;
+ unsigned long flags;
+ bool need_restart = false;
+
+ spin_lock_irqsave(&debugfs->coredump_lock, flags);
+
+ if (!debugfs->coredump_scheduled) {
+ cl_dbg_trace(cl_hw,
+ "Starting recovery due to reason:%d\n",
+ reason);
+ cl_recovery_start(cl_hw, reason);
+ } else {
+ need_restart = true;
+ }
+
+ spin_unlock_irqrestore(&debugfs->coredump_lock, flags);
+
+ return need_restart;
+}
+
+bool cl_coredump_is_scheduled(struct cl_hw *cl_hw)
+{
+ struct cl_debugfs *debugfs = &cl_hw->debugfs;
+
+ return debugfs->coredump_scheduled;
+}
+
+void cl_coredump_reset_trace(struct cl_hw *cl_hw)
+{
+ struct cl_debugfs *debugfs = &cl_hw->debugfs;
+
+ debugfs->trace_prst = false;
+}
+
+void cl_coredump_init(struct cl_hw *cl_hw, struct dentry *dir_drv)
+{
+ struct cl_debugfs *debugfs = &cl_hw->debugfs;
+
+ debugfs->dir = dir_drv;
+ debugfs->unregistering = false;
+ debugfs->trace_prst = false;
+ debugfs->coredump_scheduled = false;
+
+ INIT_WORK(&debugfs->coredump_work, cl_coredump_work);
+
+ spin_lock_init(&debugfs->coredump_lock);
+
+ /*
+ * Initialize coredump_call_tstamp to current time minus
+ * (ci_coredump_diff_time_ms + 1), so that if assert happens immediately
+ * coredump will be called.
+ */
+ debugfs->coredump_call_tstamp = jiffies -
+ msecs_to_jiffies(cl_hw->conf->ci_coredump_diff_time_ms + 1);
+}
+
+void cl_coredump_close(struct cl_hw *cl_hw)
+{
+ struct cl_debugfs *debugfs = &cl_hw->debugfs;
+
+ flush_work(&debugfs->coredump_work);
+
+ if (!debugfs->dir)
+ return;
+
+ debugfs->unregistering = true;
+ debugfs_remove_recursive(debugfs->dir);
+ debugfs->dir = NULL;
+}