diff --git a/drivers/watchdog/sbsa_gwdt.c b/drivers/watchdog/sbsa_gwdt.c index abfb563c00dde..147654a5d5947 100644 --- a/drivers/watchdog/sbsa_gwdt.c +++ b/drivers/watchdog/sbsa_gwdt.c @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include #include @@ -88,6 +90,13 @@ * indicate whether to adjust wdd->timeout to avoid a race with WS0 * @refresh_base: Virtual address of the watchdog refresh frame * @control_base: Virtual address of the watchdog control frame + * @lock: Serializes the watchdog ops against the system sleep hooks + * @hw_armed: The watchdog is logically running (started by firmware, + * early_enable or userspace); the hardware follows it except + * while a system sleep transition is in progress + * @pm_stopped: A system sleep transition stopped the hardware and owes + * a restart at PM_POST_* + * @pm_nb: PM notifier stopping the watchdog across system sleep */ struct sbsa_gwdt { struct watchdog_device wdd; @@ -96,6 +105,10 @@ struct sbsa_gwdt { bool need_ws0_race_workaround; void __iomem *refresh_base; void __iomem *control_base; + spinlock_t lock; /* hw_armed, pm_stopped */ + bool hw_armed; + bool pm_stopped; + struct notifier_block pm_nb; }; #define DEFAULT_TIMEOUT 10 /* seconds */ @@ -250,12 +263,33 @@ static void sbsa_gwdt_get_version(struct watchdog_device *wdd) !action && (impl == SBSA_GWDT_IMPL_MEDIATEK); } +static void sbsa_gwdt_hw_start(struct sbsa_gwdt *gwdt) +{ + /* writing WCS will cause an explicit watchdog refresh */ + writel(SBSA_GWDT_WCS_EN, gwdt->control_base + SBSA_GWDT_WCS); +} + +static void sbsa_gwdt_hw_stop(struct sbsa_gwdt *gwdt) +{ + /* Simply write 0 to WCS to clean WCS_EN bit */ + writel(0, gwdt->control_base + SBSA_GWDT_WCS); +} + static int sbsa_gwdt_start(struct watchdog_device *wdd) { struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + unsigned long flags; - /* writing WCS will cause an explicit watchdog refresh */ - writel(SBSA_GWDT_WCS_EN, gwdt->control_base + SBSA_GWDT_WCS); + spin_lock_irqsave(&gwdt->lock, flags); + gwdt->hw_armed = true; + /* + * While a system sleep transition is in progress nobody can refresh + * the watchdog: leave the hardware stopped and let the PM_POST_* + * notifier arm it once everything has resumed. + */ + if (!gwdt->pm_stopped) + sbsa_gwdt_hw_start(gwdt); + spin_unlock_irqrestore(&gwdt->lock, flags); return 0; } @@ -263,9 +297,14 @@ static int sbsa_gwdt_start(struct watchdog_device *wdd) static int sbsa_gwdt_stop(struct watchdog_device *wdd) { struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + unsigned long flags; - /* Simply write 0 to WCS to clean WCS_EN bit */ - writel(0, gwdt->control_base + SBSA_GWDT_WCS); + spin_lock_irqsave(&gwdt->lock, flags); + gwdt->hw_armed = false; + /* A stop during the transition also cancels the pending restart. */ + gwdt->pm_stopped = false; + sbsa_gwdt_hw_stop(gwdt); + spin_unlock_irqrestore(&gwdt->lock, flags); return 0; } @@ -294,6 +333,86 @@ static const struct watchdog_ops sbsa_gwdt_ops = { .get_timeleft = sbsa_gwdt_get_timeleft, }; +/* + * Per-device suspend/resume callbacks alone would stop the watchdog only + * once this device itself is suspended, one of the last steps of suspend + * entry, and restart it during device resume, before tasks are thawed. A + * watchdog running from boot (early_enable) would therefore be armed, with + * nobody refreshing it, through task freezing and every other device's + * suspend callback on the way down, and again from device resume until + * userspace runs on the way up; anything stalling past the timeout in + * either window resets the system. + * + * Own the transition from a PM notifier instead: stop at the *_PREPARE + * events, before anything is frozen, and restart at PM_POST_*, after + * everything has resumed. The driver state (hw_armed, pm_stopped) is kept + * under a lock shared with the watchdog ops so that a userspace stop or + * magic close after thaw cannot race the restart, and a start requested + * while the transition is in progress is deferred to PM_POST_*. A + * suspend-only device callback remains as the final guard for a device + * whose probe overlapped the *_PREPARE event; it has no resume + * counterpart, so nothing re-arms the hardware before PM_POST_*. + */ +static void sbsa_gwdt_sleep_stop(struct sbsa_gwdt *gwdt) +{ + unsigned long flags; + + spin_lock_irqsave(&gwdt->lock, flags); + if (gwdt->hw_armed && !gwdt->pm_stopped) { + sbsa_gwdt_hw_stop(gwdt); + gwdt->pm_stopped = true; + } + spin_unlock_irqrestore(&gwdt->lock, flags); +} + +static void sbsa_gwdt_sleep_restart(struct sbsa_gwdt *gwdt) +{ + unsigned long flags; + + spin_lock_irqsave(&gwdt->lock, flags); + if (gwdt->pm_stopped) { + gwdt->pm_stopped = false; + if (gwdt->hw_armed) + sbsa_gwdt_hw_start(gwdt); + } + spin_unlock_irqrestore(&gwdt->lock, flags); +} + +static int sbsa_gwdt_pm_notify(struct notifier_block *nb, unsigned long mode, + void *data) +{ + struct sbsa_gwdt *gwdt = container_of(nb, struct sbsa_gwdt, pm_nb); + + switch (mode) { + case PM_SUSPEND_PREPARE: + case PM_HIBERNATION_PREPARE: + case PM_RESTORE_PREPARE: + sbsa_gwdt_sleep_stop(gwdt); + break; + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + sbsa_gwdt_sleep_restart(gwdt); + break; + } + + return NOTIFY_DONE; +} + +static void sbsa_gwdt_unregister_pm_notifier(void *data) +{ + unregister_pm_notifier(data); +} + +static int sbsa_gwdt_suspend(struct device *dev) +{ + sbsa_gwdt_sleep_stop(dev_get_drvdata(dev)); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sbsa_gwdt_pm_ops, sbsa_gwdt_suspend, NULL); + static int sbsa_gwdt_probe(struct platform_device *pdev) { void __iomem *rf_base, *cf_base; @@ -325,6 +444,21 @@ static int sbsa_gwdt_probe(struct platform_device *pdev) gwdt->clk = arch_timer_get_cntfrq(); gwdt->refresh_base = rf_base; gwdt->control_base = cf_base; + spin_lock_init(&gwdt->lock); + + /* + * Register the sleep hook before anything can arm the watchdog, and + * treat its failure as fatal: without it a running watchdog would + * survive into system sleep with nobody refreshing it. + */ + gwdt->pm_nb.notifier_call = sbsa_gwdt_pm_notify; + ret = register_pm_notifier(&gwdt->pm_nb); + if (!ret) + ret = devm_add_action_or_reset(dev, + sbsa_gwdt_unregister_pm_notifier, + &gwdt->pm_nb); + if (ret) + return dev_err_probe(dev, ret, "Failed to register PM notifier\n"); wdd = &gwdt->wdd; wdd->parent = dev; @@ -354,8 +488,10 @@ static int sbsa_gwdt_probe(struct platform_device *pdev) dev_warn(dev, "System reset by WDT.\n"); wdd->bootstatus |= WDIOF_CARDRESET; } - if (status & SBSA_GWDT_WCS_EN) + if (status & SBSA_GWDT_WCS_EN) { set_bit(WDOG_HW_RUNNING, &wdd->status); + gwdt->hw_armed = true; + } if (action) { irq = platform_get_irq(pdev, 0); @@ -414,32 +550,6 @@ static int sbsa_gwdt_probe(struct platform_device *pdev) return 0; } -/* Disable watchdog if it is active during suspend */ -static int __maybe_unused sbsa_gwdt_suspend(struct device *dev) -{ - struct sbsa_gwdt *gwdt = dev_get_drvdata(dev); - - if (watchdog_hw_running(&gwdt->wdd)) - sbsa_gwdt_stop(&gwdt->wdd); - - return 0; -} - -/* Enable watchdog if necessary */ -static int __maybe_unused sbsa_gwdt_resume(struct device *dev) -{ - struct sbsa_gwdt *gwdt = dev_get_drvdata(dev); - - if (watchdog_hw_running(&gwdt->wdd)) - sbsa_gwdt_start(&gwdt->wdd); - - return 0; -} - -static const struct dev_pm_ops sbsa_gwdt_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(sbsa_gwdt_suspend, sbsa_gwdt_resume) -}; - static const struct of_device_id sbsa_gwdt_of_match[] = { { .compatible = "arm,sbsa-gwdt", }, {}, @@ -455,7 +565,7 @@ MODULE_DEVICE_TABLE(platform, sbsa_gwdt_pdev_match); static struct platform_driver sbsa_gwdt_driver = { .driver = { .name = DRV_NAME, - .pm = &sbsa_gwdt_pm_ops, + .pm = pm_sleep_ptr(&sbsa_gwdt_pm_ops), .of_match_table = sbsa_gwdt_of_match, }, .probe = sbsa_gwdt_probe,