watchdog: add watchdog pretimeout governor framework
authorVladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
Fri, 7 Oct 2016 12:39:54 +0000 (15:39 +0300)
committerWim Van Sebroeck <wim@iguana.be>
Sat, 8 Oct 2016 08:27:10 +0000 (10:27 +0200)
The change adds a simple watchdog pretimeout framework infrastructure,
its purpose is to allow users to select a desired handling of watchdog
pretimeout events, which may be generated by some watchdog devices.

A user selects a default watchdog pretimeout governor during
compilation stage.

Watchdogs with WDIOF_PRETIMEOUT capability now have one more device
attribute in sysfs, pretimeout_governor attribute is intended to display
the selected watchdog pretimeout governor.

The framework has no impact at runtime on watchdog devices with no
WDIOF_PRETIMEOUT capability set.

Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Tested-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Documentation/watchdog/watchdog-kernel-api.txt
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/watchdog_dev.c
drivers/watchdog/watchdog_pretimeout.c [new file with mode: 0644]
drivers/watchdog/watchdog_pretimeout.h [new file with mode: 0644]
include/linux/watchdog.h

index 3402dca..ea27747 100644 (file)
@@ -48,6 +48,7 @@ struct watchdog_device {
        const struct attribute_group **groups;
        const struct watchdog_info *info;
        const struct watchdog_ops *ops;
+       const struct watchdog_governor *gov;
        unsigned int bootstatus;
        unsigned int timeout;
        unsigned int pretimeout;
@@ -75,6 +76,7 @@ It contains following fields:
 * info: a pointer to a watchdog_info structure. This structure gives some
   additional information about the watchdog timer itself. (Like it's unique name)
 * ops: a pointer to the list of watchdog operations that the watchdog supports.
+* gov: a pointer to the assigned watchdog device pretimeout governor or NULL.
 * timeout: the watchdog timer's timeout value (in seconds).
   This is the time after which the system will reboot if user space does
   not send a heartbeat request if WDOG_ACTIVE is set.
@@ -288,3 +290,14 @@ User should follow the following guidelines for setting the priority:
 * 128: default restart handler, use if no other handler is expected to be
   available, and/or if restart is sufficient to restart the entire system
 * 255: highest priority, will preempt all other restart handlers
+
+To raise a pretimeout notification, the following function should be used:
+
+void watchdog_notify_pretimeout(struct watchdog_device *wdd)
+
+The function can be called in the interrupt context. If watchdog pretimeout
+governor framework (kbuild CONFIG_WATCHDOG_PRETIMEOUT_GOV symbol) is enabled,
+an action is taken by a preconfigured pretimeout governor preassigned to
+the watchdog device. If watchdog pretimeout governor framework is not
+enabled, watchdog_notify_pretimeout() prints a notification message to
+the kernel log buffer.
index 1bffe00..04d535a 100644 (file)
@@ -1831,4 +1831,11 @@ config USBPCWATCHDOG
 
          Most people will say N.
 
+comment "Watchdog Pretimeout Governors"
+
+config WATCHDOG_PRETIMEOUT_GOV
+       bool "Enable watchdog pretimeout governors"
+       help
+         The option allows to select watchdog pretimeout governors.
+
 endif # WATCHDOG
index c22ad3e..990c36e 100644 (file)
@@ -3,9 +3,12 @@
 #
 
 # The WatchDog Timer Driver Core.
-watchdog-objs  += watchdog_core.o watchdog_dev.o
 obj-$(CONFIG_WATCHDOG_CORE)    += watchdog.o
 
+watchdog-objs  += watchdog_core.o watchdog_dev.o
+
+watchdog-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV)     += watchdog_pretimeout.o
+
 # Only one watchdog can succeed. We probe the ISA/PCI/USB based
 # watchdog-cards first, then the architecture specific watchdog
 # drivers and then the architecture independent "softdog" driver.
index 4b381a6..d2d0b5e 100644 (file)
@@ -49,6 +49,7 @@
 #include <linux/uaccess.h>     /* For copy_to_user/put_user/... */
 
 #include "watchdog_core.h"
+#include "watchdog_pretimeout.h"
 
 /*
  * struct watchdog_core_data - watchdog core internal data
@@ -488,6 +489,16 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
 }
 static DEVICE_ATTR_RO(state);
 
+static ssize_t pretimeout_governor_show(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+       return watchdog_pretimeout_governor_get(wdd, buf);
+}
+static DEVICE_ATTR_RO(pretimeout_governor);
+
 static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr,
                                int n)
 {
@@ -500,6 +511,10 @@ static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr,
        else if (attr == &dev_attr_pretimeout.attr &&
                 !(wdd->info->options & WDIOF_PRETIMEOUT))
                mode = 0;
+       else if (attr == &dev_attr_pretimeout_governor.attr &&
+                (!(wdd->info->options & WDIOF_PRETIMEOUT) ||
+                 !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)))
+               mode = 0;
 
        return mode;
 }
@@ -512,6 +527,7 @@ static struct attribute *wdt_attrs[] = {
        &dev_attr_bootstatus.attr,
        &dev_attr_status.attr,
        &dev_attr_nowayout.attr,
+       &dev_attr_pretimeout_governor.attr,
        NULL,
 };
 
@@ -989,6 +1005,12 @@ int watchdog_dev_register(struct watchdog_device *wdd)
                return PTR_ERR(dev);
        }
 
+       ret = watchdog_register_pretimeout(wdd);
+       if (ret) {
+               device_destroy(&watchdog_class, devno);
+               watchdog_cdev_unregister(wdd);
+       }
+
        return ret;
 }
 
@@ -1002,6 +1024,7 @@ int watchdog_dev_register(struct watchdog_device *wdd)
 
 void watchdog_dev_unregister(struct watchdog_device *wdd)
 {
+       watchdog_unregister_pretimeout(wdd);
        device_destroy(&watchdog_class, wdd->wd_data->cdev.dev);
        watchdog_cdev_unregister(wdd);
 }
diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c
new file mode 100644 (file)
index 0000000..7261225
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015-2016 Mentor Graphics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/watchdog.h>
+
+#include "watchdog_pretimeout.h"
+
+/* Default watchdog pretimeout governor */
+static struct watchdog_governor *default_gov;
+
+/* The spinlock protects default_gov, wdd->gov and pretimeout_list */
+static DEFINE_SPINLOCK(pretimeout_lock);
+
+/* List of watchdog devices, which can generate a pretimeout event */
+static LIST_HEAD(pretimeout_list);
+
+struct watchdog_pretimeout {
+       struct watchdog_device          *wdd;
+       struct list_head                entry;
+};
+
+int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf)
+{
+       int count = 0;
+
+       spin_lock_irq(&pretimeout_lock);
+       if (wdd->gov)
+               count = sprintf(buf, "%s\n", wdd->gov->name);
+       spin_unlock_irq(&pretimeout_lock);
+
+       return count;
+}
+
+void watchdog_notify_pretimeout(struct watchdog_device *wdd)
+{
+       unsigned long flags;
+
+       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);
+}
+EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout);
+
+int watchdog_register_governor(struct watchdog_governor *gov)
+{
+       struct watchdog_pretimeout *p;
+
+       if (!default_gov) {
+               spin_lock_irq(&pretimeout_lock);
+               default_gov = gov;
+
+               list_for_each_entry(p, &pretimeout_list, entry)
+                       if (!p->wdd->gov)
+                               p->wdd->gov = default_gov;
+               spin_unlock_irq(&pretimeout_lock);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(watchdog_register_governor);
+
+void watchdog_unregister_governor(struct watchdog_governor *gov)
+{
+       struct watchdog_pretimeout *p;
+
+       spin_lock_irq(&pretimeout_lock);
+       if (gov == default_gov)
+               default_gov = NULL;
+
+       list_for_each_entry(p, &pretimeout_list, entry)
+               if (p->wdd->gov == gov)
+                       p->wdd->gov = default_gov;
+       spin_unlock_irq(&pretimeout_lock);
+}
+EXPORT_SYMBOL(watchdog_unregister_governor);
+
+int watchdog_register_pretimeout(struct watchdog_device *wdd)
+{
+       struct watchdog_pretimeout *p;
+
+       if (!(wdd->info->options & WDIOF_PRETIMEOUT))
+               return 0;
+
+       p = kzalloc(sizeof(*p), GFP_KERNEL);
+       if (!p)
+               return -ENOMEM;
+
+       spin_lock_irq(&pretimeout_lock);
+       list_add(&p->entry, &pretimeout_list);
+       p->wdd = wdd;
+       wdd->gov = default_gov;
+       spin_unlock_irq(&pretimeout_lock);
+
+       return 0;
+}
+
+void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
+{
+       struct watchdog_pretimeout *p, *t;
+
+       if (!(wdd->info->options & WDIOF_PRETIMEOUT))
+               return;
+
+       spin_lock_irq(&pretimeout_lock);
+       wdd->gov = NULL;
+
+       list_for_each_entry_safe(p, t, &pretimeout_list, entry) {
+               if (p->wdd == wdd) {
+                       list_del(&p->entry);
+                       break;
+               }
+       }
+       spin_unlock_irq(&pretimeout_lock);
+
+       kfree(p);
+}
diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h
new file mode 100644 (file)
index 0000000..c6cd9f8
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef __WATCHDOG_PRETIMEOUT_H
+#define __WATCHDOG_PRETIMEOUT_H
+
+#define WATCHDOG_GOV_NAME_MAXLEN       20
+
+struct watchdog_device;
+
+struct watchdog_governor {
+       const char      name[WATCHDOG_GOV_NAME_MAXLEN];
+       void            (*pretimeout)(struct watchdog_device *wdd);
+};
+
+#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)
+/* Interfaces to watchdog pretimeout governors */
+int watchdog_register_governor(struct watchdog_governor *gov);
+void watchdog_unregister_governor(struct watchdog_governor *gov);
+
+/* Interfaces to watchdog_dev.c */
+int watchdog_register_pretimeout(struct watchdog_device *wdd);
+void watchdog_unregister_pretimeout(struct watchdog_device *wdd);
+int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf);
+
+#else
+static inline int watchdog_register_pretimeout(struct watchdog_device *wdd)
+{
+       return 0;
+}
+
+static inline void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
+{
+}
+
+static inline int watchdog_pretimeout_governor_get(struct watchdog_device *wdd,
+                                                  char *buf)
+{
+       return -EINVAL;
+}
+#endif
+
+#endif
index 4035df7..35a4d81 100644 (file)
@@ -19,6 +19,7 @@
 struct watchdog_ops;
 struct watchdog_device;
 struct watchdog_core_data;
+struct watchdog_governor;
 
 /** struct watchdog_ops - The watchdog-devices operations
  *
@@ -61,6 +62,7 @@ struct watchdog_ops {
  *             watchdog device.
  * @info:      Pointer to a watchdog_info structure.
  * @ops:       Pointer to the list of watchdog operations.
+ * @gov:       Pointer to watchdog pretimeout governor.
  * @bootstatus:        Status of the watchdog device at boot.
  * @timeout:   The watchdog devices timeout value (in seconds).
  * @pretimeout: The watchdog devices pre_timeout value.
@@ -97,6 +99,7 @@ struct watchdog_device {
        const struct attribute_group **groups;
        const struct watchdog_info *info;
        const struct watchdog_ops *ops;
+       const struct watchdog_governor *gov;
        unsigned int bootstatus;
        unsigned int timeout;
        unsigned int pretimeout;
@@ -185,6 +188,16 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
        return wdd->driver_data;
 }
 
+/* Use the following functions to report watchdog pretimeout event */
+#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)
+void watchdog_notify_pretimeout(struct watchdog_device *wdd);
+#else
+static inline void watchdog_notify_pretimeout(struct watchdog_device *wdd)
+{
+       pr_alert("watchdog%d: pretimeout event\n", wdd->id);
+}
+#endif
+
 /* drivers/watchdog/watchdog_core.c */
 void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority);
 extern int watchdog_init_timeout(struct watchdog_device *wdd,