Kernel watchdog timeout

Implement kernel watchdog that detects when firmware becomes
unresponsive.

Change-Id: I5c5b58a56a2ce629e1fd7cabae83b61823239ea6
diff --git a/kernel/ethosu_watchdog.c b/kernel/ethosu_watchdog.c
new file mode 100644
index 0000000..bde0803
--- /dev/null
+++ b/kernel/ethosu_watchdog.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2022 Arm Limited.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you can access it online at
+ * http://www.gnu.org/licenses/gpl-2.0.html.
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+
+/****************************************************************************
+ * Includes
+ ****************************************************************************/
+
+#include "ethosu_watchdog.h"
+
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+
+/****************************************************************************
+ * Variables
+ ****************************************************************************/
+
+static unsigned long watchdog_timeout_ms = 3000;
+module_param(watchdog_timeout_ms, ulong, 0664);
+MODULE_PARM_DESC(watchdog_timeout_ms,
+		 "Watchdog timeout in milliseconds for unresponsive firmware.");
+
+/****************************************************************************
+ * Functions
+ ****************************************************************************/
+
+static void ethosu_watchdog_update(struct ethosu_watchdog *wdog)
+{
+	int ret;
+
+	ret = mod_timer(&wdog->timer,
+			jiffies + msecs_to_jiffies(watchdog_timeout_ms));
+
+	dev_info(wdog->dev,
+		 "Wdog: Update watchdog timeout. ret=%d, timeout_ms=%lu, refcount=%u", ret,
+		 watchdog_timeout_ms, atomic_read(&wdog->refcount));
+}
+
+static void ethosu_watchdog_work(struct work_struct *work)
+{
+	struct ethosu_watchdog *wdog =
+		container_of(work, struct ethosu_watchdog, work);
+
+	dev_info(wdog->dev, "Wdog: Watchdog timeout. refcount=%u",
+		 atomic_read(&wdog->refcount));
+
+	wdog->callback(wdog);
+}
+
+static void ethosu_watchdog_timeout(struct timer_list *timer)
+{
+	struct ethosu_watchdog *wdog =
+		container_of(timer, struct ethosu_watchdog, timer);
+
+	queue_work(system_unbound_wq, &wdog->work);
+}
+
+#if KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE
+static void ethosu_watchdog_timeout_legacy(unsigned long data)
+{
+	ethosu_watchdog_timeout((struct timer_list *)data);
+}
+
+#endif
+
+int ethosu_watchdog_init(struct ethosu_watchdog *wdog,
+			 struct device *dev,
+			 ethosu_watchdog_cb callback)
+{
+	wdog->dev = dev;
+	wdog->callback = callback;
+	atomic_set(&wdog->refcount, 0);
+	INIT_WORK(&wdog->work, ethosu_watchdog_work);
+
+#if KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE
+	timer_setup(&wdog->timer, ethosu_watchdog_timeout, 0);
+#else
+	setup_timer(&wdog->timer, ethosu_watchdog_timeout_legacy,
+		    (unsigned long)&wdog->timer);
+#endif
+
+	return 0;
+}
+
+void ethosu_watchdog_deinit(struct ethosu_watchdog *wdog)
+{
+	del_timer(&wdog->timer);
+}
+
+int ethosu_watchdog_reset(struct ethosu_watchdog *wdog)
+{
+	del_timer(&wdog->timer);
+	atomic_set(&wdog->refcount, 0);
+
+	return 0;
+}
+
+void ethosu_watchdog_inc(struct ethosu_watchdog *wdog)
+{
+	atomic_inc(&wdog->refcount);
+	ethosu_watchdog_update(wdog);
+}
+
+void ethosu_watchdog_dec(struct ethosu_watchdog *wdog)
+{
+	if (atomic_dec_and_test(&wdog->refcount)) {
+		dev_info(wdog->dev, "Wdog: Cancel watchdog timeout");
+		del_timer(&wdog->timer);
+	} else {
+		ethosu_watchdog_update(wdog);
+	}
+}