[PATCH 3/4] lib: sbi: Add SBI watchdog framework

Prajwal N prajwal.n at oss.qualcomm.com
Fri Oct 2 01:30:39 PDT 2026


Update the build infrastructure to include the SBI
watchdog compilation.

Implement the SBI watchdog functionality including
start, stop, pat, read and write attributes,
pretimeout and timeout handlers.

Signed-off-by: Prajwal N <prajwal.n at oss.qualcomm.com>
---
 include/sbi/sbi_wdt.h |  72 +++++++
 lib/sbi/objects.mk    |   1 +
 lib/sbi/sbi_init.c    |   8 +
 lib/sbi/sbi_wdt.c     | 455 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 536 insertions(+)
 create mode 100644 include/sbi/sbi_wdt.h
 create mode 100644 lib/sbi/sbi_wdt.c

diff --git a/include/sbi/sbi_wdt.h b/include/sbi/sbi_wdt.h
new file mode 100644
index 00000000..43f866dc
--- /dev/null
+++ b/include/sbi/sbi_wdt.h
@@ -0,0 +1,72 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Qualcomm Inc.
+ *
+ * Authors:
+ *   Prajwal N <prajwal.n at oss.qualcomm.com>
+ */
+
+#ifndef __SBI_WDT_H__
+#define __SBI_WDT_H__
+
+/**
+ * Read the watchdog attribute
+ * @param attribute_id The wdt attribute id to be read
+ * @param attribute_value The value of attribute returned
+ * @return int error
+ */
+int sbi_watchdog_read_attribute(u32 attribute_id, u32* attribute_value);
+
+/**
+ * Write the watchdog attribute
+ * @param attribute_id The wdt attribute id to be written
+ * @param attribute_value The wdt attribute value to be written
+ * @return int error
+ */
+int sbi_watchdog_write_attribute(u32 attribute_id, u32 attribute_value);
+
+/**
+ * Write the watchdog MSI message attribute
+ * @param msi_addr_low The value of MSI low address
+ * @param msi_addr_high The value of MSI high address
+ * @param msi_data The value of MSI data
+ * @return int error
+ */
+int sbi_watchdog_write_notif_msi_message(u32 msi_addr_low, u32 msi_addr_high, u32 msi_data);
+
+/**
+ * Start the watchdog
+ * @param void
+ * @return int error
+ */
+int sbi_watchdog_start(void);
+
+/**
+ * Stop the watchdog
+ * @param void
+ * @return int error
+ */
+int sbi_watchdog_stop(void);
+
+/**
+ * Pat the watchdog
+ * @param void
+ * @return int error
+ */
+int sbi_watchdog_pat(void);
+
+/**
+ * Initialize the watchdog
+ * @param void
+ * @return int error
+ */
+int sbi_watchdog_init(void);
+
+/**
+ * Remove the watchdog
+ * @param void
+ * @return int error
+ */
+int sbi_watchdog_exit(void);
+#endif
diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk
index ae27d0c5..c8bdc94a 100644
--- a/lib/sbi/objects.mk
+++ b/lib/sbi/objects.mk
@@ -106,5 +106,6 @@ endif
 libsbi-objs-y += sbi_unpriv.o
 libsbi-objs-y += sbi_expected_trap.o
 libsbi-objs-y += sbi_cppc.o
+libsbi-objs-y += sbi_wdt.o
 libsbi-objs-$(CC_SUPPORT_VECTOR) += sbi_vector.o
 libsbi-objs-y += sbi_fp.o
diff --git a/lib/sbi/sbi_init.c b/lib/sbi/sbi_init.c
index acd2f8b6..a8ce0a63 100644
--- a/lib/sbi/sbi_init.c
+++ b/lib/sbi/sbi_init.c
@@ -35,6 +35,7 @@
 #include <sbi/sbi_tlb.h>
 #include <sbi/sbi_version.h>
 #include <sbi/sbi_unit_test.h>
+#include <sbi/sbi_wdt.h>
 
 #define BANNER                                              \
 	"   ____                    _____ ____ _____\n"     \
@@ -365,6 +366,13 @@ static void __noreturn init_coldboot(struct sbi_scratch *scratch, u32 hartid)
 		sbi_hart_hang();
 	}
 
+	/* Initialise the watchdog*/
+	rc = sbi_watchdog_init();
+	if (rc) {
+		sbi_printf("%s: watchdog init failed (error %d)\n", __func__, rc);
+		sbi_hart_hang();
+	}
+
 	/*
 	 * Note: Finalize domains after HSM initialization
 	 * Note: Finalize domains before HART PMP configuration so
diff --git a/lib/sbi/sbi_wdt.c b/lib/sbi/sbi_wdt.c
new file mode 100644
index 00000000..988c6581
--- /dev/null
+++ b/lib/sbi/sbi_wdt.c
@@ -0,0 +1,455 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Qualcomm Inc.
+ *
+ * Authors:
+ *   Prajwal N <prajwal.n at oss.qualcomm.com>
+ */
+
+#include <sbi/riscv_asm.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_ecall.h>
+#include <sbi/sbi_bitops.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/riscv_locks.h>
+#include <sbi/sbi_heap.h>
+#include <sbi/sbi_irqchip.h>
+#include <sbi/sbi_timer.h>
+#include <sbi/sbi_wdt.h>
+#include <sbi/sbi_system.h>
+#include <sbi/sbi_console.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_string.h>
+#include <sbi/sbi_hart_protection.h>
+
+/** Watchdog capabitlity - MSI */
+#define WDT_CAP_MSI_MASK	BIT(1)
+
+/** Watchdog capabitlity - SSE */
+#define WDT_CAP_SSE_MASK	BIT(0)
+
+/**
+ * Helpers to enable/disable channel capability bits
+ * _c: capability variable
+ * _m: capability mask
+ */
+#define CAP_ENABLE(_c, _m)	INSERT_FIELD(_c, _m, 1)
+#define CAP_DISABLE(_c, _m)	INSERT_FIELD(_c, _m, 0)
+#define CAP_GET(_c, _m)		EXTRACT_FIELD(_c, _m)
+
+/* Watchdog Permission Flags */
+#define WDT_ATTR_PERM_RO	BIT(0)
+#define WDT_ATTR_PERM_W	 BIT(1)
+#define WDT_ATTR_PERM_RW	(WDT_ATTR_PERM_RO) | (WDT_ATTR_PERM_W)
+
+static const u32 sbi_watchdog_attr_perm[SBI_WDT_ATTR_MAX] = {
+	[SBI_WDT_CAPABILITY]            = WDT_ATTR_PERM_RO,
+	[SBI_WDT_STATE]                 = WDT_ATTR_PERM_RO,
+	[SBI_WDT_MIN_PERIOD]            = WDT_ATTR_PERM_RO,
+	[SBI_WDT_PERIOD]                = WDT_ATTR_PERM_RW,
+	[SBI_WDT_TIME_LEFT]             = WDT_ATTR_PERM_RO,
+	[SBI_WDT_NOTIF_TIME]            = WDT_ATTR_PERM_RW,
+	[SBI_WDT_NOTIF_MSI_ADDR_LOW]    = WDT_ATTR_PERM_RW,
+	[SBI_WDT_NOTIF_MSI_ADDR_HIGH]   = WDT_ATTR_PERM_RW,
+	[SBI_WDT_NOTIF_MSI_DATA]        = WDT_ATTR_PERM_RW
+};
+
+/**
+ * SBI watchdog state
+ */
+typedef enum {
+	SBI_WDT_STOPPED = 0,
+	SBI_WDT_RUNNING	= 1,
+	SBI_WDT_PAUSED  = 2,
+	SBI_WDT_EXPIRED	= 3
+} sbi_wdt_state_t;
+
+/**
+ * SBI watchdog attributes
+ */
+struct sbi_watchdog_attrs {
+	/* MSI and SSE capability flags */
+	u32 wdt_capability;
+	/* Watchdog state */
+	u32 wdt_state;
+	/* Minimum watchdog timeout supported in us */
+	u32 wdt_min_period;
+	/* Watchdog timeout period in us */
+	u32 wdt_period;
+	/* Time left until watchdog expiry in us */
+	u32 wdt_time_left;
+	/* Watchdog pretimeout in us */
+	u32 wdt_notif_time;
+	/* MSI target address low 32-bit */
+	u32 wdt_notif_msi_addr_low;
+	/* MSI target address high 32-bit */
+	u32 wdt_notif_msi_addr_high;
+	/* MSI data */
+	u32 wdt_notif_msi_data;
+};
+
+struct sbi_watchdog {
+	/* Watchdog attributes */
+	struct sbi_watchdog_attrs wdt_attr;
+	/* Watchdog timeout event */
+	struct sbi_timer_event wdt_timer_ev;
+	/* Watchdog pretimeout event */
+	struct sbi_timer_event wdt_pretimeout_ev;
+	/* Watchdog spinlock */
+	spinlock_t wdt_lock;
+};
+
+/* Global pointer to the watchdog structure */
+static struct sbi_watchdog *sbi_system_watchdog;
+
+/** Make sure all the attributes are packed for direct memcpy in ATTR_READ */
+#define assert_field_offset(field, attr_offset)				\
+	_Static_assert(							\
+		((offsetof(struct sbi_watchdog_attrs, field)) /		\
+			sizeof(u32)) == attr_offset,			\
+		"field" #field						\
+		" from struct sbi_watchdog_attrs invalid offset, expected " #attr_offset)
+
+assert_field_offset(wdt_capability, SBI_WDT_CAPABILITY);
+assert_field_offset(wdt_state, SBI_WDT_STATE);
+assert_field_offset(wdt_min_period, SBI_WDT_MIN_PERIOD);
+assert_field_offset(wdt_period, SBI_WDT_PERIOD);
+assert_field_offset(wdt_time_left, SBI_WDT_TIME_LEFT);
+assert_field_offset(wdt_notif_time, SBI_WDT_NOTIF_TIME);
+assert_field_offset(wdt_notif_msi_addr_low, SBI_WDT_NOTIF_MSI_ADDR_LOW);
+assert_field_offset(wdt_notif_msi_addr_high, SBI_WDT_NOTIF_MSI_ADDR_HIGH);
+assert_field_offset(wdt_notif_msi_data, SBI_WDT_NOTIF_MSI_DATA);
+
+int sbi_watchdog_read_attribute(u32 attribute_id, u32* attribute_value)
+{
+	u64 current, deadline, tick_delta;
+	int ret = SBI_SUCCESS;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	switch (attribute_id) {
+	case SBI_WDT_TIME_LEFT:
+		/* Calculate the time left before expiry and update the attribute */
+		current = sbi_timer_value();
+		deadline = sbi_system_watchdog->wdt_timer_ev.time_stamp;
+		if (current >= deadline) {
+			sbi_system_watchdog->wdt_attr.wdt_time_left = 0;
+			break;
+		}
+		tick_delta = deadline - current;
+		sbi_system_watchdog->wdt_attr.wdt_time_left = sbi_timer_ticks_to_usec(tick_delta);
+		break;
+
+	default:
+		/* Return error if the attribute index is out of bound*/
+		if (attribute_id >= SBI_WDT_ATTR_MAX) {
+			ret = SBI_ERR_INVALID_PARAM;
+			goto out;
+		}
+		break;
+	}
+
+	u32 *wdt_attrs = (u32 *)&sbi_system_watchdog->wdt_attr;
+	*attribute_value = wdt_attrs[attribute_id];
+
+out:
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+	return ret;
+}
+
+int sbi_watchdog_write_attribute(u32 attribute_id, u32 attribute_value)
+{
+	int ret;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	/* Check if the attribute is writable */
+	if (!(sbi_watchdog_attr_perm[attribute_id] & WDT_ATTR_PERM_W)) {
+		ret = SBI_ERR_DENIED;
+		goto out;
+	}
+
+	switch (attribute_id) {
+	case SBI_WDT_PERIOD:
+		/* Return error if wdt_period passed is less than wdt_min_period */
+		if (attribute_value < sbi_system_watchdog->wdt_attr.wdt_min_period) {
+			ret = SBI_ERR_FAILED;
+			goto out;
+		}
+		break;
+
+	case SBI_WDT_NOTIF_TIME:
+		/* Ignore the write if the MSI capability is not enabled */
+		if (!CAP_GET(sbi_system_watchdog->wdt_attr.wdt_capability, WDT_CAP_MSI_MASK)) {
+			ret = SBI_SUCCESS;
+			goto out;
+		}
+
+		/* Returen error if the notif time is greater than the timeout period*/
+		if (attribute_value >= sbi_system_watchdog->wdt_attr.wdt_period) {
+			ret = SBI_ERR_FAILED;
+			goto out;
+		}
+		break;
+
+	case SBI_WDT_NOTIF_MSI_ADDR_LOW:
+	case SBI_WDT_NOTIF_MSI_ADDR_HIGH:
+	case SBI_WDT_NOTIF_MSI_DATA:
+		/* Ignore the write if the MSI capability is not enabled */
+		if (!CAP_GET(sbi_system_watchdog->wdt_attr.wdt_capability, WDT_CAP_MSI_MASK)) {
+			ret = SBI_SUCCESS;
+			goto out;
+		}
+
+		/*
+		 * The MSI address and data can be written only when
+		 * the notifications are disabled
+		 */
+		if (sbi_system_watchdog->wdt_attr.wdt_notif_time != 0) {
+			ret = SBI_ERR_FAILED;
+			goto out;
+		}
+		break;
+
+	default:
+		/* Return error if the attribute index is out of bound*/
+		if (attribute_id >= SBI_WDT_ATTR_MAX) {
+			ret = SBI_ERR_INVALID_PARAM;
+			goto out;
+		}
+		break;
+	}
+
+	u32 *wdt_attrs = (u32 *)&sbi_system_watchdog->wdt_attr;
+	wdt_attrs[attribute_id] = attribute_value;
+
+	ret = SBI_SUCCESS;
+
+out:
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+	return ret;
+}
+
+int sbi_watchdog_write_notif_msi_message(u32 msi_addr_low, u32 msi_addr_high, u32 msi_data)
+{
+	int ret;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	/* Ignore the write if the MSI capability is not enabled */
+	if (!CAP_GET(sbi_system_watchdog->wdt_attr.wdt_capability, WDT_CAP_MSI_MASK)) {
+		ret = SBI_SUCCESS;
+		goto out;
+	}
+
+	/* The MSI address and data can be written only when the notifications are disabled */
+	if (sbi_system_watchdog->wdt_attr.wdt_notif_time != 0) {
+		ret = SBI_EINVAL;
+		goto out;
+	}
+
+	u32 *wdt_attrs = (u32 *)&sbi_system_watchdog->wdt_attr;
+	wdt_attrs[SBI_WDT_NOTIF_MSI_ADDR_LOW] = msi_addr_low;
+	wdt_attrs[SBI_WDT_NOTIF_MSI_ADDR_HIGH] = msi_addr_high;
+	wdt_attrs[SBI_WDT_NOTIF_MSI_DATA] = msi_data;
+
+	ret = SBI_SUCCESS;
+
+out:
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+	return ret;
+}
+
+static void sbi_watchdog_pretimeout_cleanup(struct sbi_timer_event *ev)
+{
+	/* Nothing to do */
+}
+
+static void sbi_watchdog_pretimeout_callback(struct sbi_timer_event *ev,
+					     struct sbi_timer_event_restart *restart)
+{
+	u64 msi_addr;
+	u32 msi_data;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+	/* Calculate the MSI address using the low and high addresses*/
+	msi_addr = (((u64)sbi_system_watchdog->wdt_attr.wdt_notif_msi_addr_high << 32 ) |
+					sbi_system_watchdog->wdt_attr.wdt_notif_msi_addr_low);
+	msi_data = sbi_system_watchdog->wdt_attr.wdt_notif_msi_data;
+
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+
+	sbi_hart_protection_temp_map_range((unsigned long)msi_addr, sizeof(u32));
+	/* Write the MSI data to the MSI address*/
+	writel(msi_data, (void *)(unsigned long)msi_addr);
+	sbi_hart_protection_temp_unmap_range((unsigned long)msi_addr, sizeof(u32));
+}
+
+static void sbi_watchdog_event_cleanup(struct sbi_timer_event *ev)
+{
+	/* Nothing to do */
+}
+
+static void sbi_watchdog_expired_callback(struct sbi_timer_event *ev,
+					  struct sbi_timer_event_restart *restart)
+{
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	sbi_system_watchdog->wdt_attr.wdt_state = SBI_WDT_EXPIRED;
+	/* Trigger the system reboot*/
+	sbi_system_reset(SBI_SRST_RESET_TYPE_WARM_REBOOT, SBI_SRST_RESET_REASON_NONE);
+
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+}
+
+static void sbi_watchdog_enable_timer_events()
+{
+	u64 timeout, pretimeout;
+	u32 pretimeout_us;
+
+	/* Restart the pretimeout event only if the wdt_notif_time is non zero*/
+	if (sbi_system_watchdog->wdt_attr.wdt_notif_time != 0) {
+		pretimeout_us = sbi_system_watchdog->wdt_attr.wdt_period -
+				sbi_system_watchdog->wdt_attr.wdt_notif_time;
+		pretimeout = sbi_timer_value() + sbi_timer_compute_udelta(pretimeout_us);
+
+		sbi_timer_event_start(&sbi_system_watchdog->wdt_pretimeout_ev, pretimeout);
+	}
+
+	/* Restart the Watchdog timeout event only if the wdt_period is non zero*/
+	if (sbi_system_watchdog->wdt_attr.wdt_period != 0) {
+		timeout = sbi_timer_value() +
+			  sbi_timer_compute_udelta(sbi_system_watchdog->wdt_attr.wdt_period);
+		sbi_timer_event_start(&sbi_system_watchdog->wdt_timer_ev, timeout);
+	}
+}
+
+int sbi_watchdog_start(void)
+{
+	int ret;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	/* Return error if the watchdog is not in SBI_WDT_STOPPED state*/
+	if (sbi_system_watchdog->wdt_attr.wdt_state != SBI_WDT_STOPPED) {
+		ret = SBI_ERR_INVALID_STATE;
+		goto out;
+	}
+
+	sbi_watchdog_enable_timer_events();
+
+	sbi_system_watchdog->wdt_attr.wdt_state = SBI_WDT_RUNNING;
+	ret = SBI_SUCCESS;
+
+out:
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+	return ret;
+}
+
+int sbi_watchdog_stop(void)
+{
+	int ret;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	/* Return error if the watchdog is already in SBI_WDT_STOPPED state */
+	if (sbi_system_watchdog->wdt_attr.wdt_state == SBI_WDT_STOPPED) {
+		ret = SBI_ERR_INVALID_STATE;
+		goto out;
+	}
+
+	/* Stop both the pretimeout and expiry events */
+	sbi_timer_event_stop(&sbi_system_watchdog->wdt_pretimeout_ev);
+	sbi_timer_event_stop(&sbi_system_watchdog->wdt_timer_ev);
+
+	sbi_system_watchdog->wdt_attr.wdt_state = SBI_WDT_STOPPED;
+	sbi_system_watchdog->wdt_attr.wdt_time_left = 0;
+
+	ret = SBI_SUCCESS;
+
+out:
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+	return ret;
+}
+
+int sbi_watchdog_pat(void)
+{
+	int ret;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	/* Return error if the watchdog is not in SBI_WDT_RUNNING state*/
+	if (sbi_system_watchdog->wdt_attr.wdt_state != SBI_WDT_RUNNING) {
+		ret = SBI_ERR_INVALID_STATE;
+		goto out;
+	}
+
+	sbi_watchdog_enable_timer_events();
+	ret = SBI_SUCCESS;
+
+out:
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+	return ret;
+}
+
+static void sbi_watchdog_init_attributes(void)
+{
+	const struct sbi_timer_device *tdev;
+	unsigned long timer_freq;
+	u32 capability = 0;
+
+	spin_lock(&sbi_system_watchdog->wdt_lock);
+
+	sbi_memset(&sbi_system_watchdog->wdt_attr, 0, sizeof(struct sbi_watchdog_attrs));
+
+	/* Retrieve the MSI capability from irqchip */
+	if (sbi_irqchip_find_device_by_caps(SBI_IRQCHIP_CAPS_MSI, NULL))
+		capability = CAP_ENABLE(capability, WDT_CAP_MSI_MASK);
+
+	sbi_system_watchdog->wdt_attr.wdt_capability = capability;
+
+	sbi_system_watchdog->wdt_attr.wdt_state = SBI_WDT_STOPPED;
+
+	/* Set the min period based on the timer frequency */
+	tdev = sbi_timer_get_device();
+	if (tdev != NULL) {
+		timer_freq = tdev->timer_freq;
+		/* Ceil division to ensure the result is non-zero */
+		sbi_system_watchdog->wdt_attr.wdt_min_period =
+					(u32)((SEC_TO_MICROSEC + timer_freq - 1) / timer_freq);
+	}
+
+	spin_unlock(&sbi_system_watchdog->wdt_lock);
+}
+
+int sbi_watchdog_init(void)
+{
+	/* Allocate memory for global watchdog structure sbi_system_watchdog */
+	sbi_system_watchdog = (struct sbi_watchdog*)sbi_zalloc(sizeof(struct sbi_watchdog));
+
+	if(!sbi_system_watchdog) {
+		return SBI_ENOMEM;
+	}
+
+	SPIN_LOCK_INIT(sbi_system_watchdog->wdt_lock);
+	SBI_INIT_TIMER_EVENT(&sbi_system_watchdog->wdt_timer_ev,
+			     sbi_watchdog_expired_callback,
+			     sbi_watchdog_event_cleanup, NULL);
+	SBI_INIT_TIMER_EVENT(&sbi_system_watchdog->wdt_pretimeout_ev,
+			     sbi_watchdog_pretimeout_callback,
+			     sbi_watchdog_pretimeout_cleanup, NULL);
+
+	/* Initialize the default watchdog attributes */
+	sbi_watchdog_init_attributes();
+
+	return SBI_SUCCESS;
+}
+
+int sbi_watchdog_exit(void)
+{
+	/* Clean up the global watchdog */
+	sbi_free(sbi_system_watchdog);
+
+	return 0;
+}
-- 
2.43.0




More information about the opensbi mailing list