watchdog: Add multiple device support
authorAlan Cox <alan@linux.intel.com>
Thu, 10 May 2012 19:48:59 +0000 (21:48 +0200)
committerWim Van Sebroeck <wim@iguana.be>
Wed, 30 May 2012 05:54:25 +0000 (07:54 +0200)
We keep the old /dev/watchdog interface file for the first watchdog via
miscdev. This is basically a cut and paste of the relevant interface code
from the rtc driver layer tweaked for watchdog.

Revised to fix problems noted by Hans de Goede

Signed-off-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Documentation/watchdog/watchdog-kernel-api.txt
drivers/watchdog/watchdog_core.c
drivers/watchdog/watchdog_core.h
drivers/watchdog/watchdog_dev.c
include/linux/watchdog.h

index 25fe430..3c85fc7 100644 (file)
@@ -1,6 +1,6 @@
 The Linux WatchDog Timer Driver Core kernel API.
 ===============================================
-Last reviewed: 16-Mar-2012
+Last reviewed: 21-May-2012
 
 Wim Van Sebroeck <wim@iguana.be>
 
@@ -39,6 +39,8 @@ watchdog_device structure.
 The watchdog device structure looks like this:
 
 struct watchdog_device {
+       int id;
+       struct cdev cdev;
        const struct watchdog_info *info;
        const struct watchdog_ops *ops;
        unsigned int bootstatus;
@@ -50,6 +52,12 @@ struct watchdog_device {
 };
 
 It contains following fields:
+* id: set by watchdog_register_device, id 0 is special. It has both a
+  /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.
 * 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.
index 8598308..5f98793 100644 (file)
 #include <linux/kernel.h>      /* For printk/panic/... */
 #include <linux/watchdog.h>    /* For watchdog specific items */
 #include <linux/init.h>                /* For __init/__exit/... */
+#include <linux/idr.h>         /* For ida_* macros */
 
 #include "watchdog_core.h"     /* For watchdog_dev_register/... */
 
+static DEFINE_IDA(watchdog_ida);
+
 /**
  * watchdog_register_device() - register a watchdog device
  * @wdd: watchdog device
@@ -49,7 +52,7 @@
  */
 int watchdog_register_device(struct watchdog_device *wdd)
 {
-       int ret;
+       int ret, id;
 
        if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
                return -EINVAL;
@@ -74,11 +77,28 @@ int watchdog_register_device(struct watchdog_device *wdd)
         * corrupted in a later stage then we expect a kernel panic!
         */
 
-       /* We only support 1 watchdog device via the /dev/watchdog interface */
+       id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);
+       if (id < 0)
+               return id;
+       wdd->id = id;
+
        ret = watchdog_dev_register(wdd);
        if (ret) {
-               pr_err("error registering /dev/watchdog (err=%d)\n", ret);
-               return ret;
+               ida_simple_remove(&watchdog_ida, id);
+               if (!(id == 0 && ret == -EBUSY))
+                       return ret;
+
+               /* Retry in case a legacy watchdog module exists */
+               id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL);
+               if (id < 0)
+                       return id;
+               wdd->id = id;
+
+               ret = watchdog_dev_register(wdd);
+               if (ret) {
+                       ida_simple_remove(&watchdog_ida, id);
+                       return ret;
+               }
        }
 
        return 0;
@@ -102,9 +122,24 @@ void watchdog_unregister_device(struct watchdog_device *wdd)
        ret = watchdog_dev_unregister(wdd);
        if (ret)
                pr_err("error unregistering /dev/watchdog (err=%d)\n", ret);
+       ida_simple_remove(&watchdog_ida, wdd->id);
 }
 EXPORT_SYMBOL_GPL(watchdog_unregister_device);
 
+static int __init watchdog_init(void)
+{
+       return watchdog_dev_init();
+}
+
+static void __exit watchdog_exit(void)
+{
+       watchdog_dev_exit();
+       ida_destroy(&watchdog_ida);
+}
+
+subsys_initcall(watchdog_init);
+module_exit(watchdog_exit);
+
 MODULE_AUTHOR("Alan Cox <alan@lxorguk.ukuu.org.uk>");
 MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
 MODULE_DESCRIPTION("WatchDog Timer Driver Core");
index 80503f2..6c95141 100644 (file)
  *     This material is provided "AS-IS" and at no charge.
  */
 
+#define MAX_DOGS       32      /* Maximum number of watchdog devices */
+
 /*
  *     Functions/procedures to be called by the core
  */
 extern int watchdog_dev_register(struct watchdog_device *);
 extern int watchdog_dev_unregister(struct watchdog_device *);
+extern int __init watchdog_dev_init(void);
+extern void __exit watchdog_dev_exit(void);
index beaf9cb..3b22582 100644 (file)
 
 #include "watchdog_core.h"
 
-/* make sure we only register one /dev/watchdog device */
-static unsigned long watchdog_dev_busy;
+/* 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 *wdd;
+static struct watchdog_device *old_wdd;
 
 /*
  *     watchdog_ping: ping the watchdog.
@@ -138,6 +138,7 @@ static int watchdog_stop(struct watchdog_device *wddev)
 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;
        size_t i;
        char c;
 
@@ -177,6 +178,7 @@ static ssize_t watchdog_write(struct file *file, const char __user *data,
 static long watchdog_ioctl(struct file *file, unsigned int cmd,
                                                        unsigned long arg)
 {
+       struct watchdog_device *wdd = file->private_data;
        void __user *argp = (void __user *)arg;
        int __user *p = argp;
        unsigned int val;
@@ -249,11 +251,11 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
 }
 
 /*
- *     watchdog_open: open the /dev/watchdog device.
+ *     watchdog_open: open the /dev/watchdog* devices.
  *     @inode: inode of device
  *     @file: file handle to device
  *
- *     When the /dev/watchdog device gets opened, we start the watchdog.
+ *     When the /dev/watchdog* device gets opened, we start the watchdog.
  *     Watch out: the /dev/watchdog device is single open, so we make sure
  *     it can only be opened once.
  */
@@ -261,6 +263,13 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
 static int watchdog_open(struct inode *inode, struct file *file)
 {
        int err = -EBUSY;
+       struct watchdog_device *wdd;
+
+       /* Get the corresponding watchdog device */
+       if (imajor(inode) == MISC_MAJOR)
+               wdd = old_wdd;
+       else
+               wdd = container_of(inode->i_cdev, struct watchdog_device, cdev);
 
        /* the watchdog is single open! */
        if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status))
@@ -277,6 +286,8 @@ static int watchdog_open(struct inode *inode, struct file *file)
        if (err < 0)
                goto out_mod;
 
+       file->private_data = wdd;
+
        /* dev/watchdog is a virtual (and thus non-seekable) filesystem */
        return nonseekable_open(inode, file);
 
@@ -288,9 +299,9 @@ out:
 }
 
 /*
- *      watchdog_release: release the /dev/watchdog device.
- *      @inode: inode of device
- *      @file: file handle to device
+ *     watchdog_release: release the watchdog device.
+ *     @inode: inode of device
+ *     @file: file handle to device
  *
  *     This is the code for when /dev/watchdog gets closed. We will only
  *     stop the watchdog when we have received the magic char (and nowayout
@@ -299,6 +310,7 @@ out:
 
 static int watchdog_release(struct inode *inode, struct file *file)
 {
+       struct watchdog_device *wdd = file->private_data;
        int err = -EBUSY;
 
        /*
@@ -340,62 +352,87 @@ static struct miscdevice watchdog_miscdev = {
 };
 
 /*
- *     watchdog_dev_register:
+ *     watchdog_dev_register: register a watchdog device
  *     @watchdog: watchdog device
  *
- *     Register a watchdog device as /dev/watchdog. /dev/watchdog
- *     is actually a miscdevice and thus we set it up like that.
+ *     Register a watchdog device including handling the legacy
+ *     /dev/watchdog node. /dev/watchdog is actually a miscdevice and
+ *     thus we set it up like that.
  */
 
 int watchdog_dev_register(struct watchdog_device *watchdog)
 {
-       int err;
-
-       /* Only one device can register for /dev/watchdog */
-       if (test_and_set_bit(0, &watchdog_dev_busy)) {
-               pr_err("only one watchdog can use /dev/watchdog\n");
-               return -EBUSY;
+       int err, devno;
+
+       if (watchdog->id == 0) {
+               err = misc_register(&watchdog_miscdev);
+               if (err != 0) {
+                       pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
+                               watchdog->info->identity, WATCHDOG_MINOR, err);
+                       if (err == -EBUSY)
+                               pr_err("%s: a legacy watchdog module is probably present.\n",
+                                       watchdog->info->identity);
+                       return err;
+               }
+               old_wdd = watchdog;
        }
 
-       wdd = watchdog;
-
-       err = misc_register(&watchdog_miscdev);
-       if (err != 0) {
-               pr_err("%s: cannot register miscdev on minor=%d (err=%d)\n",
-                      watchdog->info->identity, WATCHDOG_MINOR, err);
-               goto out;
+       /* Fill in the data structures */
+       devno = MKDEV(MAJOR(watchdog_devt), watchdog->id);
+       cdev_init(&watchdog->cdev, &watchdog_fops);
+       watchdog->cdev.owner = watchdog->ops->owner;
+
+       /* Add the device */
+       err  = cdev_add(&watchdog->cdev, devno, 1);
+       if (err) {
+               pr_err("watchdog%d unable to add device %d:%d\n",
+                       watchdog->id,  MAJOR(watchdog_devt), watchdog->id);
+               if (watchdog->id == 0) {
+                       misc_deregister(&watchdog_miscdev);
+                       old_wdd = NULL;
+               }
        }
-
-       return 0;
-
-out:
-       wdd = NULL;
-       clear_bit(0, &watchdog_dev_busy);
        return err;
 }
 
 /*
- *     watchdog_dev_unregister:
+ *     watchdog_dev_unregister: unregister a watchdog device
  *     @watchdog: watchdog device
  *
- *     Deregister the /dev/watchdog device.
+ *     Unregister the watchdog and if needed the legacy /dev/watchdog device.
  */
 
 int watchdog_dev_unregister(struct watchdog_device *watchdog)
 {
-       /* Check that a watchdog device was registered in the past */
-       if (!test_bit(0, &watchdog_dev_busy) || !wdd)
-               return -ENODEV;
-
-       /* We can only unregister the watchdog device that was registered */
-       if (watchdog != wdd) {
-               pr_err("%s: watchdog was not registered as /dev/watchdog\n",
-                      watchdog->info->identity);
-               return -ENODEV;
+       cdev_del(&watchdog->cdev);
+       if (watchdog->id == 0) {
+               misc_deregister(&watchdog_miscdev);
+               old_wdd = NULL;
        }
-
-       misc_deregister(&watchdog_miscdev);
-       wdd = NULL;
-       clear_bit(0, &watchdog_dev_busy);
        return 0;
 }
+
+/*
+ *     watchdog_dev_init: init dev part of watchdog core
+ *
+ *     Allocate a range of chardev nodes to use for watchdog devices
+ */
+
+int __init watchdog_dev_init(void)
+{
+       int err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog");
+       if (err < 0)
+               pr_err("watchdog: unable to allocate char dev region\n");
+       return err;
+}
+
+/*
+ *     watchdog_dev_exit: exit dev part of watchdog core
+ *
+ *     Release the range of chardev nodes used for watchdog devices
+ */
+
+void __exit watchdog_dev_exit(void)
+{
+       unregister_chrdev_region(watchdog_devt, MAX_DOGS);
+}
index 1984ea6..508d563 100644 (file)
@@ -54,6 +54,8 @@ struct watchdog_info {
 #ifdef __KERNEL__
 
 #include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
 
 struct watchdog_ops;
 struct watchdog_device;
@@ -89,6 +91,8 @@ struct watchdog_ops {
 
 /** 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.
  * @info:      Pointer to a watchdog_info structure.
  * @ops:       Pointer to the list of watchdog operations.
  * @bootstatus:        Status of the watchdog device at boot.
@@ -105,6 +109,8 @@ struct watchdog_ops {
  * via the watchdog_set_drvdata and watchdog_get_drvdata helpers.
  */
 struct watchdog_device {
+       int id;
+       struct cdev cdev;
        const struct watchdog_info *info;
        const struct watchdog_ops *ops;
        unsigned int bootstatus;