[PATCH 1/6] watchdog: add watchdog pretimeout framework

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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>
---
 drivers/watchdog/Kconfig               |   8 +
 drivers/watchdog/Makefile              |   5 +-
 drivers/watchdog/watchdog_core.c       |  14 +-
 drivers/watchdog/watchdog_pretimeout.c | 348 +++++++++++++++++++++++++++++++++
 drivers/watchdog/watchdog_pretimeout.h |  31 +++
 include/linux/watchdog.h               |  10 +
 6 files changed, 413 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 7a8a6c6..eeb515c 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..27c531f
--- /dev/null
+++ b/drivers/watchdog/watchdog_pretimeout.c
@@ -0,0 +1,348 @@
+/*
+ * 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;
+};
+
+#define to_pretimeout(wk) container_of(work, struct watchdog_pretimeout, wk)
+
+static struct watchdog_governor *find_governor(const char *gov_name)
+{
+	struct watchdog_governor *gov;
+
+	list_for_each_entry(gov, &governor_list, entry)
+		if (!strncasecmp(gov_name, gov->name, WATCHDOG_GOV_NAME_LEN))
+			return gov;
+
+	return NULL;
+}
+
+static struct watchdog_governor *find_default_governor(void)
+{
+	struct watchdog_governor *gov;
+
+	list_for_each_entry(gov, &governor_list, entry)
+		if (gov->is_default)
+			return gov;
+
+	return NULL;
+}
+
+static void watchdog_pretimeout_execute(struct work_struct *work)
+{
+	struct watchdog_pretimeout *p;
+
+	mutex_lock(&governor_lock);
+
+	p = to_pretimeout(work);
+
+	/* While we were scheduled a governer may be unregistered */
+	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;
+	}
+
+	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;
+
+	if (!gov || !gov->name || !gov->pretimeout ||
+	    strlen(gov->name) >= WATCHDOG_GOV_NAME_LEN)
+		return -EINVAL;
+
+	mutex_lock(&governor_lock);
+
+	if (find_governor(gov->name)) {
+		mutex_unlock(&governor_lock);
+		return -EBUSY;
+	}
+
+	if (gov->is_default) {
+		if (find_default_governor()) {
+			mutex_unlock(&governor_lock);
+			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);
+	}
+
+	list_add(&gov->entry, &governor_list);
+
+	mutex_unlock(&governor_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(watchdog_register_governor);
+
+void watchdog_unregister_governor(struct watchdog_governor *gov)
+{
+	struct watchdog_pretimeout *p;
+	struct watchdog_governor *gov_default;
+
+	if (!gov)
+		return;
+
+	mutex_lock(&governor_lock);
+
+	gov_default = find_default_governor();
+	if (gov_default == gov)
+		gov_default = NULL;
+
+	spin_lock_irq(&pretimeout_lock);
+	list_for_each_entry(p, &pretimeout_list, entry)
+		if (p->wdd->gov == gov)
+			p->wdd->gov = gov_default;
+	spin_unlock_irq(&pretimeout_lock);
+
+	list_del(&gov->entry);
+
+	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 watchdog_governor *gov;
+
+	mutex_lock(&governor_lock);
+
+	gov = find_governor(buf);
+	if (!gov) {
+		mutex_unlock(&governor_lock);
+		return -EINVAL;
+	}
+
+	spin_lock_irq(&pretimeout_lock);
+	wdd->gov = 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 watchdog_governor *gov;
+	int count = 0;
+
+	mutex_lock(&governor_lock);
+
+	list_for_each_entry(gov, &governor_list, entry)
+		count += sprintf(buf + count, "%s\n", 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();
+
+	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;
+
+	mutex_lock(&governor_lock);
+
+	spin_lock_irq(&pretimeout_lock);
+	wdd->gov = NULL;
+	spin_unlock_irq(&pretimeout_lock);
+
+	flush_workqueue(pretimeout_wq);
+
+	/* No need to use list_for_each_entry_safe(), remove only one entry */
+	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..3af714f
--- /dev/null
+++ b/drivers/watchdog/watchdog_pretimeout.h
@@ -0,0 +1,31 @@
+#ifndef __WATCHDOG_PRETIMEOUT_H
+#define __WATCHDOG_PRETIMEOUT_H
+
+#include <linux/watchdog.h>
+
+#define WATCHDOG_GOV_NAME_LEN	20
+
+struct watchdog_governor {
+	const char		name[WATCHDOG_GOV_NAME_LEN];
+	void			(*pretimeout)(struct watchdog_device *wdd);
+	bool			is_default;
+	struct list_head	entry;
+};
+
+/* 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.1.4

--
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



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux