diff options
Diffstat (limited to 'target/linux/ipq806x/patches-4.4/009-6-watchdog-Separate-and-maintain-variables-based-on-variable-lifetime.patch')
-rw-r--r-- | target/linux/ipq806x/patches-4.4/009-6-watchdog-Separate-and-maintain-variables-based-on-variable-lifetime.patch | 969 |
1 files changed, 969 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches-4.4/009-6-watchdog-Separate-and-maintain-variables-based-on-variable-lifetime.patch b/target/linux/ipq806x/patches-4.4/009-6-watchdog-Separate-and-maintain-variables-based-on-variable-lifetime.patch new file mode 100644 index 0000000000..214dabfb9a --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/009-6-watchdog-Separate-and-maintain-variables-based-on-variable-lifetime.patch @@ -0,0 +1,969 @@ +From b4ffb1909843b28f3b1b60197d517b123b7a9b66 Mon Sep 17 00:00:00 2001 +From: Guenter Roeck <linux@roeck-us.net> +Date: Fri, 25 Dec 2015 16:01:42 -0800 +Subject: watchdog: Separate and maintain variables based on variable lifetime + +All variables required by the watchdog core to manage a watchdog are +currently stored in struct watchdog_device. The lifetime of those +variables is determined by the watchdog driver. However, the lifetime +of variables used by the watchdog core differs from the lifetime of +struct watchdog_device. To remedy this situation, watchdog drivers +can implement ref and unref callbacks, to be used by the watchdog +core to lock struct watchdog_device in memory. + +While this solves the immediate problem, it depends on watchdog drivers +to actually implement the ref/unref callbacks. This is error prone, +often not implemented in the first place, or not implemented correctly. + +To solve the problem without requiring driver support, split the variables +in struct watchdog_device into two data structures - one for variables +associated with the watchdog driver, one for variables associated with +the watchdog core. With this approach, the watchdog core can keep track +of its variable lifetime and no longer depends on ref/unref callbacks +in the driver. As a side effect, some of the variables originally in +struct watchdog_driver are now private to the watchdog core and no longer +visible in watchdog drivers. + +As a side effect of the changes made, an ioctl will now always fail +with -ENODEV after a watchdog device was unregistered with the character +device still open. Previously, it would only fail with -ENODEV in some +situations. Also, ioctl operations are now atomic from driver perspective. +With this change, it is now guaranteed that the driver will not unregister +a watchdog between a timeout change and the subsequent ping. + +The 'ref' and 'unref' callbacks in struct watchdog_driver are no longer +used and marked as deprecated. + +Signed-off-by: Guenter Roeck <linux@roeck-us.net> +Signed-off-by: Wim Van Sebroeck <wim@iguana.be> +--- + Documentation/watchdog/watchdog-kernel-api.txt | 45 +-- + drivers/watchdog/watchdog_core.c | 2 - + drivers/watchdog/watchdog_dev.c | 383 +++++++++++++------------ + include/linux/watchdog.h | 22 +- + 4 files changed, 218 insertions(+), 234 deletions(-) + +--- a/Documentation/watchdog/watchdog-kernel-api.txt ++++ b/Documentation/watchdog/watchdog-kernel-api.txt +@@ -44,7 +44,6 @@ The watchdog device structure looks like + + struct watchdog_device { + int id; +- struct cdev cdev; + struct device *dev; + struct device *parent; + const struct watchdog_info *info; +@@ -56,7 +55,7 @@ struct watchdog_device { + struct notifier_block reboot_nb; + struct notifier_block restart_nb; + void *driver_data; +- struct mutex lock; ++ struct watchdog_core_data *wd_data; + unsigned long status; + struct list_head deferred; + }; +@@ -66,8 +65,6 @@ It contains following fields: + /dev/watchdog0 cdev (dynamic major, minor 0) as well as the old + /dev/watchdog miscdev. The id is set automatically when calling + watchdog_register_device. +-* cdev: cdev for the dynamic /dev/watchdog<id> device nodes. This +- field is also populated by watchdog_register_device. + * dev: device under the watchdog class (created by watchdog_register_device). + * parent: set this to the parent device (or NULL) before calling + watchdog_register_device. +@@ -89,11 +86,10 @@ It contains following fields: + * driver_data: a pointer to the drivers private data of a watchdog device. + This data should only be accessed via the watchdog_set_drvdata and + watchdog_get_drvdata routines. +-* lock: Mutex for WatchDog Timer Driver Core internal use only. ++* wd_data: a pointer to watchdog core internal data. + * status: this field contains a number of status bits that give extra + information about the status of the device (Like: is the watchdog timer +- running/active, is the nowayout bit set, is the device opened via +- the /dev/watchdog interface or not, ...). ++ running/active, or is the nowayout bit set). + * deferred: entry in wtd_deferred_reg_list which is used to + register early initialized watchdogs. + +@@ -110,8 +106,8 @@ struct watchdog_ops { + int (*set_timeout)(struct watchdog_device *, unsigned int); + unsigned int (*get_timeleft)(struct watchdog_device *); + int (*restart)(struct watchdog_device *); +- void (*ref)(struct watchdog_device *); +- void (*unref)(struct watchdog_device *); ++ void (*ref)(struct watchdog_device *) __deprecated; ++ void (*unref)(struct watchdog_device *) __deprecated; + long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); + }; + +@@ -120,20 +116,6 @@ driver's operations. This module owner w + the watchdog is active. (This to avoid a system crash when you unload the + module and /dev/watchdog is still open). + +-If the watchdog_device struct is dynamically allocated, just locking the module +-is not enough and a driver also needs to define the ref and unref operations to +-ensure the structure holding the watchdog_device does not go away. +- +-The simplest (and usually sufficient) implementation of this is to: +-1) Add a kref struct to the same structure which is holding the watchdog_device +-2) Define a release callback for the kref which frees the struct holding both +-3) Call kref_init on this kref *before* calling watchdog_register_device() +-4) Define a ref operation calling kref_get on this kref +-5) Define a unref operation calling kref_put on this kref +-6) When it is time to cleanup: +- * Do not kfree() the struct holding both, the last kref_put will do this! +- * *After* calling watchdog_unregister_device() call kref_put on the kref +- + Some operations are mandatory and some are optional. The mandatory operations + are: + * start: this is a pointer to the routine that starts the watchdog timer +@@ -176,34 +158,21 @@ they are supported. These optional routi + * get_timeleft: this routines returns the time that's left before a reset. + * restart: this routine restarts the machine. It returns 0 on success or a + negative errno code for failure. +-* ref: the operation that calls kref_get on the kref of a dynamically +- allocated watchdog_device struct. +-* unref: the operation that calls kref_put on the kref of a dynamically +- allocated watchdog_device struct. + * ioctl: if this routine is present then it will be called first before we do + our own internal ioctl call handling. This routine should return -ENOIOCTLCMD + if a command is not supported. The parameters that are passed to the ioctl + call are: watchdog_device, cmd and arg. + ++The 'ref' and 'unref' operations are no longer used and deprecated. ++ + The status bits should (preferably) be set with the set_bit and clear_bit alike + bit-operations. The status bits that are defined are: + * WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device + is active or not. When the watchdog is active after booting, then you should + set this status bit (Note: when you register the watchdog timer device with + this bit set, then opening /dev/watchdog will skip the start operation) +-* WDOG_DEV_OPEN: this status bit shows whether or not the watchdog device +- was opened via /dev/watchdog. +- (This bit should only be used by the WatchDog Timer Driver Core). +-* WDOG_ALLOW_RELEASE: this bit stores whether or not the magic close character +- has been sent (so that we can support the magic close feature). +- (This bit should only be used by the WatchDog Timer Driver Core). + * WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog. + If this bit is set then the watchdog timer will not be able to stop. +-* WDOG_UNREGISTERED: this bit gets set by the WatchDog Timer Driver Core +- after calling watchdog_unregister_device, and then checked before calling +- any watchdog_ops, so that you can be sure that no operations (other then +- unref) will get called after unregister, even if userspace still holds a +- reference to /dev/watchdog + + To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog + timer device) you can either: +--- a/drivers/watchdog/watchdog_core.c ++++ b/drivers/watchdog/watchdog_core.c +@@ -210,8 +210,6 @@ static int __watchdog_register_device(st + * corrupted in a later stage then we expect a kernel panic! + */ + +- mutex_init(&wdd->lock); +- + /* Use alias for watchdog id if possible */ + if (wdd->parent) { + ret = of_alias_get_id(wdd->parent->of_node, "watchdog"); +--- a/drivers/watchdog/watchdog_dev.c ++++ b/drivers/watchdog/watchdog_dev.c +@@ -32,27 +32,51 @@ + + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +-#include <linux/module.h> /* For module stuff/... */ +-#include <linux/types.h> /* For standard types (like size_t) */ ++#include <linux/cdev.h> /* For character device */ + #include <linux/errno.h> /* For the -ENODEV/... values */ +-#include <linux/kernel.h> /* For printk/panic/... */ + #include <linux/fs.h> /* For file operations */ +-#include <linux/watchdog.h> /* For watchdog specific items */ +-#include <linux/miscdevice.h> /* For handling misc devices */ + #include <linux/init.h> /* For __init/__exit/... */ ++#include <linux/kernel.h> /* For printk/panic/... */ ++#include <linux/kref.h> /* For data references */ ++#include <linux/miscdevice.h> /* For handling misc devices */ ++#include <linux/module.h> /* For module stuff/... */ ++#include <linux/mutex.h> /* For mutexes */ ++#include <linux/slab.h> /* For memory functions */ ++#include <linux/types.h> /* For standard types (like size_t) */ ++#include <linux/watchdog.h> /* For watchdog specific items */ + #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ + + #include "watchdog_core.h" + ++/* ++ * struct watchdog_core_data - watchdog core internal data ++ * @kref: Reference count. ++ * @cdev: The watchdog's Character device. ++ * @wdd: Pointer to watchdog device. ++ * @lock: Lock for watchdog core. ++ * @status: Watchdog core internal status bits. ++ */ ++struct watchdog_core_data { ++ struct kref kref; ++ struct cdev cdev; ++ struct watchdog_device *wdd; ++ struct mutex lock; ++ unsigned long status; /* Internal status bits */ ++#define _WDOG_DEV_OPEN 0 /* Opened ? */ ++#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ ++}; ++ + /* the dev_t structure to store the dynamically allocated watchdog devices */ + static dev_t watchdog_devt; +-/* the watchdog device behind /dev/watchdog */ +-static struct watchdog_device *old_wdd; ++/* Reference to watchdog device behind /dev/watchdog */ ++static struct watchdog_core_data *old_wd_data; + + /* + * watchdog_ping: ping the watchdog. + * @wdd: the watchdog device to ping + * ++ * The caller must hold wd_data->lock. ++ * + * If the watchdog has no own ping operation then it needs to be + * restarted via the start operation. This wrapper function does + * exactly that. +@@ -61,25 +85,16 @@ static struct watchdog_device *old_wdd; + + static int watchdog_ping(struct watchdog_device *wdd) + { +- int err = 0; +- +- mutex_lock(&wdd->lock); +- +- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { +- err = -ENODEV; +- goto out_ping; +- } ++ int err; + + if (!watchdog_active(wdd)) +- goto out_ping; ++ return 0; + + if (wdd->ops->ping) + err = wdd->ops->ping(wdd); /* ping the watchdog */ + else + err = wdd->ops->start(wdd); /* restart watchdog */ + +-out_ping: +- mutex_unlock(&wdd->lock); + return err; + } + +@@ -87,6 +102,8 @@ out_ping: + * watchdog_start: wrapper to start the watchdog. + * @wdd: the watchdog device to start + * ++ * The caller must hold wd_data->lock. ++ * + * Start the watchdog if it is not active and mark it active. + * This function returns zero on success or a negative errno code for + * failure. +@@ -94,24 +111,15 @@ out_ping: + + static int watchdog_start(struct watchdog_device *wdd) + { +- int err = 0; +- +- mutex_lock(&wdd->lock); +- +- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { +- err = -ENODEV; +- goto out_start; +- } ++ int err; + + if (watchdog_active(wdd)) +- goto out_start; ++ return 0; + + err = wdd->ops->start(wdd); + if (err == 0) + set_bit(WDOG_ACTIVE, &wdd->status); + +-out_start: +- mutex_unlock(&wdd->lock); + return err; + } + +@@ -119,6 +127,8 @@ out_start: + * watchdog_stop: wrapper to stop the watchdog. + * @wdd: the watchdog device to stop + * ++ * The caller must hold wd_data->lock. ++ * + * Stop the watchdog if it is still active and unmark it active. + * This function returns zero on success or a negative errno code for + * failure. +@@ -127,93 +137,58 @@ out_start: + + static int watchdog_stop(struct watchdog_device *wdd) + { +- int err = 0; +- +- mutex_lock(&wdd->lock); +- +- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { +- err = -ENODEV; +- goto out_stop; +- } ++ int err; + + if (!watchdog_active(wdd)) +- goto out_stop; ++ return 0; + + if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) { + dev_info(wdd->dev, "nowayout prevents watchdog being stopped!\n"); +- err = -EBUSY; +- goto out_stop; ++ return -EBUSY; + } + + err = wdd->ops->stop(wdd); + if (err == 0) + clear_bit(WDOG_ACTIVE, &wdd->status); + +-out_stop: +- mutex_unlock(&wdd->lock); + return err; + } + + /* + * watchdog_get_status: wrapper to get the watchdog status + * @wdd: the watchdog device to get the status from +- * @status: the status of the watchdog device ++ * ++ * The caller must hold wd_data->lock. + * + * Get the watchdog's status flags. + */ + +-static int watchdog_get_status(struct watchdog_device *wdd, +- unsigned int *status) ++static unsigned int watchdog_get_status(struct watchdog_device *wdd) + { +- int err = 0; +- +- *status = 0; + if (!wdd->ops->status) +- return -EOPNOTSUPP; +- +- mutex_lock(&wdd->lock); +- +- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { +- err = -ENODEV; +- goto out_status; +- } +- +- *status = wdd->ops->status(wdd); ++ return 0; + +-out_status: +- mutex_unlock(&wdd->lock); +- return err; ++ return wdd->ops->status(wdd); + } + + /* + * watchdog_set_timeout: set the watchdog timer timeout + * @wdd: the watchdog device to set the timeout for + * @timeout: timeout to set in seconds ++ * ++ * The caller must hold wd_data->lock. + */ + + static int watchdog_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) + { +- int err; +- + if (!wdd->ops->set_timeout || !(wdd->info->options & WDIOF_SETTIMEOUT)) + return -EOPNOTSUPP; + + if (watchdog_timeout_invalid(wdd, timeout)) + return -EINVAL; + +- mutex_lock(&wdd->lock); +- +- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { +- err = -ENODEV; +- goto out_timeout; +- } +- +- err = wdd->ops->set_timeout(wdd, timeout); +- +-out_timeout: +- mutex_unlock(&wdd->lock); +- return err; ++ return wdd->ops->set_timeout(wdd, timeout); + } + + /* +@@ -221,30 +196,22 @@ out_timeout: + * @wdd: the watchdog device to get the remaining time from + * @timeleft: the time that's left + * ++ * The caller must hold wd_data->lock. ++ * + * Get the time before a watchdog will reboot (if not pinged). + */ + + static int watchdog_get_timeleft(struct watchdog_device *wdd, + unsigned int *timeleft) + { +- int err = 0; +- + *timeleft = 0; ++ + if (!wdd->ops->get_timeleft) + return -EOPNOTSUPP; + +- mutex_lock(&wdd->lock); +- +- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { +- err = -ENODEV; +- goto out_timeleft; +- } +- + *timeleft = wdd->ops->get_timeleft(wdd); + +-out_timeleft: +- mutex_unlock(&wdd->lock); +- return err; ++ return 0; + } + + #ifdef CONFIG_WATCHDOG_SYSFS +@@ -261,14 +228,14 @@ static ssize_t status_show(struct device + char *buf) + { + struct watchdog_device *wdd = dev_get_drvdata(dev); +- ssize_t status; +- unsigned int val; ++ struct watchdog_core_data *wd_data = wdd->wd_data; ++ unsigned int status; + +- status = watchdog_get_status(wdd, &val); +- if (!status) +- status = sprintf(buf, "%u\n", val); ++ mutex_lock(&wd_data->lock); ++ status = watchdog_get_status(wdd); ++ mutex_unlock(&wd_data->lock); + +- return status; ++ return sprintf(buf, "%u\n", status); + } + static DEVICE_ATTR_RO(status); + +@@ -285,10 +252,13 @@ static ssize_t timeleft_show(struct devi + char *buf) + { + struct watchdog_device *wdd = dev_get_drvdata(dev); ++ struct watchdog_core_data *wd_data = wdd->wd_data; + ssize_t status; + unsigned int val; + ++ mutex_lock(&wd_data->lock); + status = watchdog_get_timeleft(wdd, &val); ++ mutex_unlock(&wd_data->lock); + if (!status) + status = sprintf(buf, "%u\n", val); + +@@ -365,28 +335,17 @@ __ATTRIBUTE_GROUPS(wdt); + * @wdd: the watchdog device to do the ioctl on + * @cmd: watchdog command + * @arg: argument pointer ++ * ++ * The caller must hold wd_data->lock. + */ + + static int watchdog_ioctl_op(struct watchdog_device *wdd, unsigned int cmd, + unsigned long arg) + { +- int err; +- + if (!wdd->ops->ioctl) + return -ENOIOCTLCMD; + +- mutex_lock(&wdd->lock); +- +- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) { +- err = -ENODEV; +- goto out_ioctl; +- } +- +- err = wdd->ops->ioctl(wdd, cmd, arg); +- +-out_ioctl: +- mutex_unlock(&wdd->lock); +- return err; ++ return wdd->ops->ioctl(wdd, cmd, arg); + } + + /* +@@ -404,10 +363,11 @@ out_ioctl: + static ssize_t watchdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) + { +- struct watchdog_device *wdd = file->private_data; ++ struct watchdog_core_data *wd_data = file->private_data; ++ struct watchdog_device *wdd; ++ int err; + size_t i; + char c; +- int err; + + if (len == 0) + return 0; +@@ -416,18 +376,25 @@ static ssize_t watchdog_write(struct fil + * Note: just in case someone wrote the magic character + * five months ago... + */ +- clear_bit(WDOG_ALLOW_RELEASE, &wdd->status); ++ clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); + + /* scan to see whether or not we got the magic character */ + for (i = 0; i != len; i++) { + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') +- set_bit(WDOG_ALLOW_RELEASE, &wdd->status); ++ set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); + } + + /* someone wrote to us, so we send the watchdog a keepalive ping */ +- err = watchdog_ping(wdd); ++ ++ err = -ENODEV; ++ mutex_lock(&wd_data->lock); ++ wdd = wd_data->wdd; ++ if (wdd) ++ err = watchdog_ping(wdd); ++ mutex_unlock(&wd_data->lock); ++ + if (err < 0) + return err; + +@@ -447,71 +414,94 @@ static ssize_t watchdog_write(struct fil + static long watchdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) + { +- struct watchdog_device *wdd = file->private_data; ++ struct watchdog_core_data *wd_data = file->private_data; + void __user *argp = (void __user *)arg; ++ struct watchdog_device *wdd; + int __user *p = argp; + unsigned int val; + int err; + ++ mutex_lock(&wd_data->lock); ++ ++ wdd = wd_data->wdd; ++ if (!wdd) { ++ err = -ENODEV; ++ goto out_ioctl; ++ } ++ + err = watchdog_ioctl_op(wdd, cmd, arg); + if (err != -ENOIOCTLCMD) +- return err; ++ goto out_ioctl; + + switch (cmd) { + case WDIOC_GETSUPPORT: +- return copy_to_user(argp, wdd->info, ++ err = copy_to_user(argp, wdd->info, + sizeof(struct watchdog_info)) ? -EFAULT : 0; ++ break; + case WDIOC_GETSTATUS: +- err = watchdog_get_status(wdd, &val); +- if (err == -ENODEV) +- return err; +- return put_user(val, p); ++ val = watchdog_get_status(wdd); ++ err = put_user(val, p); ++ break; + case WDIOC_GETBOOTSTATUS: +- return put_user(wdd->bootstatus, p); ++ err = put_user(wdd->bootstatus, p); ++ break; + case WDIOC_SETOPTIONS: +- if (get_user(val, p)) +- return -EFAULT; ++ if (get_user(val, p)) { ++ err = -EFAULT; ++ break; ++ } + if (val & WDIOS_DISABLECARD) { + err = watchdog_stop(wdd); + if (err < 0) +- return err; ++ break; + } +- if (val & WDIOS_ENABLECARD) { ++ if (val & WDIOS_ENABLECARD) + err = watchdog_start(wdd); +- if (err < 0) +- return err; +- } +- return 0; ++ break; + case WDIOC_KEEPALIVE: +- if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) +- return -EOPNOTSUPP; +- return watchdog_ping(wdd); ++ if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) { ++ err = -EOPNOTSUPP; ++ break; ++ } ++ err = watchdog_ping(wdd); ++ break; + case WDIOC_SETTIMEOUT: +- if (get_user(val, p)) +- return -EFAULT; ++ if (get_user(val, p)) { ++ err = -EFAULT; ++ break; ++ } + err = watchdog_set_timeout(wdd, val); + if (err < 0) +- return err; ++ break; + /* If the watchdog is active then we send a keepalive ping + * to make sure that the watchdog keep's running (and if + * possible that it takes the new timeout) */ + err = watchdog_ping(wdd); + if (err < 0) +- return err; ++ break; + /* Fall */ + case WDIOC_GETTIMEOUT: + /* timeout == 0 means that we don't know the timeout */ +- if (wdd->timeout == 0) +- return -EOPNOTSUPP; +- return put_user(wdd->timeout, p); ++ if (wdd->timeout == 0) { ++ err = -EOPNOTSUPP; ++ break; ++ } ++ err = put_user(wdd->timeout, p); ++ break; + case WDIOC_GETTIMELEFT: + err = watchdog_get_timeleft(wdd, &val); +- if (err) +- return err; +- return put_user(val, p); ++ if (err < 0) ++ break; ++ err = put_user(val, p); ++ break; + default: +- return -ENOTTY; ++ err = -ENOTTY; ++ break; + } ++ ++out_ioctl: ++ mutex_unlock(&wd_data->lock); ++ return err; + } + + /* +@@ -526,45 +516,59 @@ static long watchdog_ioctl(struct file * + + static int watchdog_open(struct inode *inode, struct file *file) + { +- int err = -EBUSY; ++ struct watchdog_core_data *wd_data; + struct watchdog_device *wdd; ++ int err; + + /* Get the corresponding watchdog device */ + if (imajor(inode) == MISC_MAJOR) +- wdd = old_wdd; ++ wd_data = old_wd_data; + else +- wdd = container_of(inode->i_cdev, struct watchdog_device, cdev); ++ wd_data = container_of(inode->i_cdev, struct watchdog_core_data, ++ cdev); + + /* the watchdog is single open! */ +- if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status)) ++ if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status)) + return -EBUSY; + ++ wdd = wd_data->wdd; ++ + /* + * If the /dev/watchdog device is open, we don't want the module + * to be unloaded. + */ +- if (!try_module_get(wdd->ops->owner)) +- goto out; ++ if (!try_module_get(wdd->ops->owner)) { ++ err = -EBUSY; ++ goto out_clear; ++ } + + err = watchdog_start(wdd); + if (err < 0) + goto out_mod; + +- file->private_data = wdd; ++ file->private_data = wd_data; + +- if (wdd->ops->ref) +- wdd->ops->ref(wdd); ++ kref_get(&wd_data->kref); + + /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ + return nonseekable_open(inode, file); + + out_mod: +- module_put(wdd->ops->owner); +-out: +- clear_bit(WDOG_DEV_OPEN, &wdd->status); ++ module_put(wd_data->wdd->ops->owner); ++out_clear: ++ clear_bit(_WDOG_DEV_OPEN, &wd_data->status); + return err; + } + ++static void watchdog_core_data_release(struct kref *kref) ++{ ++ struct watchdog_core_data *wd_data; ++ ++ wd_data = container_of(kref, struct watchdog_core_data, kref); ++ ++ kfree(wd_data); ++} ++ + /* + * watchdog_release: release the watchdog device. + * @inode: inode of device +@@ -577,9 +581,16 @@ out: + + static int watchdog_release(struct inode *inode, struct file *file) + { +- struct watchdog_device *wdd = file->private_data; ++ struct watchdog_core_data *wd_data = file->private_data; ++ struct watchdog_device *wdd; + int err = -EBUSY; + ++ mutex_lock(&wd_data->lock); ++ ++ wdd = wd_data->wdd; ++ if (!wdd) ++ goto done; ++ + /* + * We only stop the watchdog if we received the magic character + * or if WDIOF_MAGICCLOSE is not set. If nowayout was set then +@@ -587,29 +598,24 @@ static int watchdog_release(struct inode + */ + if (!test_bit(WDOG_ACTIVE, &wdd->status)) + err = 0; +- else if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) || ++ else if (test_and_clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status) || + !(wdd->info->options & WDIOF_MAGICCLOSE)) + err = watchdog_stop(wdd); + + /* If the watchdog was not stopped, send a keepalive ping */ + if (err < 0) { +- mutex_lock(&wdd->lock); +- if (!test_bit(WDOG_UNREGISTERED, &wdd->status)) +- dev_crit(wdd->dev, "watchdog did not stop!\n"); +- mutex_unlock(&wdd->lock); ++ dev_crit(wdd->dev, "watchdog did not stop!\n"); + watchdog_ping(wdd); + } + +- /* Allow the owner module to be unloaded again */ +- module_put(wdd->ops->owner); +- + /* make sure that /dev/watchdog can be re-opened */ +- clear_bit(WDOG_DEV_OPEN, &wdd->status); +- +- /* Note wdd may be gone after this, do not use after this! */ +- if (wdd->ops->unref) +- wdd->ops->unref(wdd); ++ clear_bit(_WDOG_DEV_OPEN, &wd_data->status); + ++done: ++ mutex_unlock(&wd_data->lock); ++ /* Allow the owner module to be unloaded again */ ++ module_put(wd_data->cdev.owner); ++ kref_put(&wd_data->kref, watchdog_core_data_release); + return 0; + } + +@@ -639,10 +645,20 @@ static struct miscdevice watchdog_miscde + + static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) + { ++ struct watchdog_core_data *wd_data; + int err; + ++ wd_data = kzalloc(sizeof(struct watchdog_core_data), GFP_KERNEL); ++ if (!wd_data) ++ return -ENOMEM; ++ kref_init(&wd_data->kref); ++ mutex_init(&wd_data->lock); ++ ++ wd_data->wdd = wdd; ++ wdd->wd_data = wd_data; ++ + if (wdd->id == 0) { +- old_wdd = wdd; ++ old_wd_data = wd_data; + watchdog_miscdev.parent = wdd->parent; + err = misc_register(&watchdog_miscdev); + if (err != 0) { +@@ -651,23 +667,25 @@ static int watchdog_cdev_register(struct + if (err == -EBUSY) + pr_err("%s: a legacy watchdog module is probably present.\n", + wdd->info->identity); +- old_wdd = NULL; ++ old_wd_data = NULL; ++ kfree(wd_data); + return err; + } + } + + /* Fill in the data structures */ +- cdev_init(&wdd->cdev, &watchdog_fops); +- wdd->cdev.owner = wdd->ops->owner; ++ cdev_init(&wd_data->cdev, &watchdog_fops); ++ wd_data->cdev.owner = wdd->ops->owner; + + /* Add the device */ +- err = cdev_add(&wdd->cdev, devno, 1); ++ err = cdev_add(&wd_data->cdev, devno, 1); + if (err) { + pr_err("watchdog%d unable to add device %d:%d\n", + wdd->id, MAJOR(watchdog_devt), wdd->id); + if (wdd->id == 0) { + misc_deregister(&watchdog_miscdev); +- old_wdd = NULL; ++ old_wd_data = NULL; ++ kref_put(&wd_data->kref, watchdog_core_data_release); + } + } + return err; +@@ -683,15 +701,20 @@ static int watchdog_cdev_register(struct + + static void watchdog_cdev_unregister(struct watchdog_device *wdd) + { +- mutex_lock(&wdd->lock); +- set_bit(WDOG_UNREGISTERED, &wdd->status); +- mutex_unlock(&wdd->lock); ++ struct watchdog_core_data *wd_data = wdd->wd_data; + +- cdev_del(&wdd->cdev); ++ cdev_del(&wd_data->cdev); + if (wdd->id == 0) { + misc_deregister(&watchdog_miscdev); +- old_wdd = NULL; ++ old_wd_data = NULL; + } ++ ++ mutex_lock(&wd_data->lock); ++ wd_data->wdd = NULL; ++ wdd->wd_data = NULL; ++ mutex_unlock(&wd_data->lock); ++ ++ kref_put(&wd_data->kref, watchdog_core_data_release); + } + + static struct class watchdog_class = { +@@ -742,9 +765,9 @@ int watchdog_dev_register(struct watchdo + + void watchdog_dev_unregister(struct watchdog_device *wdd) + { +- watchdog_cdev_unregister(wdd); + device_destroy(&watchdog_class, wdd->dev->devt); + wdd->dev = NULL; ++ watchdog_cdev_unregister(wdd); + } + + /* +--- a/include/linux/watchdog.h ++++ b/include/linux/watchdog.h +@@ -17,6 +17,7 @@ + + struct watchdog_ops; + struct watchdog_device; ++struct watchdog_core_data; + + /** struct watchdog_ops - The watchdog-devices operations + * +@@ -28,8 +29,6 @@ struct watchdog_device; + * @set_timeout:The routine for setting the watchdog devices timeout value (in seconds). + * @get_timeleft:The routine that gets the time left before a reset (in seconds). + * @restart: The routine for restarting the machine. +- * @ref: The ref operation for dyn. allocated watchdog_device structs +- * @unref: The unref operation for dyn. allocated watchdog_device structs + * @ioctl: The routines that handles extra ioctl calls. + * + * The watchdog_ops structure contains a list of low-level operations +@@ -48,15 +47,14 @@ struct watchdog_ops { + int (*set_timeout)(struct watchdog_device *, unsigned int); + unsigned int (*get_timeleft)(struct watchdog_device *); + int (*restart)(struct watchdog_device *); +- void (*ref)(struct watchdog_device *); +- void (*unref)(struct watchdog_device *); ++ void (*ref)(struct watchdog_device *) __deprecated; ++ void (*unref)(struct watchdog_device *) __deprecated; + long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); + }; + + /** struct watchdog_device - The structure that defines a watchdog device + * + * @id: The watchdog's ID. (Allocated by watchdog_register_device) +- * @cdev: The watchdog's Character device. + * @dev: The device for our watchdog + * @parent: The parent bus device + * @info: Pointer to a watchdog_info structure. +@@ -67,8 +65,8 @@ struct watchdog_ops { + * @max_timeout:The watchdog devices maximum timeout value (in seconds). + * @reboot_nb: The notifier block to stop watchdog on reboot. + * @restart_nb: The notifier block to register a restart function. +- * @driver-data:Pointer to the drivers private data. +- * @lock: Lock for watchdog core internal use only. ++ * @driver_data:Pointer to the drivers private data. ++ * @wd_data: Pointer to watchdog core internal data. + * @status: Field that contains the devices internal status bits. + * @deferred: entry in wtd_deferred_reg_list which is used to + * register early initialized watchdogs. +@@ -84,7 +82,6 @@ struct watchdog_ops { + */ + struct watchdog_device { + int id; +- struct cdev cdev; + struct device *dev; + struct device *parent; + const struct watchdog_info *info; +@@ -96,15 +93,12 @@ struct watchdog_device { + struct notifier_block reboot_nb; + struct notifier_block restart_nb; + void *driver_data; +- struct mutex lock; ++ struct watchdog_core_data *wd_data; + unsigned long status; + /* Bit numbers for status flags */ + #define WDOG_ACTIVE 0 /* Is the watchdog running/active */ +-#define WDOG_DEV_OPEN 1 /* Opened via /dev/watchdog ? */ +-#define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */ +-#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */ +-#define WDOG_UNREGISTERED 4 /* Has the device been unregistered */ +-#define WDOG_STOP_ON_REBOOT 5 /* Should be stopped on reboot */ ++#define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */ ++#define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */ + struct list_head deferred; + }; + |