mirror of
https://github.com/armbian/linux-cix.git
synced 2026-01-06 12:30:45 -08:00
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>
This commit is contained in:
committed by
Wim Van Sebroeck
parent
e7d162faa6
commit
b4ffb19098
@@ -44,7 +44,6 @@ The watchdog device structure looks like this:
|
||||
|
||||
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 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
|
||||
@@ -176,34 +158,21 @@ they are supported. These optional routines/operations are:
|
||||
* 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:
|
||||
|
||||
@@ -210,8 +210,6 @@ static int __watchdog_register_device(struct watchdog_device *wdd)
|
||||
* 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");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user