Commit ff84136c authored by Vladimir Zapolskiy's avatar Vladimir Zapolskiy Committed by Wim Van Sebroeck
Browse files

watchdog: add watchdog pretimeout governor framework



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: default avatarVladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
Reviewed-by: default avatarGuenter Roeck <linux@roeck-us.net>
Reviewed-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Tested-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarWim Van Sebroeck <wim@iguana.be>
parent fc113d54
Loading
Loading
Loading
Loading
+13 −0
Original line number Original line Diff line number Diff line
@@ -48,6 +48,7 @@ struct watchdog_device {
	const struct attribute_group **groups;
	const struct attribute_group **groups;
	const struct watchdog_info *info;
	const struct watchdog_info *info;
	const struct watchdog_ops *ops;
	const struct watchdog_ops *ops;
	const struct watchdog_governor *gov;
	unsigned int bootstatus;
	unsigned int bootstatus;
	unsigned int timeout;
	unsigned int timeout;
	unsigned int pretimeout;
	unsigned int pretimeout;
@@ -75,6 +76,7 @@ It contains following fields:
* info: a pointer to a watchdog_info structure. This structure gives some
* info: a pointer to a watchdog_info structure. This structure gives some
  additional information about the watchdog timer itself. (Like it's unique name)
  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.
* 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).
* timeout: the watchdog timer's timeout value (in seconds).
  This is the time after which the system will reboot if user space does
  This is the time after which the system will reboot if user space does
  not send a heartbeat request if WDOG_ACTIVE is set.
  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
* 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
  available, and/or if restart is sufficient to restart the entire system
* 255: highest priority, will preempt all other restart handlers
* 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.
+7 −0
Original line number Original line Diff line number Diff line
@@ -1831,4 +1831,11 @@ config USBPCWATCHDOG


	  Most people will say N.
	  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
endif # WATCHDOG
+4 −1
Original line number Original line Diff line number Diff line
@@ -3,9 +3,12 @@
#
#


# The WatchDog Timer Driver Core.
# The WatchDog Timer Driver Core.
watchdog-objs	+= watchdog_core.o watchdog_dev.o
obj-$(CONFIG_WATCHDOG_CORE)	+= watchdog.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
# Only one watchdog can succeed. We probe the ISA/PCI/USB based
# watchdog-cards first, then the architecture specific watchdog
# watchdog-cards first, then the architecture specific watchdog
# drivers and then the architecture independent "softdog" driver.
# drivers and then the architecture independent "softdog" driver.
+23 −0
Original line number Original line Diff line number Diff line
@@ -49,6 +49,7 @@
#include <linux/uaccess.h>	/* For copy_to_user/put_user/... */
#include <linux/uaccess.h>	/* For copy_to_user/put_user/... */


#include "watchdog_core.h"
#include "watchdog_core.h"
#include "watchdog_pretimeout.h"


/*
/*
 * struct watchdog_core_data - watchdog core internal data
 * 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 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,
static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr,
				int n)
				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 &&
	else if (attr == &dev_attr_pretimeout.attr &&
		 !(wdd->info->options & WDIOF_PRETIMEOUT))
		 !(wdd->info->options & WDIOF_PRETIMEOUT))
		mode = 0;
		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;
	return mode;
}
}
@@ -512,6 +527,7 @@ static struct attribute *wdt_attrs[] = {
	&dev_attr_bootstatus.attr,
	&dev_attr_bootstatus.attr,
	&dev_attr_status.attr,
	&dev_attr_status.attr,
	&dev_attr_nowayout.attr,
	&dev_attr_nowayout.attr,
	&dev_attr_pretimeout_governor.attr,
	NULL,
	NULL,
};
};


@@ -989,6 +1005,12 @@ int watchdog_dev_register(struct watchdog_device *wdd)
		return PTR_ERR(dev);
		return PTR_ERR(dev);
	}
	}


	ret = watchdog_register_pretimeout(wdd);
	if (ret) {
		device_destroy(&watchdog_class, devno);
		watchdog_cdev_unregister(wdd);
	}

	return ret;
	return ret;
}
}


@@ -1002,6 +1024,7 @@ int watchdog_dev_register(struct watchdog_device *wdd)


void watchdog_dev_unregister(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);
	device_destroy(&watchdog_class, wdd->wd_data->cdev.dev);
	watchdog_cdev_unregister(wdd);
	watchdog_cdev_unregister(wdd);
}
}
+131 −0
Original line number Original line Diff line number Diff line
/*
 * 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);
}
Loading