* [PATCH 0/3] watchdog: qcom: Support NMI pretimeout warnings
@ 2026-07-30 21:32 ` Mayank Rungta
0 siblings, 0 replies; 14+ messages in thread
From: Mayank Rungta @ 2026-07-30 21:32 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner
Cc: linux-watchdog, linux-kernel, linux-arm-msm, Kirill A. Shutemov,
Douglas Anderson, Mayank Rungta
On ARM64 Qualcomm SoCs, when a system freezes completely due to hard
locked CPUs with standard interrupts disabled, a standard watchdog
pretimeout warning interrupt (bark) fails to fire. To diagnose total
system lockups, we need to transition the Qualcomm hardware watchdog
pretimeout bark interrupt into an NMI (or pseudo-NMI).
Enabling NMI pretimeout handlers within loadable driver module requires
making watchdog pretimeout NMI-safe and unlocking GPL module access to
NMI registration functions.
This 3-patch series achieves NMI pretimeout enablement for qcom-wdt:
1) Replaces spinlocks in watchdog_notify_pretimeout() with RCU to
guarantee safe execution from NMI context.
2) Exports request_nmi(), free_nmi(), enable_nmi(), and
disable_nmi_nosync() to GPL loadable kernel modules.
3) Updates qcom-wdt to request its pretimeout bark interrupt as a
NMI (or pseudo-NMI).
Testing & Verification:
- Built and tested on ARM64 Qualcomm Snapdragon SoC (Google CoachZ)
loadable module configurations (CONFIG_QCOM_WDT=m).
- With GICv3 pseudo-NMI enabled, simulated hard CPU lockups and
IRQ-disabled hang conditions via lkdtm. Confirmed that qcom-wdt traps
the watchdog pretimeout bark interrupt as a pseudo-NMI, safely
executes watchdog_notify_pretimeout() without deadlocks.
- Verified that runtime transitions between pretimeout governors in
sysfs execute safely.
- Booted with pseudo-NMIs disabled and confirmed that qcom-wdt detects
unsupported NMI and falls back to standard IRQ.
Signed-off-by: Mayank Rungta <mrungta@google.com>
---
Mayank Rungta (3):
watchdog: pretimeout: Protect governor access with RCU for NMI safety
genirq: Export NMI APIs
watchdog: qcom: Register pretimeout interrupt as NMI
drivers/watchdog/qcom-wdt.c | 55 +++++++++++++++++++++++++++++++---
drivers/watchdog/watchdog_pretimeout.c | 47 ++++++++++++++++-------------
include/linux/watchdog.h | 2 +-
kernel/irq/manage.c | 4 +++
4 files changed, 82 insertions(+), 26 deletions(-)
---
base-commit: 3fe08b9796f36ef437ab9328e7dd1e5ff2d66603
change-id: 20260724-qcom-wdt-nmi-series-06a48da7b415
Best regards,
--
Mayank Rungta <mrungta@google.com>
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH 1/3] watchdog: pretimeout: Protect governor access with RCU for NMI safety
2026-07-30 21:32 ` Mayank Rungta
@ 2026-07-30 21:32 ` Mayank Rungta
-1 siblings, 0 replies; 14+ messages in thread
From: Mayank Rungta via B4 Relay @ 2026-07-30 21:32 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner
Cc: linux-watchdog, linux-kernel, linux-arm-msm, Kirill A. Shutemov,
Douglas Anderson, Mayank Rungta
From: Mayank Rungta <mrungta@google.com>
Currently, watchdog_notify_pretimeout() acquires pretimeout_lock using
spin_lock_irqsave() to safely dereference wdd->gov before invoking the
pretimeout callback.
On architectures supporting pseudo-NMIs (ARM64 GICv3), watchdog drivers
may register their pretimeout warning interrupt (bark) as an NMI.
Because spin_lock_irqsave() disables regular interrupts but leaves NMIs
unmasked, if a pretimeout NMI fires while pretimeout_lock is already
held by normal process context, attempting to re-acquire pretimeout_lock
triggers an unrecoverable deadlock.
To make pretimeout notifications completely safe to execute from NMI,
convert read access to wdd->gov inside watchdog_notify_pretimeout() from
a spinlock to lockless RCU. Use rcu_assign_pointer() when modifying
wdd->gov and invoke synchronize_rcu() during governor unregister and
pretimeout unregister routines.
Signed-off-by: Mayank Rungta <mrungta@google.com>
---
drivers/watchdog/watchdog_pretimeout.c | 47 +++++++++++++++++++---------------
include/linux/watchdog.h | 2 +-
2 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c
index 02e09b9e396d..3793d4cb60f8 100644
--- a/drivers/watchdog/watchdog_pretimeout.c
+++ b/drivers/watchdog/watchdog_pretimeout.c
@@ -4,6 +4,7 @@
*/
#include <linux/list.h>
+#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
@@ -17,7 +18,7 @@
/* Default watchdog pretimeout governor */
static struct watchdog_governor *default_gov;
-/* The spinlock protects default_gov, wdd->gov and pretimeout_list */
+/* The spinlock protects default_gov and pretimeout_list */
static DEFINE_SPINLOCK(pretimeout_lock);
/* List of watchdog devices, which can generate a pretimeout event */
@@ -67,12 +68,14 @@ int watchdog_pretimeout_available_governors_get(char *buf)
int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf)
{
+ struct watchdog_governor *gov;
int count = 0;
- spin_lock_irq(&pretimeout_lock);
- if (wdd->gov)
- count = sysfs_emit(buf, "%s\n", wdd->gov->name);
- spin_unlock_irq(&pretimeout_lock);
+ rcu_read_lock();
+ gov = rcu_dereference(wdd->gov);
+ if (gov)
+ count = sysfs_emit(buf, "%s\n", gov->name);
+ rcu_read_unlock();
return count;
}
@@ -91,7 +94,7 @@ int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
}
spin_lock_irq(&pretimeout_lock);
- wdd->gov = priv->gov;
+ rcu_assign_pointer(wdd->gov, priv->gov);
spin_unlock_irq(&pretimeout_lock);
mutex_unlock(&governor_lock);
@@ -101,16 +104,13 @@ int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
void watchdog_notify_pretimeout(struct watchdog_device *wdd)
{
- unsigned long flags;
+ struct watchdog_governor *gov;
- spin_lock_irqsave(&pretimeout_lock, flags);
- if (!wdd->gov) {
- spin_unlock_irqrestore(&pretimeout_lock, flags);
- return;
- }
-
- wdd->gov->pretimeout(wdd);
- spin_unlock_irqrestore(&pretimeout_lock, flags);
+ rcu_read_lock();
+ gov = rcu_dereference(wdd->gov);
+ if (gov)
+ gov->pretimeout(wdd);
+ rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout);
@@ -140,8 +140,8 @@ int watchdog_register_governor(struct watchdog_governor *gov)
default_gov = gov;
list_for_each_entry(p, &pretimeout_list, entry)
- if (!p->wdd->gov)
- p->wdd->gov = default_gov;
+ if (!rcu_access_pointer(p->wdd->gov))
+ rcu_assign_pointer(p->wdd->gov, default_gov);
spin_unlock_irq(&pretimeout_lock);
}
@@ -170,11 +170,14 @@ void watchdog_unregister_governor(struct watchdog_governor *gov)
if (default_gov == gov)
default_gov = NULL;
list_for_each_entry(p, &pretimeout_list, entry)
- if (p->wdd->gov == gov)
- p->wdd->gov = default_gov;
+ if (rcu_dereference_protected(p->wdd->gov,
+ lockdep_is_held(&pretimeout_lock)) == gov)
+ rcu_assign_pointer(p->wdd->gov, default_gov);
spin_unlock_irq(&pretimeout_lock);
mutex_unlock(&governor_lock);
+
+ synchronize_rcu();
}
EXPORT_SYMBOL(watchdog_unregister_governor);
@@ -192,7 +195,7 @@ int watchdog_register_pretimeout(struct watchdog_device *wdd)
spin_lock_irq(&pretimeout_lock);
list_add(&p->entry, &pretimeout_list);
p->wdd = wdd;
- wdd->gov = default_gov;
+ rcu_assign_pointer(wdd->gov, default_gov);
spin_unlock_irq(&pretimeout_lock);
return 0;
@@ -206,7 +209,7 @@ void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
return;
spin_lock_irq(&pretimeout_lock);
- wdd->gov = NULL;
+ rcu_assign_pointer(wdd->gov, NULL);
list_for_each_entry_safe(p, t, &pretimeout_list, entry) {
if (p->wdd == wdd) {
@@ -216,4 +219,6 @@ void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
}
}
spin_unlock_irq(&pretimeout_lock);
+
+ synchronize_rcu();
}
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
index 29cd03686154..5a3c35968cc8 100644
--- a/include/linux/watchdog.h
+++ b/include/linux/watchdog.h
@@ -105,7 +105,7 @@ struct watchdog_device {
const struct attribute_group **groups;
const struct watchdog_info *info;
const struct watchdog_ops *ops;
- const struct watchdog_governor *gov;
+ const struct watchdog_governor __rcu *gov;
unsigned int bootstatus;
unsigned int timeout;
unsigned int pretimeout;
--
2.55.0.508.g3f0d502094-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH 1/3] watchdog: pretimeout: Protect governor access with RCU for NMI safety
@ 2026-07-30 21:32 ` Mayank Rungta
0 siblings, 0 replies; 14+ messages in thread
From: Mayank Rungta @ 2026-07-30 21:32 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner
Cc: linux-watchdog, linux-kernel, linux-arm-msm, Kirill A. Shutemov,
Douglas Anderson, Mayank Rungta
Currently, watchdog_notify_pretimeout() acquires pretimeout_lock using
spin_lock_irqsave() to safely dereference wdd->gov before invoking the
pretimeout callback.
On architectures supporting pseudo-NMIs (ARM64 GICv3), watchdog drivers
may register their pretimeout warning interrupt (bark) as an NMI.
Because spin_lock_irqsave() disables regular interrupts but leaves NMIs
unmasked, if a pretimeout NMI fires while pretimeout_lock is already
held by normal process context, attempting to re-acquire pretimeout_lock
triggers an unrecoverable deadlock.
To make pretimeout notifications completely safe to execute from NMI,
convert read access to wdd->gov inside watchdog_notify_pretimeout() from
a spinlock to lockless RCU. Use rcu_assign_pointer() when modifying
wdd->gov and invoke synchronize_rcu() during governor unregister and
pretimeout unregister routines.
Signed-off-by: Mayank Rungta <mrungta@google.com>
---
drivers/watchdog/watchdog_pretimeout.c | 47 +++++++++++++++++++---------------
include/linux/watchdog.h | 2 +-
2 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c
index 02e09b9e396d..3793d4cb60f8 100644
--- a/drivers/watchdog/watchdog_pretimeout.c
+++ b/drivers/watchdog/watchdog_pretimeout.c
@@ -4,6 +4,7 @@
*/
#include <linux/list.h>
+#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
@@ -17,7 +18,7 @@
/* Default watchdog pretimeout governor */
static struct watchdog_governor *default_gov;
-/* The spinlock protects default_gov, wdd->gov and pretimeout_list */
+/* The spinlock protects default_gov and pretimeout_list */
static DEFINE_SPINLOCK(pretimeout_lock);
/* List of watchdog devices, which can generate a pretimeout event */
@@ -67,12 +68,14 @@ int watchdog_pretimeout_available_governors_get(char *buf)
int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf)
{
+ struct watchdog_governor *gov;
int count = 0;
- spin_lock_irq(&pretimeout_lock);
- if (wdd->gov)
- count = sysfs_emit(buf, "%s\n", wdd->gov->name);
- spin_unlock_irq(&pretimeout_lock);
+ rcu_read_lock();
+ gov = rcu_dereference(wdd->gov);
+ if (gov)
+ count = sysfs_emit(buf, "%s\n", gov->name);
+ rcu_read_unlock();
return count;
}
@@ -91,7 +94,7 @@ int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
}
spin_lock_irq(&pretimeout_lock);
- wdd->gov = priv->gov;
+ rcu_assign_pointer(wdd->gov, priv->gov);
spin_unlock_irq(&pretimeout_lock);
mutex_unlock(&governor_lock);
@@ -101,16 +104,13 @@ int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
void watchdog_notify_pretimeout(struct watchdog_device *wdd)
{
- unsigned long flags;
+ struct watchdog_governor *gov;
- spin_lock_irqsave(&pretimeout_lock, flags);
- if (!wdd->gov) {
- spin_unlock_irqrestore(&pretimeout_lock, flags);
- return;
- }
-
- wdd->gov->pretimeout(wdd);
- spin_unlock_irqrestore(&pretimeout_lock, flags);
+ rcu_read_lock();
+ gov = rcu_dereference(wdd->gov);
+ if (gov)
+ gov->pretimeout(wdd);
+ rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout);
@@ -140,8 +140,8 @@ int watchdog_register_governor(struct watchdog_governor *gov)
default_gov = gov;
list_for_each_entry(p, &pretimeout_list, entry)
- if (!p->wdd->gov)
- p->wdd->gov = default_gov;
+ if (!rcu_access_pointer(p->wdd->gov))
+ rcu_assign_pointer(p->wdd->gov, default_gov);
spin_unlock_irq(&pretimeout_lock);
}
@@ -170,11 +170,14 @@ void watchdog_unregister_governor(struct watchdog_governor *gov)
if (default_gov == gov)
default_gov = NULL;
list_for_each_entry(p, &pretimeout_list, entry)
- if (p->wdd->gov == gov)
- p->wdd->gov = default_gov;
+ if (rcu_dereference_protected(p->wdd->gov,
+ lockdep_is_held(&pretimeout_lock)) == gov)
+ rcu_assign_pointer(p->wdd->gov, default_gov);
spin_unlock_irq(&pretimeout_lock);
mutex_unlock(&governor_lock);
+
+ synchronize_rcu();
}
EXPORT_SYMBOL(watchdog_unregister_governor);
@@ -192,7 +195,7 @@ int watchdog_register_pretimeout(struct watchdog_device *wdd)
spin_lock_irq(&pretimeout_lock);
list_add(&p->entry, &pretimeout_list);
p->wdd = wdd;
- wdd->gov = default_gov;
+ rcu_assign_pointer(wdd->gov, default_gov);
spin_unlock_irq(&pretimeout_lock);
return 0;
@@ -206,7 +209,7 @@ void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
return;
spin_lock_irq(&pretimeout_lock);
- wdd->gov = NULL;
+ rcu_assign_pointer(wdd->gov, NULL);
list_for_each_entry_safe(p, t, &pretimeout_list, entry) {
if (p->wdd == wdd) {
@@ -216,4 +219,6 @@ void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
}
}
spin_unlock_irq(&pretimeout_lock);
+
+ synchronize_rcu();
}
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
index 29cd03686154..5a3c35968cc8 100644
--- a/include/linux/watchdog.h
+++ b/include/linux/watchdog.h
@@ -105,7 +105,7 @@ struct watchdog_device {
const struct attribute_group **groups;
const struct watchdog_info *info;
const struct watchdog_ops *ops;
- const struct watchdog_governor *gov;
+ const struct watchdog_governor __rcu *gov;
unsigned int bootstatus;
unsigned int timeout;
unsigned int pretimeout;
--
2.55.0.508.g3f0d502094-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH 1/3] watchdog: pretimeout: Protect governor access with RCU for NMI safety
2026-07-30 21:32 ` Mayank Rungta
(?)
@ 2026-07-30 21:46 ` Doug Anderson
-1 siblings, 0 replies; 14+ messages in thread
From: Doug Anderson @ 2026-07-30 21:46 UTC (permalink / raw)
To: mrungta
Cc: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner, linux-watchdog,
linux-kernel, linux-arm-msm, Kirill A. Shutemov
Hi,
On Thu, Jul 30, 2026 at 2:33 PM Mayank Rungta via B4 Relay
<devnull+mrungta.google.com@kernel.org> wrote:
>
> From: Mayank Rungta <mrungta@google.com>
>
> Currently, watchdog_notify_pretimeout() acquires pretimeout_lock using
> spin_lock_irqsave() to safely dereference wdd->gov before invoking the
> pretimeout callback.
>
> On architectures supporting pseudo-NMIs (ARM64 GICv3), watchdog drivers
> may register their pretimeout warning interrupt (bark) as an NMI.
> Because spin_lock_irqsave() disables regular interrupts but leaves NMIs
> unmasked, if a pretimeout NMI fires while pretimeout_lock is already
> held by normal process context, attempting to re-acquire pretimeout_lock
> triggers an unrecoverable deadlock.
>
> To make pretimeout notifications completely safe to execute from NMI,
> convert read access to wdd->gov inside watchdog_notify_pretimeout() from
> a spinlock to lockless RCU. Use rcu_assign_pointer() when modifying
> wdd->gov and invoke synchronize_rcu() during governor unregister and
> pretimeout unregister routines.
>
> Signed-off-by: Mayank Rungta <mrungta@google.com>
> ---
> drivers/watchdog/watchdog_pretimeout.c | 47 +++++++++++++++++++---------------
> include/linux/watchdog.h | 2 +-
> 2 files changed, 27 insertions(+), 22 deletions(-)
I pre-reviewed this for Mayank. I'm not an RCU expert, but this looks
right based on my understanding. My AI was also happy with it. ;-)
Reviewed-by: Douglas Anderson <dianders@chromium.org>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 2/3] genirq: Export NMI APIs
2026-07-30 21:32 ` Mayank Rungta
@ 2026-07-30 21:32 ` Mayank Rungta
-1 siblings, 0 replies; 14+ messages in thread
From: Mayank Rungta via B4 Relay @ 2026-07-30 21:32 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner
Cc: linux-watchdog, linux-kernel, linux-arm-msm, Kirill A. Shutemov,
Douglas Anderson, Mayank Rungta
From: Mayank Rungta <mrungta@google.com>
Currently, request_nmi(), free_nmi(), enable_nmi() and disable_nmi_nosync()
are restricted to built-in kernel code because they are not exported to
loadable modules.
Export these APIs to allow loadable modules to register and manage NMIs.
This allows watchdog drivers configured as loadable modules to register
their bark interrupt as an NMI.
Signed-off-by: Mayank Rungta <mrungta@google.com>
---
kernel/irq/manage.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 2fbff2618a1e..fb0b8da32f4c 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -766,6 +766,7 @@ void disable_nmi_nosync(unsigned int irq)
{
disable_irq_nosync(irq);
}
+EXPORT_SYMBOL_GPL(disable_nmi_nosync);
void __enable_irq(struct irq_desc *desc)
{
@@ -833,6 +834,7 @@ void enable_nmi(unsigned int irq)
{
enable_irq(irq);
}
+EXPORT_SYMBOL_GPL(enable_nmi);
static int set_irq_wake_real(unsigned int irq, unsigned int on)
{
@@ -2080,6 +2082,7 @@ const void *free_nmi(unsigned int irq, void *dev_id)
return __cleanup_nmi(irq, desc);
}
+EXPORT_SYMBOL_GPL(free_nmi);
/**
* request_threaded_irq - allocate an interrupt line
@@ -2342,6 +2345,7 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
return retval;
}
+EXPORT_SYMBOL_GPL(request_nmi);
void enable_percpu_irq(unsigned int irq, unsigned int type)
{
--
2.55.0.508.g3f0d502094-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH 2/3] genirq: Export NMI APIs
@ 2026-07-30 21:32 ` Mayank Rungta
0 siblings, 0 replies; 14+ messages in thread
From: Mayank Rungta @ 2026-07-30 21:32 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner
Cc: linux-watchdog, linux-kernel, linux-arm-msm, Kirill A. Shutemov,
Douglas Anderson, Mayank Rungta
Currently, request_nmi(), free_nmi(), enable_nmi() and disable_nmi_nosync()
are restricted to built-in kernel code because they are not exported to
loadable modules.
Export these APIs to allow loadable modules to register and manage NMIs.
This allows watchdog drivers configured as loadable modules to register
their bark interrupt as an NMI.
Signed-off-by: Mayank Rungta <mrungta@google.com>
---
kernel/irq/manage.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 2fbff2618a1e..fb0b8da32f4c 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -766,6 +766,7 @@ void disable_nmi_nosync(unsigned int irq)
{
disable_irq_nosync(irq);
}
+EXPORT_SYMBOL_GPL(disable_nmi_nosync);
void __enable_irq(struct irq_desc *desc)
{
@@ -833,6 +834,7 @@ void enable_nmi(unsigned int irq)
{
enable_irq(irq);
}
+EXPORT_SYMBOL_GPL(enable_nmi);
static int set_irq_wake_real(unsigned int irq, unsigned int on)
{
@@ -2080,6 +2082,7 @@ const void *free_nmi(unsigned int irq, void *dev_id)
return __cleanup_nmi(irq, desc);
}
+EXPORT_SYMBOL_GPL(free_nmi);
/**
* request_threaded_irq - allocate an interrupt line
@@ -2342,6 +2345,7 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
return retval;
}
+EXPORT_SYMBOL_GPL(request_nmi);
void enable_percpu_irq(unsigned int irq, unsigned int type)
{
--
2.55.0.508.g3f0d502094-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH 2/3] genirq: Export NMI APIs
2026-07-30 21:32 ` Mayank Rungta
(?)
@ 2026-07-30 21:47 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-30 21:47 UTC (permalink / raw)
To: Mayank Rungta; +Cc: linux-watchdog
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Exporting NMI APIs to loadable modules exposes an unmitigated Use-After-Free and kernel panic during module unload due to the lack of NMI synchronization.
- [High] Lack of request_mutex locking in free_nmi() and the request_nmi() error path leads to data races, resource double-frees, and refcount underflows.
--
--- Patch [2]: [PATCH 2/3] genirq: Export NMI APIs ---
commit dfe5c7d2a97396d411087d0eb1dca59bbf190e69
Author: Mayank Rungta <mrungta@google.com>
genirq: Export NMI APIs
Currently, request_nmi(), free_nmi(), enable_nmi() and disable_nmi_nosync()
are restricted to built-in kernel code because they are not exported to
loadable modules. Export these APIs to allow loadable modules to register
and manage NMIs.
> diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
> index 2fbff2618a1e2..fb0b8da32f4c1 100644
> --- a/kernel/irq/manage.c
> +++ b/kernel/irq/manage.c
[ ... ]
> @@ -2080,6 +2082,7 @@ const void *free_nmi(unsigned int irq, void *dev_id)
>
> return __cleanup_nmi(irq, desc);
> }
> +EXPORT_SYMBOL_GPL(free_nmi);
[Severity: Critical]
This is a pre-existing issue, but does exporting this API to loadable modules
expose an unmitigated use-after-free and kernel panic during module unload?
When a module unloads and calls free_nmi(), __cleanup_nmi() uninstalls the
handler and calls kfree(action) without waiting for executing NMI handlers to
finish, such as via synchronize_rcu():
kernel/irq/manage.c:__cleanup_nmi() {
...
unregister_handler_proc(irq, action);
kfree(action);
...
}
Concurrent NMIs on other CPUs bypass normal IRQ locks and can continue to
execute the handler locklessly:
kernel/irq/chip.c:handle_fasteoi_nmi() {
...
struct irqaction *action = desc->action;
...
res = action->handler(irq, action->dev_id);
...
}
Since the module text section is unmapped upon unload, couldn't an actively
executing concurrent NMI jump into unmapped memory, resulting in a fatal
panic?
[ ... ]
> @@ -2342,6 +2345,7 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
>
> return retval;
> }
> +EXPORT_SYMBOL_GPL(request_nmi);
[Severity: High]
This is a pre-existing issue, but do request_nmi() and free_nmi() lack
request_mutex locking around their cleanup paths?
According to IRQ subsystem locking rules, desc->request_mutex serializes
resource allocation and teardown. However, both free_nmi() and the failure
path of request_nmi() invoke __cleanup_nmi() without holding this mutex.
In request_nmi():
kernel/irq/manage.c:request_nmi() {
...
if (retval) {
__cleanup_nmi(irq, desc);
return -EINVAL;
}
...
}
Inside __cleanup_nmi(), resource teardowns are executed unconditionally
outside of desc->lock:
kernel/irq/manage.c:__cleanup_nmi() {
...
irq_release_resources(desc);
irq_chip_pm_put(&desc->irq_data);
module_put(desc->owner);
...
}
If request_nmi() fails concurrently with another request_nmi() or free_nmi(),
won't this lead to resource double-frees and unbalanced reference counts?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730-qcom-wdt-nmi-series-v1-0-3aa86d162914@google.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH 2/3] genirq: Export NMI APIs
2026-07-30 21:32 ` Mayank Rungta
(?)
(?)
@ 2026-07-30 21:49 ` Doug Anderson
2026-07-30 22:55 ` Guenter Roeck
-1 siblings, 1 reply; 14+ messages in thread
From: Doug Anderson @ 2026-07-30 21:49 UTC (permalink / raw)
To: mrungta
Cc: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner, linux-watchdog,
linux-kernel, linux-arm-msm, Kirill A. Shutemov
Hi,
On Thu, Jul 30, 2026 at 2:33 PM Mayank Rungta via B4 Relay
<devnull+mrungta.google.com@kernel.org> wrote:
>
> From: Mayank Rungta <mrungta@google.com>
>
> Currently, request_nmi(), free_nmi(), enable_nmi() and disable_nmi_nosync()
> are restricted to built-in kernel code because they are not exported to
> loadable modules.
>
> Export these APIs to allow loadable modules to register and manage NMIs.
> This allows watchdog drivers configured as loadable modules to register
> their bark interrupt as an NMI.
>
> Signed-off-by: Mayank Rungta <mrungta@google.com>
> ---
> kernel/irq/manage.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
> index 2fbff2618a1e..fb0b8da32f4c 100644
> --- a/kernel/irq/manage.c
> +++ b/kernel/irq/manage.c
> @@ -766,6 +766,7 @@ void disable_nmi_nosync(unsigned int irq)
> {
> disable_irq_nosync(irq);
> }
> +EXPORT_SYMBOL_GPL(disable_nmi_nosync);
>
> void __enable_irq(struct irq_desc *desc)
> {
> @@ -833,6 +834,7 @@ void enable_nmi(unsigned int irq)
> {
> enable_irq(irq);
> }
> +EXPORT_SYMBOL_GPL(enable_nmi);
>
> static int set_irq_wake_real(unsigned int irq, unsigned int on)
> {
> @@ -2080,6 +2082,7 @@ const void *free_nmi(unsigned int irq, void *dev_id)
>
> return __cleanup_nmi(irq, desc);
> }
> +EXPORT_SYMBOL_GPL(free_nmi);
>
> /**
> * request_threaded_irq - allocate an interrupt line
> @@ -2342,6 +2345,7 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
>
> return retval;
> }
> +EXPORT_SYMBOL_GPL(request_nmi);
This seems reasonable to me. One thought I had was that we could
possibly get by with fewer exported symbols by changing
disable_nmi_nosync() and enable_nmi() to "static inline" functions in
the header file. That being said, what Mayank has here feels slightly
better to me.
Reviewed-by: Douglas Anderson <dianders@chromium.org>
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH 2/3] genirq: Export NMI APIs
2026-07-30 21:49 ` Doug Anderson
@ 2026-07-30 22:55 ` Guenter Roeck
0 siblings, 0 replies; 14+ messages in thread
From: Guenter Roeck @ 2026-07-30 22:55 UTC (permalink / raw)
To: Doug Anderson, mrungta
Cc: Wim Van Sebroeck, Thomas Gleixner, linux-watchdog, linux-kernel,
linux-arm-msm, Kirill A. Shutemov
On 7/30/26 14:49, Doug Anderson wrote:
> Hi,
>
> On Thu, Jul 30, 2026 at 2:33 PM Mayank Rungta via B4 Relay
> <devnull+mrungta.google.com@kernel.org> wrote:
>>
>> From: Mayank Rungta <mrungta@google.com>
>>
>> Currently, request_nmi(), free_nmi(), enable_nmi() and disable_nmi_nosync()
>> are restricted to built-in kernel code because they are not exported to
>> loadable modules.
>>
>> Export these APIs to allow loadable modules to register and manage NMIs.
>> This allows watchdog drivers configured as loadable modules to register
>> their bark interrupt as an NMI.
>>
>> Signed-off-by: Mayank Rungta <mrungta@google.com>
>> ---
>> kernel/irq/manage.c | 4 ++++
>> 1 file changed, 4 insertions(+)
>>
>> diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
>> index 2fbff2618a1e..fb0b8da32f4c 100644
>> --- a/kernel/irq/manage.c
>> +++ b/kernel/irq/manage.c
>> @@ -766,6 +766,7 @@ void disable_nmi_nosync(unsigned int irq)
>> {
>> disable_irq_nosync(irq);
>> }
>> +EXPORT_SYMBOL_GPL(disable_nmi_nosync);
>>
>> void __enable_irq(struct irq_desc *desc)
>> {
>> @@ -833,6 +834,7 @@ void enable_nmi(unsigned int irq)
>> {
>> enable_irq(irq);
>> }
>> +EXPORT_SYMBOL_GPL(enable_nmi);
>>
>> static int set_irq_wake_real(unsigned int irq, unsigned int on)
>> {
>> @@ -2080,6 +2082,7 @@ const void *free_nmi(unsigned int irq, void *dev_id)
>>
>> return __cleanup_nmi(irq, desc);
>> }
>> +EXPORT_SYMBOL_GPL(free_nmi);
>>
>> /**
>> * request_threaded_irq - allocate an interrupt line
>> @@ -2342,6 +2345,7 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
>>
>> return retval;
>> }
>> +EXPORT_SYMBOL_GPL(request_nmi);
>
> This seems reasonable to me. One thought I had was that we could
> possibly get by with fewer exported symbols by changing
> disable_nmi_nosync() and enable_nmi() to "static inline" functions in
> the header file. That being said, what Mayank has here feels slightly
> better to me.
>
I don't claim to understand the NMI APIs used, but Sashiko's feedback
seems real to me. Someone who knows that code will need to confirm that
there are no unexpected pitfalls.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 3/3] watchdog: qcom: Register pretimeout interrupt as NMI
2026-07-30 21:32 ` Mayank Rungta
@ 2026-07-30 21:32 ` Mayank Rungta
-1 siblings, 0 replies; 14+ messages in thread
From: Mayank Rungta via B4 Relay @ 2026-07-30 21:32 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner
Cc: linux-watchdog, linux-kernel, linux-arm-msm, Kirill A. Shutemov,
Douglas Anderson, Mayank Rungta
From: Mayank Rungta <mrungta@google.com>
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 <mrungta@google.com>
---
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
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH 3/3] watchdog: qcom: Register pretimeout interrupt as NMI
@ 2026-07-30 21:32 ` Mayank Rungta
0 siblings, 0 replies; 14+ messages in thread
From: Mayank Rungta @ 2026-07-30 21:32 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner
Cc: linux-watchdog, linux-kernel, linux-arm-msm, Kirill A. Shutemov,
Douglas Anderson, Mayank Rungta
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 <mrungta@google.com>
---
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
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH 3/3] watchdog: qcom: Register pretimeout interrupt as NMI
2026-07-30 21:32 ` Mayank Rungta
(?)
@ 2026-07-30 21:44 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-30 21:44 UTC (permalink / raw)
To: Mayank Rungta; +Cc: linux-watchdog
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Potential Use-After-Free and NULL pointer dereference in NMI teardown during driver unbind or probe failure.
--
--- Patch [3]: [PATCH 3/3] watchdog: qcom: Register pretimeout interrupt as NMI ---
commit 630edce3072ee30140f775e46b40445975b0d011
Author: Mayank Rungta <mrungta@google.com>
watchdog: qcom: Register pretimeout interrupt as NMI
Attempt to register the pretimeout interrupt as an NMI, falling back to a
standard IRQ if NMI registration fails. Explicitly arm and disarm the NMI
during start and stop operations, and register a devres action to free the
NMI on teardown.
> diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c
> index 49bd04841f0c8..19d8e91cff408 100644
> --- a/drivers/watchdog/qcom-wdt.c
> +++ b/drivers/watchdog/qcom-wdt.c
[ ... ]
> +static void qcom_wdt_free_nmi(void *arg)
> +{
> + struct qcom_wdt *wdt = arg;
> +
> + qcom_wdt_disable_irq(wdt);
> + free_nmi(wdt->irq, &wdt->wdd);
[Severity: High]
Is there a regression here that could cause a use-after-free during driver
unbind or probe failure?
Unlike free_irq(), free_nmi() does not synchronize with in-flight NMI handlers
on other CPUs. It immediately clears desc->action and calls kfree() on the
struct irqaction.
If the pretimeout NMI triggers concurrently just before the interrupt is
masked, could the architecture NMI handler be executing and dereference the
freed irqaction?
kernel/irq/chip.c:handle_fasteoi_nmi() {
...
res = action->handler(irq, action->dev_id);
...
}
Additionally, since subsequent devres unwinding frees the struct qcom_wdt,
could qcom_wdt_isr() also dereference the freed wdd pointer when calling
watchdog_notify_pretimeout(wdd)?
Could this be triggered by root via sysfs unbind, or naturally during a
hardware-induced pretimeout racing with a module unload, leading to a crash?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730-qcom-wdt-nmi-series-v1-0-3aa86d162914@google.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH 3/3] watchdog: qcom: Register pretimeout interrupt as NMI
2026-07-30 21:32 ` Mayank Rungta
(?)
(?)
@ 2026-07-30 21:52 ` Doug Anderson
-1 siblings, 0 replies; 14+ messages in thread
From: Doug Anderson @ 2026-07-30 21:52 UTC (permalink / raw)
To: mrungta
Cc: Wim Van Sebroeck, Guenter Roeck, Thomas Gleixner, linux-watchdog,
linux-kernel, linux-arm-msm, Kirill A. Shutemov
Hi,
On Thu, Jul 30, 2026 at 2:33 PM Mayank Rungta via B4 Relay
<devnull+mrungta.google.com@kernel.org> wrote:
>
> From: Mayank Rungta <mrungta@google.com>
>
> 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 <mrungta@google.com>
> ---
> drivers/watchdog/qcom-wdt.c | 55 +++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 51 insertions(+), 4 deletions(-)
I pre-reviewed this for Mayank and it looks reasonable to me.
Reviewed-by: Douglas Anderson <dianders@chromium.org>
^ permalink raw reply [flat|nested] 14+ messages in thread