watchdog: introduce watchdog_dev_suspend/resume
authorGrzegorz Jaszczyk <grzegorz.jaszczyk@linaro.org>
Fri, 18 Jun 2021 19:50:32 +0000 (21:50 +0200)
committerWim Van Sebroeck <wim@linux-watchdog.org>
Sun, 22 Aug 2021 08:28:08 +0000 (10:28 +0200)
The watchdog drivers often disable wdog clock during suspend and then
enable it again during resume. Nevertheless the ping worker is still
running and can issue low-level ping while the wdog clock is disabled
causing the system hang. To prevent such condition register pm notifier
in the watchdog core which will call watchdog_dev_suspend/resume and
actually cancel ping worker during suspend and restore it back, if
needed, during resume.

Signed-off-by: Grzegorz Jaszczyk <grzegorz.jaszczyk@linaro.org>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20210618195033.3209598-2-grzegorz.jaszczyk@linaro.org
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>
drivers/watchdog/watchdog_core.c
drivers/watchdog/watchdog_dev.c
include/linux/watchdog.h

index 5df0a22e2cb438412be071331cbed9d203ebffca..3fe8a7edc252f3f93ff4bddb5100beed8e28f62d 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/idr.h>         /* For ida_* macros */
 #include <linux/err.h>         /* For IS_ERR macros */
 #include <linux/of.h>          /* For of_get_timeout_sec */
+#include <linux/suspend.h>
 
 #include "watchdog_core.h"     /* For watchdog_dev_register/... */
 
@@ -185,6 +186,33 @@ static int watchdog_restart_notifier(struct notifier_block *nb,
        return NOTIFY_DONE;
 }
 
+static int watchdog_pm_notifier(struct notifier_block *nb, unsigned long mode,
+                               void *data)
+{
+       struct watchdog_device *wdd;
+       int ret = 0;
+
+       wdd = container_of(nb, struct watchdog_device, pm_nb);
+
+       switch (mode) {
+       case PM_HIBERNATION_PREPARE:
+       case PM_RESTORE_PREPARE:
+       case PM_SUSPEND_PREPARE:
+               ret = watchdog_dev_suspend(wdd);
+               break;
+       case PM_POST_HIBERNATION:
+       case PM_POST_RESTORE:
+       case PM_POST_SUSPEND:
+               ret = watchdog_dev_resume(wdd);
+               break;
+       }
+
+       if (ret)
+               return NOTIFY_BAD;
+
+       return NOTIFY_DONE;
+}
+
 /**
  * watchdog_set_restart_priority - Change priority of restart handler
  * @wdd: watchdog device
@@ -292,6 +320,15 @@ static int __watchdog_register_device(struct watchdog_device *wdd)
                                wdd->id, ret);
        }
 
+       if (test_bit(WDOG_NO_PING_ON_SUSPEND, &wdd->status)) {
+               wdd->pm_nb.notifier_call = watchdog_pm_notifier;
+
+               ret = register_pm_notifier(&wdd->pm_nb);
+               if (ret)
+                       pr_warn("watchdog%d: Cannot register pm handler (%d)\n",
+                               wdd->id, ret);
+       }
+
        return 0;
 }
 
index f408967ff1a4a8329592beba9862ea15a9057fcf..597cf16ea4ba146932797bc6803c22fa8bc8e9b8 100644 (file)
@@ -1228,6 +1228,53 @@ void __exit watchdog_dev_exit(void)
        kthread_destroy_worker(watchdog_kworker);
 }
 
+int watchdog_dev_suspend(struct watchdog_device *wdd)
+{
+       struct watchdog_core_data *wd_data = wdd->wd_data;
+       int ret = 0;
+
+       if (!wdd->wd_data)
+               return -ENODEV;
+
+       /* ping for the last time before suspend */
+       mutex_lock(&wd_data->lock);
+       if (watchdog_worker_should_ping(wd_data))
+               ret = __watchdog_ping(wd_data->wdd);
+       mutex_unlock(&wd_data->lock);
+
+       if (ret)
+               return ret;
+
+       /*
+        * make sure that watchdog worker will not kick in when the wdog is
+        * suspended
+        */
+       hrtimer_cancel(&wd_data->timer);
+       kthread_cancel_work_sync(&wd_data->work);
+
+       return 0;
+}
+
+int watchdog_dev_resume(struct watchdog_device *wdd)
+{
+       struct watchdog_core_data *wd_data = wdd->wd_data;
+       int ret = 0;
+
+       if (!wdd->wd_data)
+               return -ENODEV;
+
+       /*
+        * __watchdog_ping will also retrigger hrtimer and therefore restore the
+        * ping worker if needed.
+        */
+       mutex_lock(&wd_data->lock);
+       if (watchdog_worker_should_ping(wd_data))
+               ret = __watchdog_ping(wd_data->wdd);
+       mutex_unlock(&wd_data->lock);
+
+       return ret;
+}
+
 module_param(handle_boot_enabled, bool, 0444);
 MODULE_PARM_DESC(handle_boot_enabled,
        "Watchdog core auto-updates boot enabled watchdogs before userspace takes over (default="
index 9b19e6bb68b501c92644e49cd5f515ae5f1cf231..99660197a36cb44099d9a96c4173a5b712e4af8b 100644 (file)
@@ -107,6 +107,7 @@ struct watchdog_device {
        unsigned int max_hw_heartbeat_ms;
        struct notifier_block reboot_nb;
        struct notifier_block restart_nb;
+       struct notifier_block pm_nb;
        void *driver_data;
        struct watchdog_core_data *wd_data;
        unsigned long status;
@@ -116,6 +117,7 @@ struct watchdog_device {
 #define WDOG_STOP_ON_REBOOT    2       /* Should be stopped on reboot */
 #define WDOG_HW_RUNNING                3       /* True if HW watchdog running */
 #define WDOG_STOP_ON_UNREGISTER        4       /* Should be stopped on unregister */
+#define WDOG_NO_PING_ON_SUSPEND        5       /* Ping worker should be stopped on suspend */
        struct list_head deferred;
 };
 
@@ -156,6 +158,12 @@ static inline void watchdog_stop_on_unregister(struct watchdog_device *wdd)
        set_bit(WDOG_STOP_ON_UNREGISTER, &wdd->status);
 }
 
+/* Use the following function to stop the wdog ping worker when suspending */
+static inline void watchdog_stop_ping_on_suspend(struct watchdog_device *wdd)
+{
+       set_bit(WDOG_NO_PING_ON_SUSPEND, &wdd->status);
+}
+
 /* Use the following function to check if a timeout value is invalid */
 static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigned int t)
 {
@@ -209,6 +217,8 @@ extern int watchdog_init_timeout(struct watchdog_device *wdd,
                                  unsigned int timeout_parm, struct device *dev);
 extern int watchdog_register_device(struct watchdog_device *);
 extern void watchdog_unregister_device(struct watchdog_device *);
+int watchdog_dev_suspend(struct watchdog_device *wdd);
+int watchdog_dev_resume(struct watchdog_device *wdd);
 
 int watchdog_set_last_hw_keepalive(struct watchdog_device *, unsigned int);