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 a watchdog driver. By design every watchdog pretimeout governor may be compiled as a kernel module, a user selects a default watchdog pretimeout governor during compilation stage and can select another governor in runtime. Watchdogs with WDIOF_PRETIMEOUT capability now have two device attributes in sysfs: read/write pretimeout_governor attribute and read only pretimeout_available_governors attribute. Watchdogs with no WDIOF_PRETIMEOUT capability has no changes in sysfs. Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@xxxxxxxxxx> --- Changes from v1 to v2: * removed framework private bits from struct watchdog_governor, * centralized compile-time selection of a default governor in watchdog_pretimeout.h, * added can_sleep option, now only sleeping governors (e.g. userspace) will be executed in a special workqueue, * changed fallback logic, if a governor in use is removed, now this situation is not possible, because in use governors have non-zero module refcount, drivers/watchdog/Kconfig | 8 + drivers/watchdog/Makefile | 5 +- drivers/watchdog/watchdog_core.c | 14 +- drivers/watchdog/watchdog_pretimeout.c | 370 +++++++++++++++++++++++++++++++++ drivers/watchdog/watchdog_pretimeout.h | 32 +++ include/linux/watchdog.h | 10 + 6 files changed, 436 insertions(+), 3 deletions(-) create mode 100644 drivers/watchdog/watchdog_pretimeout.c create mode 100644 drivers/watchdog/watchdog_pretimeout.h diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1c427be..8613639 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1625,4 +1625,12 @@ config USBPCWATCHDOG Most people will say N. +comment "Watchdog Pretimeout Governors" + +config WATCHDOG_PRETIMEOUT_GOV + bool "Enable watchdog pretimeout governors" + default n + help + The option allows to select watchdog pretimeout governors. + endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 53d4827..6749cac 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -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. diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 873f139..ca99585 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -39,6 +39,7 @@ #include <linux/of.h> /* For of_get_timeout_sec */ #include "watchdog_core.h" /* For watchdog_dev_register/... */ +#include "watchdog_pretimeout.h" static DEFINE_IDA(watchdog_ida); static struct class *watchdog_class; @@ -194,7 +195,7 @@ static int __watchdog_register_device(struct watchdog_device *wdd) devno = wdd->cdev.dev; wdd->dev = device_create(watchdog_class, wdd->parent, devno, - NULL, "watchdog%d", wdd->id); + wdd, "watchdog%d", wdd->id); if (IS_ERR(wdd->dev)) { watchdog_dev_unregister(wdd); ida_simple_remove(&watchdog_ida, id); @@ -202,7 +203,14 @@ static int __watchdog_register_device(struct watchdog_device *wdd) return ret; } - return 0; + ret = watchdog_register_pretimeout(wdd); + if (ret) { + device_destroy(watchdog_class, devno); + watchdog_dev_unregister(wdd); + ida_simple_remove(&watchdog_ida, id); + } + + return ret; } /** @@ -238,6 +246,8 @@ static void __watchdog_unregister_device(struct watchdog_device *wdd) if (wdd == NULL) return; + watchdog_unregister_pretimeout(wdd); + devno = wdd->cdev.dev; ret = watchdog_dev_unregister(wdd); if (ret) diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c new file mode 100644 index 0000000..26691ad --- /dev/null +++ b/drivers/watchdog/watchdog_pretimeout.c @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2015 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/module.h> +#include <linux/slab.h> +#include <linux/watchdog.h> +#include <linux/workqueue.h> + +#include "watchdog_pretimeout.h" + +/* The mutex protects governor list and serializes external interfaces */ +static DEFINE_MUTEX(governor_lock); + +/* List of registered watchdog pretimeout governors */ +static LIST_HEAD(governor_list); + +/* The spinlock protects 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); + +/* Single workqueue to handle pretimeout events from all watchdogs */ +static struct workqueue_struct *pretimeout_wq; + +struct watchdog_pretimeout { + struct watchdog_device *wdd; + struct work_struct work; + struct list_head entry; +}; + +struct governor_priv { + struct watchdog_governor *gov; + bool is_default; + struct list_head entry; +}; + +static struct governor_priv *find_governor_by_name(const char *gov_name) +{ + struct governor_priv *priv; + + list_for_each_entry(priv, &governor_list, entry) + if (!strncmp(gov_name, priv->gov->name, WATCHDOG_GOV_NAME_LEN)) + return priv; + + return NULL; +} + +static struct watchdog_governor *find_default_governor(void) +{ + struct governor_priv *priv; + + list_for_each_entry(priv, &governor_list, entry) + if (priv->is_default) + return priv->gov; + + return NULL; +} + +static void watchdog_pretimeout_execute(struct work_struct *wk) +{ + struct watchdog_pretimeout *p; + + mutex_lock(&governor_lock); + + p = container_of(wk, struct watchdog_pretimeout, work); + + spin_lock_irq(&pretimeout_lock); + if (!p->wdd->gov) { + spin_unlock_irq(&pretimeout_lock); + return; + } + spin_unlock_irq(&pretimeout_lock); + + p->wdd->gov->pretimeout(p->wdd); + put_device(p->wdd->dev); + + mutex_unlock(&governor_lock); +} + +void watchdog_notify_pretimeout(struct watchdog_device *wdd) +{ + struct watchdog_pretimeout *p; + unsigned long flags; + + if (!wdd) + return; + + spin_lock_irqsave(&pretimeout_lock, flags); + if (!wdd->gov) { + spin_unlock_irqrestore(&pretimeout_lock, flags); + return; + } + + if (!wdd->gov->can_sleep) { + wdd->gov->pretimeout(wdd); + spin_unlock_irqrestore(&pretimeout_lock, flags); + return; + } + + list_for_each_entry(p, &pretimeout_list, entry) { + if (p->wdd == wdd) { + get_device(wdd->dev); + queue_work(pretimeout_wq, &p->work); + break; + } + } + spin_unlock_irqrestore(&pretimeout_lock, flags); +} +EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout); + +int watchdog_register_governor(struct watchdog_governor *gov) +{ + struct watchdog_pretimeout *p; + struct governor_priv *priv; + + if (!gov || !gov->name || !gov->pretimeout || + strlen(gov->name) >= WATCHDOG_GOV_NAME_LEN) + return -EINVAL; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_lock(&governor_lock); + + if (find_governor_by_name(gov->name)) { + kfree(priv); + mutex_unlock(&governor_lock); + return -EBUSY; + } + + priv->gov = gov; + + if (!strncmp(WATCHDOG_PRETIMEOUT_DEFAULT_GOV, + gov->name, WATCHDOG_GOV_NAME_LEN)) { + priv->is_default = true; + + spin_lock_irq(&pretimeout_lock); + list_for_each_entry(p, &pretimeout_list, entry) + if (!p->wdd->gov) + p->wdd->gov = gov; + spin_unlock_irq(&pretimeout_lock); + } + + list_add(&priv->entry, &governor_list); + + mutex_unlock(&governor_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(watchdog_register_governor); + +void watchdog_unregister_governor(struct watchdog_governor *gov) +{ + struct governor_priv *priv; + + if (!gov) + return; + + mutex_lock(&governor_lock); + + list_for_each_entry(priv, &governor_list, entry) { + if (priv->gov == gov) { + list_del(&priv->entry); + kfree(priv); + break; + } + } + + mutex_unlock(&governor_lock); +} +EXPORT_SYMBOL_GPL(watchdog_unregister_governor); + +static ssize_t pretimeout_governor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + struct governor_priv *priv; + + mutex_lock(&governor_lock); + + priv = find_governor_by_name(buf); + if (!priv) { + mutex_unlock(&governor_lock); + return -EINVAL; + } + + if (!try_module_get(priv->gov->owner)) { + mutex_unlock(&governor_lock); + return -ENODEV; + } + + spin_lock_irq(&pretimeout_lock); + if (wdd->gov) + module_put(wdd->gov->owner); + wdd->gov = priv->gov; + spin_unlock_irq(&pretimeout_lock); + + mutex_unlock(&governor_lock); + + return count; +} + +static ssize_t pretimeout_governor_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + char gov_name[WATCHDOG_GOV_NAME_LEN] = { 0 }; + int count; + + mutex_lock(&governor_lock); + + spin_lock_irq(&pretimeout_lock); + if (wdd->gov) + strncpy(gov_name, wdd->gov->name, WATCHDOG_GOV_NAME_LEN); + spin_unlock_irq(&pretimeout_lock); + + count = sprintf(buf, "%s\n", gov_name); + + mutex_unlock(&governor_lock); + + return count; +} +static DEVICE_ATTR_RW(pretimeout_governor); + +static ssize_t pretimeout_available_governors_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct governor_priv *priv; + int count = 0; + + mutex_lock(&governor_lock); + + list_for_each_entry(priv, &governor_list, entry) + count += sprintf(buf + count, "%s\n", priv->gov->name); + + mutex_unlock(&governor_lock); + + return count; +} +static DEVICE_ATTR_RO(pretimeout_available_governors); + +static struct attribute *wdd_pretimeout_attrs[] = { + &dev_attr_pretimeout_governor.attr, + &dev_attr_pretimeout_available_governors.attr, + NULL, +}; + +static umode_t wdd_pretimeout_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct watchdog_device *wdd = dev_get_drvdata(dev); + + if (wdd->info->options & WDIOF_PRETIMEOUT) + return attr->mode; + + return 0; +} + +static const struct attribute_group wdd_pretimeout_group = { + .is_visible = wdd_pretimeout_attr_is_visible, + .attrs = wdd_pretimeout_attrs, +}; + +static const struct attribute_group *wdd_pretimeout_groups[] = { + &wdd_pretimeout_group, + NULL, +}; + +int watchdog_register_pretimeout(struct watchdog_device *wdd) +{ + struct watchdog_pretimeout *p; + struct watchdog_governor *gov; + int ret; + + if (!(wdd->info->options & WDIOF_PRETIMEOUT)) + return 0; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + INIT_WORK(&p->work, watchdog_pretimeout_execute); + + ret = sysfs_create_groups(&wdd->dev->kobj, wdd_pretimeout_groups); + if (ret) { + kfree(p); + return ret; + } + + mutex_lock(&governor_lock); + gov = find_default_governor(); + if (gov && !try_module_get(gov->owner)) { + mutex_unlock(&governor_lock); + return -ENODEV; + } + + spin_lock_irq(&pretimeout_lock); + list_add(&p->entry, &pretimeout_list); + p->wdd = wdd; + wdd->gov = gov; + spin_unlock_irq(&pretimeout_lock); + + mutex_unlock(&governor_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(watchdog_register_pretimeout); + +void watchdog_unregister_pretimeout(struct watchdog_device *wdd) +{ + struct watchdog_pretimeout *p; + + if (!(wdd->info->options & WDIOF_PRETIMEOUT)) + return; + + spin_lock_irq(&pretimeout_lock); + if (wdd->gov) + module_put(wdd->gov->owner); + wdd->gov = NULL; + spin_unlock_irq(&pretimeout_lock); + + flush_workqueue(pretimeout_wq); + + mutex_lock(&governor_lock); + + spin_lock_irq(&pretimeout_lock); + list_for_each_entry(p, &pretimeout_list, entry) { + if (p->wdd == wdd) { + list_del(&p->entry); + break; + } + } + spin_unlock_irq(&pretimeout_lock); + + kfree(p); + + mutex_unlock(&governor_lock); +} +EXPORT_SYMBOL_GPL(watchdog_unregister_pretimeout); + +static int __init watchdog_register_pretimeout_gov(void) +{ + pretimeout_wq = create_workqueue("watchdog pretimeout"); + if (!pretimeout_wq) + return -ENOMEM; + + return 0; +} +subsys_initcall(watchdog_register_pretimeout_gov); + +static void __exit watchdog_unregister_pretimeout_gov(void) +{ + flush_workqueue(pretimeout_wq); + destroy_workqueue(pretimeout_wq); +} +module_exit(watchdog_unregister_pretimeout_gov); + +MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Watchdog pretimeout governor framework"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h new file mode 100644 index 0000000..e888424f --- /dev/null +++ b/drivers/watchdog/watchdog_pretimeout.h @@ -0,0 +1,32 @@ +#ifndef __WATCHDOG_PRETIMEOUT_H +#define __WATCHDOG_PRETIMEOUT_H + +#define WATCHDOG_GOV_NAME_LEN 20 + +struct module; +struct watchdog_device; + +struct watchdog_governor { + const char name[WATCHDOG_GOV_NAME_LEN]; + void (*pretimeout)(struct watchdog_device *wdd); + bool can_sleep; + struct module *owner; +}; + +/* Interfaces to watchdog pretimeout governors */ +int watchdog_register_governor(struct watchdog_governor *gov); +void watchdog_unregister_governor(struct watchdog_governor *gov); + +/* Interfaces to watchdog_core.c */ +#ifdef CONFIG_WATCHDOG_PRETIMEOUT_GOV +int watchdog_register_pretimeout(struct watchdog_device *wdd); +void watchdog_unregister_pretimeout(struct watchdog_device *wdd); +#else +static inline int watchdog_register_pretimeout(struct watchdog_device *wdd) +{ + return 0; +} +static inline void watchdog_unregister_pretimeout(struct watchdog_device *) {} +#endif + +#endif diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 027b1f4..960223e 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -16,6 +16,7 @@ struct watchdog_ops; struct watchdog_device; +struct watchdog_governor; /** struct watchdog_ops - The watchdog-devices operations * @@ -58,6 +59,7 @@ struct watchdog_ops { * @parent: The parent bus 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). * @min_timeout:The watchdog devices minimum timeout value (in seconds). @@ -84,6 +86,7 @@ struct watchdog_device { struct device *parent; const struct watchdog_info *info; const struct watchdog_ops *ops; + struct watchdog_governor *gov; unsigned int bootstatus; unsigned int timeout; unsigned int min_timeout; @@ -147,4 +150,11 @@ extern int watchdog_init_timeout(struct watchdog_device *wdd, extern int watchdog_register_device(struct watchdog_device *); extern void watchdog_unregister_device(struct watchdog_device *); +/* drivers/watchdog/watchdog_pretimeout.c */ +#ifdef CONFIG_WATCHDOG_PRETIMEOUT_GOV +void watchdog_notify_pretimeout(struct watchdog_device *wdd); +#else +static inline void watchdog_notify_pretimeout(struct watchdog_device *wdd) {} +#endif + #endif /* ifndef _LINUX_WATCHDOG_H */ -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html