diff --git a/sys/kern/kern_wdog.c b/sys/kern/kern_wdog.c --- a/sys/kern/kern_wdog.c +++ b/sys/kern/kern_wdog.c @@ -91,23 +91,41 @@ wdog_reset_all(void *unused) { struct watchdog *wd; + struct watchdog *snap[SMP_MAXCPU]; + int n = 0; + int i; int period, min_period = INT_MAX; + /* + * DF-0236: driver callbacks (wd->wdog_fn) are arbitrary kernel code + * that may sleep or perform slow MMIO. Snapshot the list of + * registered watchdogs under the spinlock, then drop the lock before + * invoking any callback so a misbehaving/sleeping driver cannot + * deadlock the global wdogmtx or stall the rest of the system. + */ spin_lock(&wdogmtx); - if (LIST_EMPTY(&wdoglist)) - goto done; + if (LIST_EMPTY(&wdoglist)) { + spin_unlock(&wdogmtx); + return; + } LIST_FOREACH(wd, &wdoglist, link) { - period = wdog_reset(wd); + if (n < SMP_MAXCPU) + snap[n++] = wd; + } + spin_unlock(&wdogmtx); + + for (i = 0; i < n; i++) { + period = wdog_reset(snap[i]); if (period < min_period) min_period = period; } + + spin_lock(&wdogmtx); if (wdog_auto_enable) { callout_reset(&wdog_callout, min_period * hz / 2, wdog_reset_all, NULL); } wdog_auto_period = min_period; - -done: spin_unlock(&wdogmtx); }