[PATCH 3/3] watchdog: qcom: Register pretimeout interrupt as NMI
Mayank Rungta via B4 Relay <[email protected]> Thu, 30 Jul 2026 14:32:49 -0700
| Newsgroups | org.kernel.vger.linux-watchdog,org.kernel.feeds.b4-sent,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Mayank Rungta <[email protected]> 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 register the pretimeout interrupt as an NMI (or pseudo-NMI) using request_nmi(). If NMI registration fails due to lack of platform GIC pseudo-NMI support, fall back to devm_request_irq(). Explicitly arm and disarm the NMI during watchdog start and stop operations using enable_nmi() and disable_nmi_nosync(). NMI Execution Call Trace (lkdtm HARDLOCKUP freeze break): Kernel panic - not syncing: watchdog pretimeout event CPU: 0 UID: 0 PID: 5419 Comm: bash Not tainted 6.19.0-rc5-00022 Hardware name: Google CoachZ (rev3+) (DT) Call trace: show_stack+0x24/0x34 (C) dump_stack_lvl+0x84/0xac dump_stack+0x1c/0x34 vpanic+0xfc/0x2d0 panic+0x6c/0x74 watchdog_notify_pretimeout+0x34/0x48 qcom_wdt_isr+0x20/0x34 handle_fasteoi_nmi+0x50/0x88 handle_irq_desc+0x4c/0x68 generic_handle_domain_nmi+0x38/0x48 __gic_handle_nmi.isra.0+0x40/0x90 gic_handle_irq+0x64/0x234 call_on_irq_stack+0x48/0x88 do_interrupt_handler+0x74/0xc4 el1_interrupt+0x5c/0xb0 lkdtm_HARDLOCKUP+0x18/0x28 (P) Signed-off-by: Mayank Rungta <[email protected]> --- drivers/watchdog/qcom-wdt.c | 55 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index 49bd04841f0c..19d8e91cff40 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -51,6 +51,9 @@ struct qcom_wdt { unsigned long rate; void __iomem *base; const u32 *layout; + int irq; + bool is_nmi; + bool irq_enabled; }; static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg) @@ -73,6 +76,30 @@ 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 && !wdt->irq_enabled) { + enable_nmi(wdt->irq); + wdt->irq_enabled = true; + } +} + +static void qcom_wdt_disable_irq(struct qcom_wdt *wdt) +{ + if (wdt->is_nmi && wdt->irq > 0 && wdt->irq_enabled) { + disable_nmi_nosync(wdt->irq); + wdt->irq_enabled = false; + } +} + +static void qcom_wdt_free_nmi(void *arg) +{ + struct qcom_wdt *wdt = arg; + + 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); @@ -83,6 +110,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; } @@ -90,6 +119,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; } @@ -240,6 +271,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) { @@ -290,10 +322,25 @@ static int qcom_wdt_probe(struct platform_device *pdev) /* check if there is pretimeout support */ irq = platform_get_irq_optional(pdev, 0); if (data->pretimeout && 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.508.g3f0d502094-goog