[PATCH v2 5/5] watchdog: qcom: Register pretimeout interrupt as NMI

From: Mayank Rungta

Date: Fri Aug 28 2026 - 21:00:40 EST


When a system is completely unresponsive due to an interrupt storm or
deadlocked CPU cores with standard interrupts disabled, a standard
watchdog pretimeout bark interrupt will fail to execute, preventing the
pretimeout governor from capturing CPU backtraces before the hardware
reset bite.

Attempt to request the Qualcomm watchdog pretimeout bark interrupt as an
NMI (or pseudo-NMI) using request_nmi(). If NMI registration is not
supported on the platform (e.g. pseudo-NMIs are disabled), gracefully
fall back to a standard interrupt via devm_request_irq().

When NMI is used, register a devres cleanup action to free the NMI upon
driver unbind, and use enable_nmi() and disable_nmi() during start and
stop.

Assisted-by: Antigravity:gemini
Signed-off-by: Mayank Rungta <mrungta@xxxxxxxxxx>
---
drivers/watchdog/qcom-wdt.c | 52 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 48 insertions(+), 4 deletions(-)

diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c
index 4eb1bf979012..82eaf8786426 100644
--- a/drivers/watchdog/qcom-wdt.c
+++ b/drivers/watchdog/qcom-wdt.c
@@ -52,6 +52,8 @@ struct qcom_wdt {
unsigned long rate;
void __iomem *base;
const u32 *layout;
+ int irq;
+ bool is_nmi;
};

static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg)
@@ -74,6 +76,28 @@ static irqreturn_t qcom_wdt_isr(int irq, void *arg)
return IRQ_HANDLED;
}

+static void qcom_wdt_enable_irq(struct qcom_wdt *wdt)
+{
+ if (wdt->is_nmi && wdt->irq > 0)
+ enable_nmi(wdt->irq);
+}
+
+static void qcom_wdt_disable_irq(struct qcom_wdt *wdt)
+{
+ if (wdt->is_nmi && wdt->irq > 0)
+ disable_nmi(wdt->irq);
+}
+
+static void qcom_wdt_free_nmi(void *arg)
+{
+ struct qcom_wdt *wdt = arg;
+
+ if (watchdog_active(&wdt->wdd))
+ qcom_wdt_disable_irq(wdt);
+
+ free_nmi(wdt->irq, &wdt->wdd);
+}
+
static int qcom_wdt_start(struct watchdog_device *wdd)
{
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
@@ -84,6 +108,8 @@ static int qcom_wdt_start(struct watchdog_device *wdd)
writel(bark * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME));
writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME));
writel(QCOM_WDT_ENABLE, wdt_addr(wdt, WDT_EN));
+
+ qcom_wdt_enable_irq(wdt);
return 0;
}

@@ -91,6 +117,8 @@ static int qcom_wdt_stop(struct watchdog_device *wdd)
{
struct qcom_wdt *wdt = to_qcom_wdt(wdd);

+ qcom_wdt_disable_irq(wdt);
+
writel(0, wdt_addr(wdt, WDT_EN));
return 0;
}
@@ -256,6 +284,7 @@ static int qcom_wdt_probe(struct platform_device *pdev)
u32 percpu_offset;
int irq, ret;
struct clk *clk;
+ unsigned long irq_flags;

data = of_device_get_match_data(dev);
if (!data) {
@@ -311,10 +340,25 @@ static int qcom_wdt_probe(struct platform_device *pdev)
if (irq < 0 && irq != -ENXIO)
return irq;
if (irq > 0) {
- ret = devm_request_irq(dev, irq, qcom_wdt_isr, 0,
- "wdt_bark", &wdt->wdd);
- if (ret)
- return ret;
+ wdt->irq = irq;
+ irq_flags = IRQF_PERCPU | IRQF_NOBALANCING |
+ IRQF_NO_AUTOEN | IRQF_NO_THREAD;
+
+ ret = request_nmi(irq, qcom_wdt_isr, irq_flags,
+ "wdt_bark", &wdt->wdd);
+ if (ret) {
+ /* Fallback to normal interrupt if NMI not supported */
+ ret = devm_request_irq(dev, irq, qcom_wdt_isr, 0,
+ "wdt_bark", &wdt->wdd);
+ if (ret)
+ return ret;
+ } else {
+ wdt->is_nmi = true;
+ ret = devm_add_action_or_reset(dev, qcom_wdt_free_nmi,
+ wdt);
+ if (ret)
+ return ret;
+ }

wdt->wdd.info = &qcom_wdt_pt_info;
wdt->wdd.pretimeout = 1;

--
2.55.0.897.gb25b4bd76c-goog