* [PATCH 1/4] sch56xx-watchdog: Use watchdog core
@ 2012-05-22 9:40 Hans de Goede
2012-05-22 9:40 ` [PATCH 2/4] sch56xx-watchdog: Remove unnecessary checks for register changes Hans de Goede
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Hans de Goede @ 2012-05-22 9:40 UTC (permalink / raw)
To: linux-watchdog; +Cc: Wim Van Sebroeck, Hans de Goede
Note this patch depends on the "watchdog: Add multiple device support" patch
from Alan Cox.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
drivers/hwmon/sch5627.c | 2 +-
drivers/hwmon/sch5636.c | 2 +-
drivers/hwmon/sch56xx-common.c | 371 +++++++---------------------------------
drivers/hwmon/sch56xx-common.h | 2 +-
4 files changed, 61 insertions(+), 316 deletions(-)
diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c
index 8ec6dfb..8342275 100644
--- a/drivers/hwmon/sch5627.c
+++ b/drivers/hwmon/sch5627.c
@@ -579,7 +579,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
}
/* Note failing to register the watchdog is not a fatal error */
- data->watchdog = sch56xx_watchdog_register(data->addr,
+ data->watchdog = sch56xx_watchdog_register(&pdev->dev, data->addr,
(build_code << 24) | (build_id << 8) | hwmon_rev,
&data->update_lock, 1);
diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c
index 906d4ed..96a7e68 100644
--- a/drivers/hwmon/sch5636.c
+++ b/drivers/hwmon/sch5636.c
@@ -510,7 +510,7 @@ static int __devinit sch5636_probe(struct platform_device *pdev)
}
/* Note failing to register the watchdog is not a fatal error */
- data->watchdog = sch56xx_watchdog_register(data->addr,
+ data->watchdog = sch56xx_watchdog_register(&pdev->dev, data->addr,
(revision[0] << 8) | revision[1],
&data->update_lock, 0);
diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c
index ce52fc5..419a8e8f 100644
--- a/drivers/hwmon/sch56xx-common.c
+++ b/drivers/hwmon/sch56xx-common.c
@@ -66,15 +66,9 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
struct sch56xx_watchdog_data {
u16 addr;
- u32 revision;
struct mutex *io_lock;
- struct mutex watchdog_lock;
- struct list_head list; /* member of the watchdog_data_list */
- struct kref kref;
- struct miscdevice watchdog_miscdev;
- unsigned long watchdog_is_open;
- char watchdog_name[10]; /* must be unique to avoid sysfs conflict */
- char watchdog_expect_close;
+ struct watchdog_info wdinfo;
+ struct watchdog_device wddev;
u8 watchdog_preset;
u8 watchdog_control;
u8 watchdog_output_enable;
@@ -82,15 +76,6 @@ struct sch56xx_watchdog_data {
static struct platform_device *sch56xx_pdev;
-/*
- * Somewhat ugly :( global data pointer list with all sch56xx devices, so that
- * we can find our device data as when using misc_register there is no other
- * method to get to ones device data from the open fop.
- */
-static LIST_HEAD(watchdog_data_list);
-/* Note this lock not only protect list access, but also data.kref access */
-static DEFINE_MUTEX(watchdog_data_mutex);
-
/* Super I/O functions */
static inline int superio_inb(int base, int reg)
{
@@ -272,22 +257,13 @@ EXPORT_SYMBOL(sch56xx_read_virtual_reg12);
* Watchdog routines
*/
-/*
- * Release our data struct when the platform device has been released *and*
- * all references to our watchdog device are released.
- */
-static void sch56xx_watchdog_release_resources(struct kref *r)
-{
- struct sch56xx_watchdog_data *data =
- container_of(r, struct sch56xx_watchdog_data, kref);
- kfree(data);
-}
-
-static int watchdog_set_timeout(struct sch56xx_watchdog_data *data,
- int timeout)
+static int watchdog_set_timeout(struct watchdog_device *wddev,
+ unsigned int timeout)
{
- int ret, resolution;
+ struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
+ unsigned int resolution;
u8 control;
+ int ret;
/* 1 second or 60 second resolution? */
if (timeout <= 255)
@@ -298,12 +274,6 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data,
if (timeout < resolution || timeout > (resolution * 255))
return -EINVAL;
- mutex_lock(&data->watchdog_lock);
- if (!data->addr) {
- ret = -ENODEV;
- goto leave;
- }
-
if (resolution == 1)
control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC;
else
@@ -316,7 +286,7 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data,
control);
mutex_unlock(data->io_lock);
if (ret)
- goto leave;
+ return ret;
data->watchdog_control = control;
}
@@ -326,38 +296,17 @@ static int watchdog_set_timeout(struct sch56xx_watchdog_data *data,
* the watchdog countdown.
*/
data->watchdog_preset = DIV_ROUND_UP(timeout, resolution);
+ wddev->timeout = data->watchdog_preset * resolution;
- ret = data->watchdog_preset * resolution;
-leave:
- mutex_unlock(&data->watchdog_lock);
- return ret;
-}
-
-static int watchdog_get_timeout(struct sch56xx_watchdog_data *data)
-{
- int timeout;
-
- mutex_lock(&data->watchdog_lock);
- if (data->watchdog_control & SCH56XX_WDOG_TIME_BASE_SEC)
- timeout = data->watchdog_preset;
- else
- timeout = data->watchdog_preset * 60;
- mutex_unlock(&data->watchdog_lock);
-
- return timeout;
+ return 0;
}
-static int watchdog_start(struct sch56xx_watchdog_data *data)
+static int watchdog_start(struct watchdog_device *wddev)
{
+ struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
int ret;
u8 val;
- mutex_lock(&data->watchdog_lock);
- if (!data->addr) {
- ret = -ENODEV;
- goto leave_unlock_watchdog;
- }
-
/*
* The sch56xx's watchdog cannot really be started / stopped
* it is always running, but we can avoid the timer expiring
@@ -405,39 +354,29 @@ static int watchdog_start(struct sch56xx_watchdog_data *data)
leave:
mutex_unlock(data->io_lock);
-leave_unlock_watchdog:
- mutex_unlock(&data->watchdog_lock);
return ret;
}
-static int watchdog_trigger(struct sch56xx_watchdog_data *data)
+static int watchdog_trigger(struct watchdog_device *wddev)
{
+ struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
int ret;
- mutex_lock(&data->watchdog_lock);
- if (!data->addr) {
- ret = -ENODEV;
- goto leave;
- }
-
/* Reset the watchdog countdown counter */
mutex_lock(data->io_lock);
ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET,
data->watchdog_preset);
mutex_unlock(data->io_lock);
-leave:
- mutex_unlock(&data->watchdog_lock);
+
return ret;
}
-static int watchdog_stop_unlocked(struct sch56xx_watchdog_data *data)
+static int watchdog_stop(struct watchdog_device *wddev)
{
+ struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
int ret = 0;
u8 val;
- if (!data->addr)
- return -ENODEV;
-
if (data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) {
val = data->watchdog_output_enable &
~SCH56XX_WDOG_OUTPUT_ENABLE;
@@ -455,184 +394,19 @@ static int watchdog_stop_unlocked(struct sch56xx_watchdog_data *data)
return ret;
}
-static int watchdog_stop(struct sch56xx_watchdog_data *data)
-{
- int ret;
-
- mutex_lock(&data->watchdog_lock);
- ret = watchdog_stop_unlocked(data);
- mutex_unlock(&data->watchdog_lock);
-
- return ret;
-}
-
-static int watchdog_release(struct inode *inode, struct file *filp)
-{
- struct sch56xx_watchdog_data *data = filp->private_data;
-
- if (data->watchdog_expect_close) {
- watchdog_stop(data);
- data->watchdog_expect_close = 0;
- } else {
- watchdog_trigger(data);
- pr_crit("unexpected close, not stopping watchdog!\n");
- }
-
- clear_bit(0, &data->watchdog_is_open);
-
- mutex_lock(&watchdog_data_mutex);
- kref_put(&data->kref, sch56xx_watchdog_release_resources);
- mutex_unlock(&watchdog_data_mutex);
-
- return 0;
-}
-
-static int watchdog_open(struct inode *inode, struct file *filp)
-{
- struct sch56xx_watchdog_data *pos, *data = NULL;
- int ret, watchdog_is_open;
-
- /*
- * We get called from drivers/char/misc.c with misc_mtx hold, and we
- * call misc_register() from sch56xx_watchdog_probe() with
- * watchdog_data_mutex hold, as misc_register() takes the misc_mtx
- * lock, this is a possible deadlock, so we use mutex_trylock here.
- */
- if (!mutex_trylock(&watchdog_data_mutex))
- return -ERESTARTSYS;
- list_for_each_entry(pos, &watchdog_data_list, list) {
- if (pos->watchdog_miscdev.minor == iminor(inode)) {
- data = pos;
- break;
- }
- }
- /* Note we can never not have found data, so we don't check for this */
- watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open);
- if (!watchdog_is_open)
- kref_get(&data->kref);
- mutex_unlock(&watchdog_data_mutex);
-
- if (watchdog_is_open)
- return -EBUSY;
-
- filp->private_data = data;
-
- /* Start the watchdog */
- ret = watchdog_start(data);
- if (ret) {
- watchdog_release(inode, filp);
- return ret;
- }
-
- return nonseekable_open(inode, filp);
-}
-
-static ssize_t watchdog_write(struct file *filp, const char __user *buf,
- size_t count, loff_t *offset)
-{
- int ret;
- struct sch56xx_watchdog_data *data = filp->private_data;
-
- if (count) {
- if (!nowayout) {
- size_t i;
-
- /* Clear it in case it was set with a previous write */
- data->watchdog_expect_close = 0;
-
- for (i = 0; i != count; i++) {
- char c;
- if (get_user(c, buf + i))
- return -EFAULT;
- if (c == 'V')
- data->watchdog_expect_close = 1;
- }
- }
- ret = watchdog_trigger(data);
- if (ret)
- return ret;
- }
- return count;
-}
-
-static long watchdog_ioctl(struct file *filp, unsigned int cmd,
- unsigned long arg)
-{
- struct watchdog_info ident = {
- .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
- .identity = "sch56xx watchdog"
- };
- int i, ret = 0;
- struct sch56xx_watchdog_data *data = filp->private_data;
-
- switch (cmd) {
- case WDIOC_GETSUPPORT:
- ident.firmware_version = data->revision;
- if (!nowayout)
- ident.options |= WDIOF_MAGICCLOSE;
- if (copy_to_user((void __user *)arg, &ident, sizeof(ident)))
- ret = -EFAULT;
- break;
-
- case WDIOC_GETSTATUS:
- case WDIOC_GETBOOTSTATUS:
- ret = put_user(0, (int __user *)arg);
- break;
-
- case WDIOC_KEEPALIVE:
- ret = watchdog_trigger(data);
- break;
-
- case WDIOC_GETTIMEOUT:
- i = watchdog_get_timeout(data);
- ret = put_user(i, (int __user *)arg);
- break;
-
- case WDIOC_SETTIMEOUT:
- if (get_user(i, (int __user *)arg)) {
- ret = -EFAULT;
- break;
- }
- ret = watchdog_set_timeout(data, i);
- if (ret >= 0)
- ret = put_user(ret, (int __user *)arg);
- break;
-
- case WDIOC_SETOPTIONS:
- if (get_user(i, (int __user *)arg)) {
- ret = -EFAULT;
- break;
- }
-
- if (i & WDIOS_DISABLECARD)
- ret = watchdog_stop(data);
- else if (i & WDIOS_ENABLECARD)
- ret = watchdog_trigger(data);
- else
- ret = -EINVAL;
- break;
-
- default:
- ret = -ENOTTY;
- }
- return ret;
-}
-
-static const struct file_operations watchdog_fops = {
- .owner = THIS_MODULE,
- .llseek = no_llseek,
- .open = watchdog_open,
- .release = watchdog_release,
- .write = watchdog_write,
- .unlocked_ioctl = watchdog_ioctl,
+static const struct watchdog_ops watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = watchdog_start,
+ .stop = watchdog_stop,
+ .ping = watchdog_trigger,
+ .set_timeout = watchdog_set_timeout,
};
-struct sch56xx_watchdog_data *sch56xx_watchdog_register(
+struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent,
u16 addr, u32 revision, struct mutex *io_lock, int check_enabled)
{
struct sch56xx_watchdog_data *data;
- int i, err, control, output_enable;
- const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 };
+ int err, control, output_enable;
/* Cache the watchdog registers */
mutex_lock(io_lock);
@@ -656,82 +430,53 @@ struct sch56xx_watchdog_data *sch56xx_watchdog_register(
return NULL;
data->addr = addr;
- data->revision = revision;
data->io_lock = io_lock;
- data->watchdog_control = control;
- data->watchdog_output_enable = output_enable;
- mutex_init(&data->watchdog_lock);
- INIT_LIST_HEAD(&data->list);
- kref_init(&data->kref);
-
- err = watchdog_set_timeout(data, 60);
- if (err < 0)
- goto error;
- /*
- * We take the data_mutex lock early so that watchdog_open() cannot
- * run when misc_register() has completed, but we've not yet added
- * our data to the watchdog_data_list.
- */
- mutex_lock(&watchdog_data_mutex);
- for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) {
- /* Register our watchdog part */
- snprintf(data->watchdog_name, sizeof(data->watchdog_name),
- "watchdog%c", (i == 0) ? '\0' : ('0' + i));
- data->watchdog_miscdev.name = data->watchdog_name;
- data->watchdog_miscdev.fops = &watchdog_fops;
- data->watchdog_miscdev.minor = watchdog_minors[i];
- err = misc_register(&data->watchdog_miscdev);
- if (err == -EBUSY)
- continue;
- if (err)
- break;
+ strlcpy(data->wdinfo.identity, "sch56xx watchdog",
+ sizeof(data->wdinfo.identity));
+ data->wdinfo.firmware_version = revision;
+ data->wdinfo.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT;
+ if (!nowayout)
+ data->wdinfo.options |= WDIOF_MAGICCLOSE;
+
+ data->wddev.info = &data->wdinfo;
+ data->wddev.ops = &watchdog_ops;
+ data->wddev.parent = parent;
+ data->wddev.timeout = 60;
+ data->wddev.min_timeout = 1;
+ data->wddev.max_timeout = 255 * 60;
+ if (nowayout)
+ data->wddev.status |= WDOG_NO_WAY_OUT;
+ if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)
+ data->wddev.status |= WDOG_ACTIVE;
+
+ /* Since the watchdog uses a downcounter there is no register to read
+ the BIOS set timeout from (if any was set at all) ->
+ Choose a preset which will give us a 1 minute timeout */
+ if (control & SCH56XX_WDOG_TIME_BASE_SEC)
+ data->watchdog_preset = 60; /* seconds */
+ else
+ data->watchdog_preset = 1; /* minute */
- list_add(&data->list, &watchdog_data_list);
- pr_info("Registered /dev/%s chardev major 10, minor: %d\n",
- data->watchdog_name, watchdog_minors[i]);
- break;
- }
- mutex_unlock(&watchdog_data_mutex);
+ data->watchdog_control = control;
+ data->watchdog_output_enable = output_enable;
+ watchdog_set_drvdata(&data->wddev, data);
+ err = watchdog_register_device(&data->wddev);
if (err) {
pr_err("Registering watchdog chardev: %d\n", err);
- goto error;
- }
- if (i == ARRAY_SIZE(watchdog_minors)) {
- pr_warn("Couldn't register watchdog (no free minor)\n");
- goto error;
+ kfree(data);
+ return NULL;
}
return data;
-
-error:
- kfree(data);
- return NULL;
}
EXPORT_SYMBOL(sch56xx_watchdog_register);
void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data)
{
- mutex_lock(&watchdog_data_mutex);
- misc_deregister(&data->watchdog_miscdev);
- list_del(&data->list);
- mutex_unlock(&watchdog_data_mutex);
-
- mutex_lock(&data->watchdog_lock);
- if (data->watchdog_is_open) {
- pr_warn("platform device unregistered with watchdog "
- "open! Stopping watchdog.\n");
- watchdog_stop_unlocked(data);
- }
- /* Tell the wdog start/stop/trigger functions our dev is gone */
- data->addr = 0;
- data->io_lock = NULL;
- mutex_unlock(&data->watchdog_lock);
-
- mutex_lock(&watchdog_data_mutex);
- kref_put(&data->kref, sch56xx_watchdog_release_resources);
- mutex_unlock(&watchdog_data_mutex);
+ watchdog_unregister_device(&data->wddev);
+ kfree(data);
}
EXPORT_SYMBOL(sch56xx_watchdog_unregister);
diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h
index 7475086..704ea2c 100644
--- a/drivers/hwmon/sch56xx-common.h
+++ b/drivers/hwmon/sch56xx-common.h
@@ -27,6 +27,6 @@ int sch56xx_read_virtual_reg16(u16 addr, u16 reg);
int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
int high_nibble);
-struct sch56xx_watchdog_data *sch56xx_watchdog_register(
+struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent,
u16 addr, u32 revision, struct mutex *io_lock, int check_enabled);
void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data);
--
1.7.10
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/4] sch56xx-watchdog: Remove unnecessary checks for register changes
2012-05-22 9:40 [PATCH 1/4] sch56xx-watchdog: Use watchdog core Hans de Goede
@ 2012-05-22 9:40 ` Hans de Goede
2012-05-22 9:40 ` [PATCH 3/4] watchdog_dev: Add support for dynamically allocated watchdog_device structs Hans de Goede
2012-05-22 9:40 ` [PATCH 4/4] sch56xx-common: Add proper ref-counting of watchdog data Hans de Goede
2 siblings, 0 replies; 4+ messages in thread
From: Hans de Goede @ 2012-05-22 9:40 UTC (permalink / raw)
To: linux-watchdog; +Cc: Wim Van Sebroeck, Hans de Goede
Since the watchdog core keeps track of the watchdog's active state, start/stop
will never get called when no changes are necessary. So we can remove the
check for the output_enable register changing before writing it (which is
an expensive operation).
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
drivers/hwmon/sch56xx-common.c | 41 ++++++++++++++++------------------------
1 file changed, 16 insertions(+), 25 deletions(-)
diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c
index 419a8e8f..35846cb 100644
--- a/drivers/hwmon/sch56xx-common.c
+++ b/drivers/hwmon/sch56xx-common.c
@@ -334,18 +334,14 @@ static int watchdog_start(struct watchdog_device *wddev)
if (ret)
goto leave;
- /* 2. Enable output (if not already enabled) */
- if (!(data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) {
- val = data->watchdog_output_enable |
- SCH56XX_WDOG_OUTPUT_ENABLE;
- ret = sch56xx_write_virtual_reg(data->addr,
- SCH56XX_REG_WDOG_OUTPUT_ENABLE,
- val);
- if (ret)
- goto leave;
+ /* 2. Enable output */
+ val = data->watchdog_output_enable | SCH56XX_WDOG_OUTPUT_ENABLE;
+ ret = sch56xx_write_virtual_reg(data->addr,
+ SCH56XX_REG_WDOG_OUTPUT_ENABLE, val);
+ if (ret)
+ goto leave;
- data->watchdog_output_enable = val;
- }
+ data->watchdog_output_enable = val;
/* 3. Clear the watchdog event bit if set */
val = inb(data->addr + 9);
@@ -377,21 +373,16 @@ static int watchdog_stop(struct watchdog_device *wddev)
int ret = 0;
u8 val;
- if (data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) {
- val = data->watchdog_output_enable &
- ~SCH56XX_WDOG_OUTPUT_ENABLE;
- mutex_lock(data->io_lock);
- ret = sch56xx_write_virtual_reg(data->addr,
- SCH56XX_REG_WDOG_OUTPUT_ENABLE,
- val);
- mutex_unlock(data->io_lock);
- if (ret)
- return ret;
-
- data->watchdog_output_enable = val;
- }
+ val = data->watchdog_output_enable & ~SCH56XX_WDOG_OUTPUT_ENABLE;
+ mutex_lock(data->io_lock);
+ ret = sch56xx_write_virtual_reg(data->addr,
+ SCH56XX_REG_WDOG_OUTPUT_ENABLE, val);
+ mutex_unlock(data->io_lock);
+ if (ret)
+ return ret;
- return ret;
+ data->watchdog_output_enable = val;
+ return 0;
}
static const struct watchdog_ops watchdog_ops = {
--
1.7.10
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 3/4] watchdog_dev: Add support for dynamically allocated watchdog_device structs
2012-05-22 9:40 [PATCH 1/4] sch56xx-watchdog: Use watchdog core Hans de Goede
2012-05-22 9:40 ` [PATCH 2/4] sch56xx-watchdog: Remove unnecessary checks for register changes Hans de Goede
@ 2012-05-22 9:40 ` Hans de Goede
2012-05-22 9:40 ` [PATCH 4/4] sch56xx-common: Add proper ref-counting of watchdog data Hans de Goede
2 siblings, 0 replies; 4+ messages in thread
From: Hans de Goede @ 2012-05-22 9:40 UTC (permalink / raw)
To: linux-watchdog; +Cc: Wim Van Sebroeck, Hans de Goede
If a driver's watchdog_device struct is part of a dynamically allocated
struct (which it often will be), merely locking the module is not enough,
even with a drivers module locked, the driver can be unbound from the device,
examples:
1) The root user can unbind it through sysfd
2) The i2c bus master driver being unloaded for an i2c watchdog
I will gladly admit that these are corner cases, but we still need to handle
them correctly.
The fix for this consists of 2 parts:
1) Add ref / unref operations, so that the driver can refcount the struct
holding the watchdog_device struct and delay freeing it until any
open filehandles referring to it are closed
2) Most driver operations will do IO on the device and the driver should not
do any IO on the device after it has been unbound. Rather then letting each
driver deal with this internally, it is better to ensure at the watchdog
core level that no operations (other then unref) will get called after
the driver has called watchdog_unregister_device(). This actually is the
bulk of this patch.
This patch also fixes some potential multithreading issues, despite only
allowing one process to open the /dev/watchdog device, we can still get
called multiple times at the same time, since a program could be using thread,
or could share the fd after a fork.
This causes 2 potential problems:
1) watchdog_start / open do an unlocked test_n_set / test_n_clear,
if these 2 race, the watchdog could be stopped while the active
bit indicates it is running or visa versa.
2) Most watchdog_dev drivers probably assume that only one
watchdog-op will get called at a time, this is not necessary
true atm.
Both of these issues are also addressed by this patch.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Documentation/watchdog/watchdog-kernel-api.txt | 24 ++++
drivers/watchdog/watchdog_dev.c | 172 ++++++++++++++++++++----
include/linux/watchdog.h | 8 ++
3 files changed, 180 insertions(+), 24 deletions(-)
diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt
index 624f547..a23cfca 100644
--- a/Documentation/watchdog/watchdog-kernel-api.txt
+++ b/Documentation/watchdog/watchdog-kernel-api.txt
@@ -45,6 +45,7 @@ struct watchdog_device {
struct device *parent;
const struct watchdog_info *info;
const struct watchdog_ops *ops;
+ struct mutex lock;
unsigned int bootstatus;
unsigned int timeout;
unsigned int min_timeout;
@@ -66,6 +67,7 @@ It contains following fields:
* info: a pointer to a watchdog_info structure. This structure gives some
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.
+* lock: Mutex for WatchDog Timer Driver Core internal use only.
* timeout: the watchdog timer's timeout value (in seconds).
* min_timeout: the watchdog timer's minimum timeout value (in seconds).
* max_timeout: the watchdog timer's maximum timeout value (in seconds).
@@ -87,6 +89,8 @@ struct watchdog_ops {
int (*start)(struct watchdog_device *);
int (*stop)(struct watchdog_device *);
/* optional operations */
+ void (*ref)(struct watchdog_device *);
+ void (*unref)(struct watchdog_device *);
int (*ping)(struct watchdog_device *);
unsigned int (*status)(struct watchdog_device *);
int (*set_timeout)(struct watchdog_device *, unsigned int);
@@ -98,6 +102,21 @@ It is important that you first define the module owner of the watchdog timer
driver's operations. This module owner will be used to lock the module when
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
@@ -157,6 +176,11 @@ bit-operations. The status bits that are defined are:
(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:
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index ba90769..f1e7b92 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -61,13 +61,24 @@ static struct watchdog_device *old_wdd;
static int watchdog_ping(struct watchdog_device *wddev)
{
+ int err = 0;
+
+ mutex_lock(&wddev->lock);
+
+ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
+ err = -ENODEV;
+ goto leave;
+ }
+
if (test_bit(WDOG_ACTIVE, &wddev->status)) {
if (wddev->ops->ping)
- return wddev->ops->ping(wddev); /* ping the watchdog */
+ err = wddev->ops->ping(wddev); /* ping the watchdog */
else
- return wddev->ops->start(wddev); /* restart watchdog */
+ err = wddev->ops->start(wddev); /* restart watchdog */
}
- return 0;
+leave:
+ mutex_unlock(&wddev->lock);
+ return err;
}
/*
@@ -81,16 +92,25 @@ static int watchdog_ping(struct watchdog_device *wddev)
static int watchdog_start(struct watchdog_device *wddev)
{
- int err;
+ int err = 0;
+
+ mutex_lock(&wddev->lock);
+
+ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
+ err = -ENODEV;
+ goto leave;
+ }
if (!test_bit(WDOG_ACTIVE, &wddev->status)) {
err = wddev->ops->start(wddev);
if (err < 0)
- return err;
+ goto leave;
set_bit(WDOG_ACTIVE, &wddev->status);
}
- return 0;
+leave:
+ mutex_unlock(&wddev->lock);
+ return err;
}
/*
@@ -105,21 +125,116 @@ static int watchdog_start(struct watchdog_device *wddev)
static int watchdog_stop(struct watchdog_device *wddev)
{
- int err = -EBUSY;
+ int err = 0;
+
+ mutex_lock(&wddev->lock);
+
+ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
+ err = -ENODEV;
+ goto leave;
+ }
if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) {
dev_info(wddev->dev, "nowayout prevents watchdog being stopped!\n");
- return err;
+ err = -EBUSY;
+ goto leave;
}
if (test_bit(WDOG_ACTIVE, &wddev->status)) {
err = wddev->ops->stop(wddev);
if (err < 0)
- return err;
+ goto leave;
clear_bit(WDOG_ACTIVE, &wddev->status);
}
- return 0;
+leave:
+ mutex_unlock(&wddev->lock);
+ return err;
+}
+
+/*
+ * watchdog_ioctl_op: call the watchdog drivers ioctl op if defined
+ * @wddev: the watchdog device to do the ioctl on
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ */
+
+static int watchdog_ioctl_op(struct watchdog_device *wddev, unsigned int cmd,
+ unsigned long arg)
+{
+ int err;
+
+ if (!wddev->ops->ioctl)
+ return -ENOIOCTLCMD;
+
+ mutex_lock(&wddev->lock);
+ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
+ err = -ENODEV;
+ goto leave;
+ }
+
+ err = wddev->ops->ioctl(wddev, cmd, arg);
+leave:
+ mutex_unlock(&wddev->lock);
+ return err;
+}
+
+/*
+ * watchdog_get_status: get the watchdog status
+ * @wddev: the watchdog device to get the status from
+ */
+
+static int watchdog_get_status(struct watchdog_device *wddev,
+ unsigned int *status)
+{
+ int err = 0;
+
+ *status = 0;
+ if (!wddev->ops->status)
+ return 0;
+
+ mutex_lock(&wddev->lock);
+ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
+ err = -ENODEV;
+ goto leave;
+ }
+
+ *status = wddev->ops->status(wddev);
+leave:
+ mutex_unlock(&wddev->lock);
+ return err;
+}
+
+/*
+ * watchdog_set_timeout: set the watchdog timer timeout
+ * @wddev: the watchdog device to set the timeout for
+ * @timeout: timeout to set in seconds
+ * @arg: argument pointer
+ */
+
+static int watchdog_set_timeout(struct watchdog_device *wddev,
+ unsigned int timeout)
+{
+ int err;
+
+ if ((wddev->ops->set_timeout == NULL) ||
+ !(wddev->info->options & WDIOF_SETTIMEOUT))
+ return -EOPNOTSUPP;
+
+ if ((wddev->max_timeout != 0) &&
+ (timeout < wddev->min_timeout || timeout > wddev->max_timeout))
+ return -EINVAL;
+
+ mutex_lock(&wddev->lock);
+ if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
+ err = -ENODEV;
+ goto leave;
+ }
+
+ err = wddev->ops->set_timeout(wddev, timeout);
+leave:
+ mutex_unlock(&wddev->lock);
+ return err;
}
/*
@@ -183,18 +298,18 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
unsigned int val;
int err;
- if (wdd->ops->ioctl) {
- err = wdd->ops->ioctl(wdd, cmd, arg);
- if (err != -ENOIOCTLCMD)
- return err;
- }
+ err = watchdog_ioctl_op(wdd, cmd, arg);
+ if (err != -ENOIOCTLCMD)
+ return err;
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, wdd->info,
sizeof(struct watchdog_info)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
- val = wdd->ops->status ? wdd->ops->status(wdd) : 0;
+ err = watchdog_get_status(wdd, &val);
+ if (err)
+ return err;
return put_user(val, p);
case WDIOC_GETBOOTSTATUS:
return put_user(wdd->bootstatus, p);
@@ -218,15 +333,9 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
watchdog_ping(wdd);
return 0;
case WDIOC_SETTIMEOUT:
- if ((wdd->ops->set_timeout == NULL) ||
- !(wdd->info->options & WDIOF_SETTIMEOUT))
- return -EOPNOTSUPP;
if (get_user(val, p))
return -EFAULT;
- if ((wdd->max_timeout != 0) &&
- (val < wdd->min_timeout || val > wdd->max_timeout))
- return -EINVAL;
- err = wdd->ops->set_timeout(wdd, val);
+ err = watchdog_set_timeout(wdd, val);
if (err < 0)
return err;
/* If the watchdog is active then we send a keepalive ping
@@ -286,6 +395,8 @@ static int watchdog_open(struct inode *inode, struct file *file)
goto out_mod;
file->private_data = wdd;
+ if (wdd->ops->ref)
+ wdd->ops->ref(wdd);
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */
return nonseekable_open(inode, file);
@@ -323,7 +434,10 @@ static int watchdog_release(struct inode *inode, struct file *file)
/* If the watchdog was not stopped, send a keepalive ping */
if (err < 0) {
- dev_crit(wdd->dev, "watchdog did not stop!\n");
+ mutex_lock(&wdd->lock);
+ if (!test_bit(WDOG_UNREGISTERED, &wdd->status))
+ dev_crit(wdd->dev, "watchdog did not stop!\n");
+ mutex_unlock(&wdd->lock);
watchdog_ping(wdd);
}
@@ -333,6 +447,10 @@ static int watchdog_release(struct inode *inode, struct file *file)
/* 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);
+
return 0;
}
@@ -363,6 +481,8 @@ int watchdog_dev_register(struct watchdog_device *watchdog)
{
int err, devno;
+ mutex_init(&watchdog->lock);
+
if (watchdog->id == 0) {
watchdog_miscdev.parent = watchdog->parent;
err = misc_register(&watchdog_miscdev);
@@ -404,6 +524,10 @@ int watchdog_dev_register(struct watchdog_device *watchdog)
int watchdog_dev_unregister(struct watchdog_device *watchdog)
{
+ mutex_lock(&watchdog->lock);
+ set_bit(WDOG_UNREGISTERED, &watchdog->status);
+ mutex_unlock(&watchdog->lock);
+
cdev_del(&watchdog->cdev);
if (watchdog->id == 0) {
misc_deregister(&watchdog_miscdev);
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
index 779849d..6672509 100644
--- a/include/linux/watchdog.h
+++ b/include/linux/watchdog.h
@@ -84,6 +84,8 @@ struct watchdog_ops {
int (*start)(struct watchdog_device *);
int (*stop)(struct watchdog_device *);
/* optional operations */
+ void (*ref)(struct watchdog_device *);
+ void (*unref)(struct watchdog_device *);
int (*ping)(struct watchdog_device *);
unsigned int (*status)(struct watchdog_device *);
int (*set_timeout)(struct watchdog_device *, unsigned int);
@@ -99,6 +101,7 @@ struct watchdog_ops {
* @parent: The parent bus device
* @info: Pointer to a watchdog_info structure.
* @ops: Pointer to the list of watchdog operations.
+ * @lock: Lock for watchdog core internal use only.
* @bootstatus: Status of the watchdog device at boot.
* @timeout: The watchdog devices timeout value.
* @min_timeout:The watchdog devices minimum timeout value.
@@ -111,6 +114,9 @@ struct watchdog_ops {
*
* The driver-data field may not be accessed directly. It must be accessed
* via the watchdog_set_drvdata and watchdog_get_drvdata helpers.
+ *
+ * The lock field is for watchdog core internal use only and should not be
+ * touched.
*/
struct watchdog_device {
int id;
@@ -119,6 +125,7 @@ struct watchdog_device {
struct device *parent;
const struct watchdog_info *info;
const struct watchdog_ops *ops;
+ struct mutex lock;
unsigned int bootstatus;
unsigned int timeout;
unsigned int min_timeout;
@@ -130,6 +137,7 @@ struct watchdog_device {
#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 /* The device has been unregistered */
};
#ifdef CONFIG_WATCHDOG_NOWAYOUT
--
1.7.10
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 4/4] sch56xx-common: Add proper ref-counting of watchdog data
2012-05-22 9:40 [PATCH 1/4] sch56xx-watchdog: Use watchdog core Hans de Goede
2012-05-22 9:40 ` [PATCH 2/4] sch56xx-watchdog: Remove unnecessary checks for register changes Hans de Goede
2012-05-22 9:40 ` [PATCH 3/4] watchdog_dev: Add support for dynamically allocated watchdog_device structs Hans de Goede
@ 2012-05-22 9:40 ` Hans de Goede
2 siblings, 0 replies; 4+ messages in thread
From: Hans de Goede @ 2012-05-22 9:40 UTC (permalink / raw)
To: linux-watchdog; +Cc: Wim Van Sebroeck, Hans de Goede
This fixes referencing free-ed memory in the corner case where /dev/watchdog
is open when the platform driver gets unbound from the platform device.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
drivers/hwmon/sch56xx-common.c | 30 +++++++++++++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c
index 35846cb..839087c 100644
--- a/drivers/hwmon/sch56xx-common.c
+++ b/drivers/hwmon/sch56xx-common.c
@@ -67,6 +67,7 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
struct sch56xx_watchdog_data {
u16 addr;
struct mutex *io_lock;
+ struct kref kref;
struct watchdog_info wdinfo;
struct watchdog_device wddev;
u8 watchdog_preset;
@@ -257,6 +258,15 @@ EXPORT_SYMBOL(sch56xx_read_virtual_reg12);
* Watchdog routines
*/
+/* Release our data struct when we're unregistered *and*
+ all references to our watchdog device are released */
+static void watchdog_release_resources(struct kref *r)
+{
+ struct sch56xx_watchdog_data *data =
+ container_of(r, struct sch56xx_watchdog_data, kref);
+ kfree(data);
+}
+
static int watchdog_set_timeout(struct watchdog_device *wddev,
unsigned int timeout)
{
@@ -385,12 +395,28 @@ static int watchdog_stop(struct watchdog_device *wddev)
return 0;
}
+static void watchdog_ref(struct watchdog_device *wddev)
+{
+ struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
+
+ kref_get(&data->kref);
+}
+
+static void watchdog_unref(struct watchdog_device *wddev)
+{
+ struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
+
+ kref_put(&data->kref, watchdog_release_resources);
+}
+
static const struct watchdog_ops watchdog_ops = {
.owner = THIS_MODULE,
.start = watchdog_start,
.stop = watchdog_stop,
.ping = watchdog_trigger,
.set_timeout = watchdog_set_timeout,
+ .ref = watchdog_ref,
+ .unref = watchdog_unref,
};
struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent,
@@ -422,6 +448,7 @@ struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent,
data->addr = addr;
data->io_lock = io_lock;
+ kref_init(&data->kref);
strlcpy(data->wdinfo.identity, "sch56xx watchdog",
sizeof(data->wdinfo.identity));
@@ -467,7 +494,8 @@ EXPORT_SYMBOL(sch56xx_watchdog_register);
void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data)
{
watchdog_unregister_device(&data->wddev);
- kfree(data);
+ kref_put(&data->kref, watchdog_release_resources);
+ /* Don't touch data after this it may have been free-ed! */
}
EXPORT_SYMBOL(sch56xx_watchdog_unregister);
--
1.7.10
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2012-05-22 9:40 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-05-22 9:40 [PATCH 1/4] sch56xx-watchdog: Use watchdog core Hans de Goede
2012-05-22 9:40 ` [PATCH 2/4] sch56xx-watchdog: Remove unnecessary checks for register changes Hans de Goede
2012-05-22 9:40 ` [PATCH 3/4] watchdog_dev: Add support for dynamically allocated watchdog_device structs Hans de Goede
2012-05-22 9:40 ` [PATCH 4/4] sch56xx-common: Add proper ref-counting of watchdog data Hans de Goede
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).