dect
/
linux-2.6
Archived
13
0
Fork 0

watchdog: Add multiple device support

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>
This commit is contained in:
Alan Cox 2012-05-10 21:48:59 +02:00 committed by Wim Van Sebroeck
parent fb5f665816
commit 45f5fed30a
5 changed files with 138 additions and 48 deletions

View File

@ -1,6 +1,6 @@
The Linux WatchDog Timer Driver Core kernel API. The Linux WatchDog Timer Driver Core kernel API.
=============================================== ===============================================
Last reviewed: 16-Mar-2012 Last reviewed: 21-May-2012
Wim Van Sebroeck <wim@iguana.be> Wim Van Sebroeck <wim@iguana.be>
@ -39,6 +39,8 @@ watchdog_device structure.
The watchdog device structure looks like this: The watchdog device structure looks like this:
struct watchdog_device { struct watchdog_device {
int id;
struct cdev cdev;
const struct watchdog_info *info; const struct watchdog_info *info;
const struct watchdog_ops *ops; const struct watchdog_ops *ops;
unsigned int bootstatus; unsigned int bootstatus;
@ -50,6 +52,12 @@ struct watchdog_device {
}; };
It contains following fields: 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 * 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.

View File

@ -34,9 +34,12 @@
#include <linux/kernel.h> /* For printk/panic/... */ #include <linux/kernel.h> /* For printk/panic/... */
#include <linux/watchdog.h> /* For watchdog specific items */ #include <linux/watchdog.h> /* For watchdog specific items */
#include <linux/init.h> /* For __init/__exit/... */ #include <linux/init.h> /* For __init/__exit/... */
#include <linux/idr.h> /* For ida_* macros */
#include "watchdog_core.h" /* For watchdog_dev_register/... */ #include "watchdog_core.h" /* For watchdog_dev_register/... */
static DEFINE_IDA(watchdog_ida);
/** /**
* watchdog_register_device() - register a watchdog device * watchdog_register_device() - register a watchdog device
* @wdd: watchdog device * @wdd: watchdog device
@ -49,7 +52,7 @@
*/ */
int watchdog_register_device(struct watchdog_device *wdd) int watchdog_register_device(struct watchdog_device *wdd)
{ {
int ret; int ret, id;
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL) if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
return -EINVAL; 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! * 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); ret = watchdog_dev_register(wdd);
if (ret) { if (ret) {
pr_err("error registering /dev/watchdog (err=%d)\n", ret); ida_simple_remove(&watchdog_ida, id);
return ret; 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; return 0;
@ -102,9 +122,24 @@ void watchdog_unregister_device(struct watchdog_device *wdd)
ret = watchdog_dev_unregister(wdd); ret = watchdog_dev_unregister(wdd);
if (ret) if (ret)
pr_err("error unregistering /dev/watchdog (err=%d)\n", ret); pr_err("error unregistering /dev/watchdog (err=%d)\n", ret);
ida_simple_remove(&watchdog_ida, wdd->id);
} }
EXPORT_SYMBOL_GPL(watchdog_unregister_device); 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("Alan Cox <alan@lxorguk.ukuu.org.uk>");
MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
MODULE_DESCRIPTION("WatchDog Timer Driver Core"); MODULE_DESCRIPTION("WatchDog Timer Driver Core");

View File

@ -26,8 +26,12 @@
* This material is provided "AS-IS" and at no charge. * 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 * Functions/procedures to be called by the core
*/ */
extern int watchdog_dev_register(struct watchdog_device *); extern int watchdog_dev_register(struct watchdog_device *);
extern int watchdog_dev_unregister(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);

View File

@ -44,10 +44,10 @@
#include "watchdog_core.h" #include "watchdog_core.h"
/* make sure we only register one /dev/watchdog device */ /* the dev_t structure to store the dynamically allocated watchdog devices */
static unsigned long watchdog_dev_busy; static dev_t watchdog_devt;
/* the watchdog device behind /dev/watchdog */ /* the watchdog device behind /dev/watchdog */
static struct watchdog_device *wdd; static struct watchdog_device *old_wdd;
/* /*
* watchdog_ping: ping the watchdog. * 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, static ssize_t watchdog_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos) size_t len, loff_t *ppos)
{ {
struct watchdog_device *wdd = file->private_data;
size_t i; size_t i;
char c; 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, static long watchdog_ioctl(struct file *file, unsigned int cmd,
unsigned long arg) unsigned long arg)
{ {
struct watchdog_device *wdd = file->private_data;
void __user *argp = (void __user *)arg; void __user *argp = (void __user *)arg;
int __user *p = argp; int __user *p = argp;
unsigned int val; 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 * @inode: inode of device
* @file: file handle to 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 * Watch out: the /dev/watchdog device is single open, so we make sure
* it can only be opened once. * 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) static int watchdog_open(struct inode *inode, struct file *file)
{ {
int err = -EBUSY; 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! */ /* the watchdog is single open! */
if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status)) 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) if (err < 0)
goto out_mod; goto out_mod;
file->private_data = wdd;
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */ /* dev/watchdog is a virtual (and thus non-seekable) filesystem */
return nonseekable_open(inode, file); return nonseekable_open(inode, file);
@ -288,9 +299,9 @@ out:
} }
/* /*
* watchdog_release: release the /dev/watchdog device. * watchdog_release: release the watchdog device.
* @inode: inode of device * @inode: inode of device
* @file: file handle to device * @file: file handle to device
* *
* This is the code for when /dev/watchdog gets closed. We will only * 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 * 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) static int watchdog_release(struct inode *inode, struct file *file)
{ {
struct watchdog_device *wdd = file->private_data;
int err = -EBUSY; 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 * @watchdog: watchdog device
* *
* Register a watchdog device as /dev/watchdog. /dev/watchdog * Register a watchdog device including handling the legacy
* is actually a miscdevice and thus we set it up like that. * /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 watchdog_dev_register(struct watchdog_device *watchdog)
{ {
int err; int err, devno;
/* Only one device can register for /dev/watchdog */ if (watchdog->id == 0) {
if (test_and_set_bit(0, &watchdog_dev_busy)) { err = misc_register(&watchdog_miscdev);
pr_err("only one watchdog can use /dev/watchdog\n"); if (err != 0) {
return -EBUSY; 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; /* Fill in the data structures */
devno = MKDEV(MAJOR(watchdog_devt), watchdog->id);
cdev_init(&watchdog->cdev, &watchdog_fops);
watchdog->cdev.owner = watchdog->ops->owner;
err = misc_register(&watchdog_miscdev); /* Add the device */
if (err != 0) { err = cdev_add(&watchdog->cdev, devno, 1);
pr_err("%s: cannot register miscdev on minor=%d (err=%d)\n", if (err) {
watchdog->info->identity, WATCHDOG_MINOR, err); pr_err("watchdog%d unable to add device %d:%d\n",
goto out; 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; return err;
} }
/* /*
* watchdog_dev_unregister: * watchdog_dev_unregister: unregister a watchdog device
* @watchdog: 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) int watchdog_dev_unregister(struct watchdog_device *watchdog)
{ {
/* Check that a watchdog device was registered in the past */ cdev_del(&watchdog->cdev);
if (!test_bit(0, &watchdog_dev_busy) || !wdd) if (watchdog->id == 0) {
return -ENODEV; misc_deregister(&watchdog_miscdev);
old_wdd = NULL;
/* 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;
} }
misc_deregister(&watchdog_miscdev);
wdd = NULL;
clear_bit(0, &watchdog_dev_busy);
return 0; 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);
}

View File

@ -54,6 +54,8 @@ struct watchdog_info {
#ifdef __KERNEL__ #ifdef __KERNEL__
#include <linux/bitops.h> #include <linux/bitops.h>
#include <linux/device.h>
#include <linux/cdev.h>
struct watchdog_ops; struct watchdog_ops;
struct watchdog_device; struct watchdog_device;
@ -89,6 +91,8 @@ struct watchdog_ops {
/** struct watchdog_device - The structure that defines a watchdog device /** 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. * @info: Pointer to a watchdog_info structure.
* @ops: Pointer to the list of watchdog operations. * @ops: Pointer to the list of watchdog operations.
* @bootstatus: Status of the watchdog device at boot. * @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. * via the watchdog_set_drvdata and watchdog_get_drvdata helpers.
*/ */
struct watchdog_device { struct watchdog_device {
int id;
struct cdev cdev;
const struct watchdog_info *info; const struct watchdog_info *info;
const struct watchdog_ops *ops; const struct watchdog_ops *ops;
unsigned int bootstatus; unsigned int bootstatus;