watchdog: sunxi_wdt: preserve boot-enabled watchdog

sunxi_wdt_probe() unconditionally stops the watchdog even when firmware
left it running. This opens an unprotected interval during boot and
prevents CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED from taking over the active
watchdog.

Detect an enabled watchdog and decode its programmed interval. Preserve
representable timeouts, and round the 0.5-second interval up to the
minimum representable one-second timeout. Use the configured timeout for
reserved interval encodings. Set the Linux reset mode and ping the
watchdog without clearing its enable bit, then mark it hardware-running
before registration so the watchdog core services it until userspace
takes control. Leave disabled watchdogs untouched.

Fixes: d00680ed00 ("watchdog: sunxi: New watchdog driver for Allwinner A10/A13")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
Link: https://patch.msgid.link/20260827-submit-sunxi-wdt-boot-enabled-v1-v2-1-610d37dccc97@gmail.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
James Hilliard 2026-08-27 22:42:31 -06:00 committed by Guenter Roeck
parent cee9395acd
commit aab55360fa

View File

@ -128,6 +128,38 @@ static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
return 0;
}
static bool sunxi_wdt_is_running(struct watchdog_device *wdt_dev)
{
struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
return readl(sunxi_wdt->wdt_base + regs->wdt_mode) & WDT_MODE_EN;
}
static unsigned int sunxi_wdt_get_timeout(struct watchdog_device *wdt_dev)
{
struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
unsigned int timeout;
u32 interval;
interval = readl(sunxi_wdt->wdt_base + regs->wdt_mode);
interval >>= regs->wdt_timeout_shift;
interval &= WDT_TIMEOUT_MASK;
/* Round the 0.5-second interval up to the minimum representable timeout. */
if (!interval)
return WDT_MIN_TIMEOUT;
for (timeout = WDT_MIN_TIMEOUT;
timeout < ARRAY_SIZE(wdt_timeout_map); timeout++) {
if (wdt_timeout_map[timeout] == interval)
return timeout;
}
/* Reserved interval encoding. */
return 0;
}
static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
unsigned int timeout)
{
@ -259,6 +291,7 @@ static int sunxi_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sunxi_wdt_dev *sunxi_wdt;
unsigned int running_timeout;
int err;
sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
@ -286,7 +319,17 @@ static int sunxi_wdt_probe(struct platform_device *pdev)
watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
if (sunxi_wdt_is_running(&sunxi_wdt->wdt_dev)) {
running_timeout = sunxi_wdt_get_timeout(&sunxi_wdt->wdt_dev);
if (running_timeout)
sunxi_wdt->wdt_dev.timeout = running_timeout;
err = sunxi_wdt_start(&sunxi_wdt->wdt_dev);
if (err)
return err;
set_bit(WDOG_HW_RUNNING, &sunxi_wdt->wdt_dev.status);
}
watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);