From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756207AbcFGRkl (ORCPT ); Tue, 7 Jun 2016 13:40:41 -0400 Received: from relay1.mentorg.com ([192.94.38.131]:39271 "EHLO relay1.mentorg.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756085AbcFGRi7 (ORCPT ); Tue, 7 Jun 2016 13:38:59 -0400 From: Vladimir Zapolskiy To: Wim Van Sebroeck , Guenter Roeck CC: Wolfram Sang , Robin Gong , , Subject: [PATCH v3 4/6] watchdog: add watchdog pretimeout framework Date: Tue, 7 Jun 2016 20:38:45 +0300 Message-ID: <1465321127-19522-5-git-send-email-vladimir_zapolskiy@mentor.com> X-Mailer: git-send-email 2.5.0 In-Reply-To: <1465321127-19522-1-git-send-email-vladimir_zapolskiy@mentor.com> References: <1465321127-19522-1-git-send-email-vladimir_zapolskiy@mentor.com> MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [137.202.0.76] Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 two device attributes in sysfs: pretimeout to display currently set pretimeout value and pretimeout_governor attribute to display the selected watchdog pretimeout governor. Watchdogs with no WDIOF_PRETIMEOUT capability has no changes in sysfs, and such watchdog devices do not require the framework. Signed-off-by: Vladimir Zapolskiy --- Changes from v2 to v3: * essentially simplified the implementation due to removal of runtime dynamic selection of watchdog pretimeout governors by a user, this feature is supposed to be added later on * removed support of sleepable watchdog pretimeout governors * moved sysfs device attributes to watchdog_dev.c, this required to add exported watchdog_pretimeout_governor_name() interface * if pretimeout framework is not selected, then pretimeout event ends up in kernel panic -- this behaviour as a default one was asked by Guenter 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 | 9 +++ drivers/watchdog/watchdog_dev.c | 16 ++++ drivers/watchdog/watchdog_pretimeout.c | 134 +++++++++++++++++++++++++++++++++ drivers/watchdog/watchdog_pretimeout.h | 42 +++++++++++ include/linux/watchdog.h | 13 ++++ 7 files changed, 226 insertions(+), 1 deletion(-) 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 b54f26c..354217e 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1800,4 +1800,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 a46e7c1..cca47de 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 7c3ba58..ae6c23a 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -40,6 +40,7 @@ #include /* For of_get_timeout_sec */ #include "watchdog_core.h" /* For watchdog_dev_register/... */ +#include "watchdog_pretimeout.h" static DEFINE_IDA(watchdog_ida); @@ -244,6 +245,13 @@ static int __watchdog_register_device(struct watchdog_device *wdd) } } + ret = watchdog_register_pretimeout(wdd); + if (ret) { + watchdog_dev_unregister(wdd); + ida_simple_remove(&watchdog_ida, wdd->id); + return ret; + } + if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) { wdd->reboot_nb.notifier_call = watchdog_reboot_notifier; @@ -251,6 +259,7 @@ static int __watchdog_register_device(struct watchdog_device *wdd) if (ret) { pr_err("watchdog%d: Cannot register reboot notifier (%d)\n", wdd->id, ret); + watchdog_unregister_pretimeout(wdd); watchdog_dev_unregister(wdd); ida_simple_remove(&watchdog_ida, wdd->id); return ret; diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 87bbae7..c0fd743 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -49,6 +49,7 @@ #include /* For copy_to_user/put_user/... */ #include "watchdog_core.h" +#include "watchdog_pretimeout.h" /* * struct watchdog_core_data - watchdog core internal data @@ -452,6 +453,16 @@ static ssize_t pretimeout_show(struct device *dev, } static DEVICE_ATTR_RO(pretimeout); +static ssize_t pretimeout_governor_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + return watchdog_pretimeout_governor_name(wdd, buf); +} +static DEVICE_ATTR_RO(pretimeout_governor); + static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, int n) { @@ -466,6 +477,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; } @@ -478,6 +493,7 @@ static struct attribute *wdt_attrs[] = { &dev_attr_status.attr, &dev_attr_nowayout.attr, &dev_attr_pretimeout.attr, + &dev_attr_pretimeout_governor.attr, NULL, }; diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c new file mode 100644 index 0000000..ebfc3d6 --- /dev/null +++ b/drivers/watchdog/watchdog_pretimeout.c @@ -0,0 +1,134 @@ +/* + * 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 +#include +#include +#include + +#include "watchdog_pretimeout.h" + +/* Default watchdog pretimeout governor */ +static struct watchdog_governor *default_gov; + +/* 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); + +struct watchdog_pretimeout { + struct watchdog_device *wdd; + struct list_head entry; +}; + +int watchdog_pretimeout_governor_name(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); + else + count = sprintf(buf, "N/A\n"); + spin_unlock_irq(&pretimeout_lock); + + return count; +} + +void watchdog_notify_pretimeout(struct watchdog_device *wdd) +{ + unsigned long flags; + + if (!wdd) + return; + + 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 (!gov || !gov->name || !gov->pretimeout || + strlen(gov->name) >= WATCHDOG_GOV_NAME_MAXLEN) + return -EINVAL; + + if (default_gov) + return -EBUSY; + + 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); + + default_gov = gov; + + return 0; +} + +void watchdog_unregister_governor(struct watchdog_governor *gov) +{ + if (!gov) + return; + + if (default_gov == gov) + default_gov = NULL; +} + +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; + + if (!(wdd->info->options & WDIOF_PRETIMEOUT)) + return; + + spin_lock_irq(&pretimeout_lock); + wdd->gov = NULL; + + list_for_each_entry(p, &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 index 0000000..9e08d56 --- /dev/null +++ b/drivers/watchdog/watchdog_pretimeout.h @@ -0,0 +1,42 @@ +#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_core.c */ +int watchdog_register_pretimeout(struct watchdog_device *wdd); +void watchdog_unregister_pretimeout(struct watchdog_device *wdd); + +/* Interfaces to watchdog_dev.c */ +int watchdog_pretimeout_governor_name(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_name(struct watchdog_device *wdd, + char *buf) +{ + return 0; +} +#endif + +#endif diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index e3d23d3..0d18113 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -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). * @min_timeout:The watchdog devices minimum timeout value (in seconds). @@ -95,6 +97,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; @@ -183,6 +186,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) +{ + panic("watchdog pretimeout event\n"); +} +#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, -- 2.5.0