Cover image for Linux Watchdog

Linux Watchdog

Words 6.4k
Views
Visitors

Timeline

Timeline

2026-01-06

init

This article introduces the implementation principles and driver framework of the Linux watchdog timer subsystem. It first explains that the watchdog, as a timer-based hardware protection mechanism in embedded systems, ensures stable system operation through periodic 'feeding' operations, and triggers a forced reset when the system encounters anomalies (such as program runaway or deadlock). Then, using the iTOP-RK3568 platform as an example, it describes the hardware configuration of the SoC-integrated watchdog module. At the software level, the article analyzes in detail the three-layer architecture of the Linux watchdog subsystem: the device driver layer, the core layer, and the unified device driver layer, and introduces the responsibilities and key functions of each layer, such as`dw_wdt_drv_probe()`、`watchdog_register_device()`、`watchdog_dev_register()`etc. In addition, the article also covers watchdog-related properties in device tree configuration, as well as the pretimeout handling mechanism. Overall, this article summarizes the design ideas and code implementation of the Linux watchdog driver, providing a reference for developers to understand and use the watchdog subsystem.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Topics
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hotplug
11. pinctrl Subsystem
12. GPIO subsystem
13. Input subsystem
14. 1-Wire
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network devices
23. ADC
24. IIO
25. USB
26. LCD

Watchdog Basics

Watchdog Timer (WDT)is in embedded systemsa hardware protection mechanism implemented based on a timer

Its core component is a programmable counter, which ensures the reliability of the system running state through periodic reset operations. When the system encounters abnormal situations, such as program runaway caused by electromagnetic interference, infinite loops caused by software logic errors, or system deadlock caused by unknown faults, the watchdog triggers a forced reset to restore the system to its initial state, avoiding manual power-off intervention.

This mechanism significantly improves the stability of long-term device operation.

Note! Although there is a watchdog, it is the last line of defense, so these problems should be avoided when designing products.

The operation flow is as follows:

  1. Initialization Configuration

Set the watchdog timeout period and enable the watchdog timer.

  1. Normal Working State

The system task performs the ‘feeding’ action on time (that is, the main program periodically feeds the watchdog). After each feeding, the counter is reset.

  1. Exception Handling Mechanism

If the system is out of control and cannot be fed (that is, the program runs away or deadlocks), when the counter reaches zero, a forced safety measure is triggered (that is, the watchdog reset circuit acts), and the system returns to the initial safe state (that is, hardware reset).

Modern processors generally adopt SoC integration solutions to implement the watchdog moduleTaking the iTOP-RK3568 development platform as an example, its chip integrates two watchdogs. See the RK3568 Technical Reference Manual (TRM) as shown below:

RK3568 Watchdog
RK3568 Watchdog

In the figure above, it can be seen that the chip contains WDT_NS and WDT_S, two watchdogs.

Watchdog Subsystem Framework

The Linux watchdog subsystem adopts a classic three-layer architecture design, effectively decoupling the user interface, core logic, and hardware operations. The watchdog subsystem framework diagram is shown below.

Watchdog Subsystem Framework
Watchdog Subsystem Framework

Among them, the top layer is user space, the second layer is kernel space, and the third layer is the hardware layer.

As for the watchdog, the corresponding hardware driver is the watchdog device driver, whichbelongs to the character device drivercategory. In the Linux kernel, a series of processing operations are performed on the watchdog driver, including encapsulation and summarization, thus forming a complete framework. This framework mainly consists of three important parts, namely:

  • the watchdog device driver layer responsible for direct interaction with hardware
  • the watchdog core layer responsible for core logic processing
  • the watchdog unified device driver layer used to interact with user space

watchdog device tree configuration

12345678
wdt: watchdog@fe600000 {	compatible = "snps,dw-wdt";	reg = <0x0 0xfe600000 0x0 0x100>;	clocks = <&cru TCLK_WDT_NS>, <&cru PCLK_WDT_NS>;	clock-names = "tclk", "pclk";	interrupts = <GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>;	status = "okay";};
  • compatibleThe property is used for device driver matching.
  • regIt specifies the address range of the watchdog registers.
  • clocksandclock-namesIt defines the related clock,interruptsand configures the interrupt information.

watchdog device driver layer

Search in the kernel directory of the Linux source codesnps,dw-wdtfinddrivers/watchdog/dw_wdt.cfile, which is the driver file corresponding to the Watchdog device driver layer.dw_wdt.cThe driver file is as follows:

1234567891011
static struct platform_driver dw_wdt_driver = {	.probe		= dw_wdt_drv_probe,	.remove		= dw_wdt_drv_remove,	.driver		= {		.name	= "dw_wdt",		.of_match_table = of_match_ptr(dw_wdt_of_match),		.pm	= &dw_wdt_pm_ops,	},};module_platform_driver(dw_wdt_driver);

module_platform_driver(dw_wdt_driver)The macrodw_wdt_driverregisters it as a platform driver.

dw_wdt_drv_probe()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
static int dw_wdt_drv_probe(struct platform_device *pdev){    // Get the device structure pointer for subsequent operations	struct device *dev = &pdev->dev;    // Define a pointer to the watchdog device structure	struct watchdog_device *wdd;    // Define a pointer to the dw_pointer to the wdt structure, dw_The wdt structure may contain specific information about the watchdog device.	struct dw_wdt *dw_wdt;	int ret;    // Use devm_The kzalloc function allocates memory to store dw_wdt structure	// dev is a pointer to the device structure, used to provide context for memory allocation	// sizeof(*dw_wdt) is the size of memory to allocate, i.e. dw_size of the wdt structure	// GFP_KERNEL is a memory allocation flag used to specify the type of memory to allocate (kernel memory)	dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL);	if (!dw_wdt)// If memory allocation fails, return the -ENOMEM error code, indicating insufficient memory		return -ENOMEM;    // Get the device's memory resource    // Use devm_ioremap_The resource function maps the memory resource into the kernel virtual address space    // dev is a pointer to the device structure, mem is the memory resource to be mapped	dw_wdt->regs = devm_platform_ioremap_resource(pdev, 0);	if (IS_ERR(dw_wdt->regs))// If mapping fails, return PTR_ERR(dw_wdt->regs) error code		return PTR_ERR(dw_wdt->regs);	/*	 * Try to request the watchdog dedicated timer clock source. It must	 * be supplied if asynchronous mode is enabled. Otherwise fallback	 * to the common timer/bus clocks configuration, in which the very	 * first found clock supply both timer and APB signals.	 */    /*	* Try to request the watchdog-specific timer clock source。If asynchronous mode is enabled,This clock source must be provided。	* otherwise,Fall back to the generic timer/Bus clock configuration,In this configuration,The first clock found provides the clock for both the timer and APB signal provides the clock。	*/	dw_wdt->clk = devm_clk_get(dev, "tclk");// Get the clock named "tclk"	if (IS_ERR(dw_wdt->clk)) {// If getting fails		dw_wdt->clk = devm_clk_get(dev, NULL);// Try to get the default clock		if (IS_ERR(dw_wdt->clk))// If it fails again, return PTR_ERR(dw_wdt->clk) error code			return PTR_ERR(dw_wdt->clk);	}	ret = clk_prepare_enable(dw_wdt->clk);// Prepare and enable the clock	if (ret)		return ret;	dw_wdt->rate = clk_get_rate(dw_wdt->clk);// Get the clock rate	if (dw_wdt->rate == 0) {// If the clock rate is 0, set the error code and jump to out_disable_Clean up at the clk label		ret = -EINVAL;		goto out_disable_clk;	}	/*	 * Request APB clock if device is configured with async clocks mode.	 * In this case both tclk and pclk clocks are supposed to be specified.	 * Alas we can't know for sure whether async mode was really activated,	 * so the pclk phandle reference is left optional. If it couldn't be	 * found we consider the device configured in synchronous clocks mode.	 */    /*	* If the device is configured for asynchronous clock mode,request APB Clock。	* In this case,,Assuming both are specified tclk and pclk Clock。	* Unfortunately,We cannot be sure whether asynchronous mode is actually enabled,	* So pclk of phandle The reference is optional。If it is not found,We consider the device configured for synchronous clock mode。	*/    // Try to get the clock named "pclk" using devm_clk_The get_optional function is used to get an optional clock	dw_wdt->pclk = devm_clk_get_optional(dev, "pclk");	if (IS_ERR(dw_wdt->pclk)) {// If getting fails		ret = PTR_ERR(dw_wdt->pclk);// Set the error code and jump to out_disable_Clean up at the clk label		goto out_disable_clk;	}    // Prepare and enable the APB clock	ret = clk_prepare_enable(dw_wdt->pclk);	if (ret)// If preparing or enabling the APB clock fails, jump to out_disable_Clean up at the clk label		goto out_disable_clk;    // Get the reset control object using devm_reset_control_get_The optional_shared function is used to get an optional shared reset control object	dw_wdt->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);	if (IS_ERR(dw_wdt->rst)) {// If getting fails		ret = PTR_ERR(dw_wdt->rst);// Set the error code and jump to out_disable_Clean up at the pclk label		goto out_disable_pclk;	}	/* Enable normal reset without pre-timeout by default. */	dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET);	/*	 * Pre-timeout IRQ is optional, since some hardware may lack support	 * of it. Note we must request rising-edge IRQ, since the lane is left	 * pending either until the next watchdog kick event or up to the	 * system reset.	 */	ret = platform_get_irq_optional(pdev, 0);	if (ret > 0) {		ret = devm_request_irq(dev, ret, dw_wdt_irq,				       IRQF_SHARED | IRQF_TRIGGER_RISING,				       pdev->name, dw_wdt);		if (ret)			goto out_disable_pclk;		dw_wdt->wdd.info = &dw_wdt_pt_ident;	} else {		if (ret == -EPROBE_DEFER)			goto out_disable_pclk;		dw_wdt->wdd.info = &dw_wdt_ident;	}    // Release the reset signal to bring the device out of reset state.	reset_control_deassert(dw_wdt->rst);	ret = dw_wdt_init_timeouts(dw_wdt, dev);	if (ret)		goto out_disable_clk;	// Assign the wdd member in the dw_wdt structure to the wdd pointer.	wdd = &dw_wdt->wdd;	wdd->ops = &dw_wdt_ops;// Set the watchdog device's operations.	wdd->min_timeout = dw_wdt_get_min_timeout(dw_wdt);// Set the minimum timeout of the watchdog device.	wdd->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt);// Based on dw_wdt structure and DW_WDT_MAX_TOP calculates the maximum hardware heartbeat time (in milliseconds).	wdd->parent = dev;// Set the parent device of the watchdog device.	watchdog_set_drvdata(wdd, dw_wdt);// Associate the dw_wdt structure with the watchdog device.	watchdog_set_nowayout(wdd, nowayout);// Set the nowayout attribute of the watchdog device. nowayout may be used to control some behaviors of the watchdog.	watchdog_init_timeout(wdd, 0, dev);// Initialize the watchdog device with the default timeout.	/*	 * If the watchdog is already running, use its already configured	 * timeout. Otherwise use the default or the value provided through	 * devicetree.	 */    /*	* If the watchdog is already running,,use its configured timeout,。	* otherwise,use the default value or the value provided by the device tree,。	*/	if (dw_wdt_is_enabled(dw_wdt)) {// Check whether the watchdog is already enabled.        // If it is enabled, get the current timeout and set it to the watchdog device.		wdd->timeout = dw_wdt_get_timeout(dw_wdt);        // Set the status bit of the watchdog device to indicate that the hardware is running.		set_bit(WDOG_HW_RUNNING, &wdd->status);	} else {        // If not enabled, set the default timeout.		wdd->timeout = DW_WDT_DEFAULT_SECONDS;        // Reinitialize the watchdog device with the default timeout.		watchdog_init_timeout(wdd, 0, dev);	}    // Associate the dw_wdt structure with the platform device.	platform_set_drvdata(pdev, dw_wdt);    // Set the restart priority of the watchdog device to 128.	watchdog_set_restart_priority(wdd, 128);    // Register the watchdog device.	ret = watchdog_register_device(wdd);	if (ret)// If registration fails, jump to out._disable_Clean up at the pclk label		goto out_disable_pclk;	dw_wdt_dbgfs_init(dw_wdt);	return 0;out_disable_pclk:	clk_disable_unprepare(dw_wdt->pclk);out_disable_clk:	clk_disable_unprepare(dw_wdt->clk);	return ret;}

struct watchdog_device

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
/** struct watchdog_device - The structure that defines a watchdog device * * @id:		The watchdog's ID. (Allocated by watchdog_register_device) * @parent:	The parent bus device * @groups:	List of sysfs attribute groups to create when creating the *		watchdog device. * @info:	Pointer to a watchdog_info structure. * @ops:	Pointer to the list of watchdog operations. * @gov:	Pointer to watchdog pretimeout governor. * @bootstatus:	Status of the watchdog device at boot. * @timeout:	The watchdog devices timeout value (in seconds). * @pretimeout: The watchdog devices pre_timeout value. * @min_timeout:The watchdog devices minimum timeout value (in seconds). * @max_timeout:The watchdog devices maximum timeout value (in seconds) *		as configurable from user space. Only relevant if *		max_hw_heartbeat_ms is not provided. * @min_hw_heartbeat_ms: *		Hardware limit for minimum time between heartbeats, *		in milli-seconds. * @max_hw_heartbeat_ms: *		Hardware limit for maximum timeout, in milli-seconds. *		Replaces max_timeout if specified. * @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. * @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. * * The watchdog_device structure contains all information about a * watchdog timer device. * * The driver-data field may not be accessed directly. It must be accessed * via the watchdog_set_drvdata and watchdog_get_drvdata helpers. */struct watchdog_device {// Define a structure representing the watchdog device.	int id;// Device unique identifier	struct device *parent;// Pointer to parent device	const struct attribute_group **groups;// Pointer to an array of attribute groups, used to manage device attributes	const struct watchdog_info *info;// Pointer to a structure containing device details	const struct watchdog_ops *ops;// Pointer to a structure containing device operation functions	const struct watchdog_governor *gov;// Pointer to a supervision-related structure, possibly used to regulate device operation    // Startup status flag, with specific meaning defined by other parts	unsigned int bootstatus;	unsigned int timeout;	unsigned int pretimeout;	unsigned int min_timeout;	unsigned int max_timeout;	unsigned int min_hw_heartbeat_ms;	unsigned int max_hw_heartbeat_ms;	struct notifier_block reboot_nb;	struct notifier_block restart_nb;	void *driver_data;	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_NO_WAY_OUT		1	/* Is 'nowayout' feature set ? */#define WDOG_STOP_ON_REBOOT	2	/* Should be stopped on reboot */#define WDOG_HW_RUNNING		3	/* True if HW watchdog running */#define WDOG_STOP_ON_UNREGISTER	4	/* Should be stopped on unregister */	struct list_head deferred;};
struct watchdog_info

watchdog_infoMember points to a structure containing watchdog device details, as shown below:

123456789101112
// Define a structure named watchdog_info, used to store specific information related to the watchdog devicestruct watchdog_info {    // Used to store option flag information supported by the watchdog device or its driver	// Different bit settings represent various supported functions, features, or working modes, etc.	__u32 options;		/* Options the card/driver supports */    // Used to store the firmware version number of the watchdog device	// As the device firmware is updated, this version number changes accordingly to distinguish different firmware versions	__u32 firmware_version;	/* Firmware version of the card */    // Used to store string information that can identify the circuit board or device itself where the watchdog device is located	// Can store up to 32 bytes of character content, used to uniquely identify the device, such as device name, model, etc.	__u8  identity[32];	/* Identity of the board */};
struct watchdog_ops

watchdog_opsMember points to a structure containing various operation functions of the watchdog device, as shown below:

12345678910111213141516171819202122232425262728293031323334353637
/** struct watchdog_ops - The watchdog-devices operations * * @owner:	The module owner. * @start:	The routine for starting the watchdog device. * @stop:	The routine for stopping the watchdog device. * @ping:	The routine that sends a keepalive ping to the watchdog device. * @status:	The routine that shows the status of the watchdog device. * @set_timeout:The routine for setting the watchdog devices timeout value (in seconds). * @set_pretimeout:The routine for setting the watchdog devices pretimeout. * @get_timeleft:The routine that gets the time left before a reset (in seconds). * @restart:	The routine for restarting the machine. * @ioctl:	The routines that handles extra ioctl calls. * * The watchdog_ops structure contains a list of low-level operations * that control a watchdog device. It also contains the module that owns * these operations. The start function is mandatory, all other * functions are optional. */struct watchdog_ops {    // Pointer to the module that owns this set of operation functions, usually used for management such as module reference counting	struct module *owner;    	/* mandatory operations */    /* The following are operation function pointers that must be implemented */	int (*start)(struct watchdog_device *); // Function pointer to start the watchdog device, takes a pointer to the watchdog device structure, returns the operation result	    /* optional operations */    /* The following are operation function pointers that may be optionally implemented */	int (*stop)(struct watchdog_device *);// Function pointer to stop the watchdog device, takes a pointer to the watchdog device structure, returns the operation result	int (*ping)(struct watchdog_device *);// Function pointer to perform the watchdog kick operation, takes a pointer to the watchdog device structure, returns the operation result	unsigned int (*status)(struct watchdog_device *);// Function pointer to get the current status of the watchdog device, takes a pointer to the watchdog device structure, returns the status value	int (*set_timeout)(struct watchdog_device *, unsigned int);// Function pointer to set the watchdog device timeout, takes a pointer to the watchdog device structure and a new timeout value, returns the operation result	int (*set_pretimeout)(struct watchdog_device *, unsigned int);// Function pointer to set the watchdog device pre-timeout, takes a pointer to the watchdog device structure and a new pre-timeout value, returns the operation result	unsigned int (*get_timeleft)(struct watchdog_device *);// Function pointer to get the remaining time of the watchdog device, takes a pointer to the watchdog device structure, returns the remaining time value	int (*restart)(struct watchdog_device *, unsigned long, void *);// Function pointer to restart the watchdog device, taking a pointer to the watchdog device structure, restart-related parameters, etc., and returning the operation result.	long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);// Function pointer for handling ioctl system calls, taking a pointer to the watchdog device structure, the ioctl command code, and parameters, and returning the processing result.};
struct watchdog_governor

watchdog_governorThe member points to a structure used for managing and controlling the operation of the watchdog device.

1234
struct watchdog_governor {	const char	name[WATCHDOG_GOV_NAME_MAXLEN];	void		(*pretimeout)(struct watchdog_device *wdd);};

Among them,pretimeoutThe function pointed to by the function pointer is mainly used to perform specific operations just before the watchdog device is about to reach its timeout.

Typically, a watchdog device sets a timeout period. If the corresponding “feeding the dog” operation (i.e., resetting the timer and related operations to indicate that the system is running normally) is not performed within this specified time, the watchdog will trigger actions such as resetting the system. And pretimeoutThe function pointed to by the function is called at a pre-stage (pre-timeout phase) close to this timeout time to perform some early processing actions.

struct dw_wdt

dw_wdt_drv_probeIn the function, a pointer to a custom-defined is defined.struct dw_wdtpointer to typedw_wdt, this structure contains some specific information related to this particular watchdog device, as shown below:

123456789101112131415161718192021222324
// Define a structure named dw_wdt, used to store various information related to a specific watchdog device.struct dw_wdt {	// Memory-mapped address pointing to the device registers; this pointer can be used to access the device registers.	void __iomem		*regs;    // Pointer to the primary clock source, used to obtain the device's main clock information.	struct clk		*clk;    // Pointer to the APB clock source, possibly used for APB-related clock operations (depending on the device)	struct clk		*pclk;    // Store clock frequency related values, possibly information such as the clock frequency used by the current device.	unsigned long		rate;	enum dw_wdt_rmod	rmod;	struct dw_wdt_timeout	timeouts[DW_WDT_NUM_TOPS];    // A structure containing general information and operation functions of the watchdog device, used to manage the basic attributes and operations of the watchdog device.	struct watchdog_device	wdd;    // Pointer to the reset control structure, used for reset-related operations and control of the device.	struct reset_control	*rst;	/* Save/restore */ /* 以下成员可能用于保存和恢复设备的某些状态信息 */	u32			control;// Control-related information of storage devices	u32			timeout;// Information related to timeout settings for storage devices#ifdef CONFIG_DEBUG_FS	struct dentry		*dbgfs_dir;#endif};

watchdog core layer

watchdog_register_device()

The probe function abovedw_wdt_drv_probe()in usewatchdog_register_device()function pairwddthe pointed-tostruct watchdog_devicePerform a registration operation on the structure.

123456789101112131415161718192021222324252627282930313233
/** * watchdog_register_device() - register a watchdog device * @wdd: watchdog device * * Register a watchdog device with the kernel so that the * watchdog timer can be accessed from userspace. * * A zero is returned on success and a negative errno code for * failure. */int watchdog_register_device(struct watchdog_device *wdd){	const char *dev_str;	int ret = 0;	mutex_lock(&wtd_deferred_reg_mutex);	if (wtd_deferred_reg_done)//If wtd_deferred_reg_If done is true, then execute.__watchdog_register_device(wdd) function		ret = __watchdog_register_device(wdd);	else// Otherwise execute watchdog_deferred_registration_add(wdd);		watchdog_deferred_registration_add(wdd);	mutex_unlock(&wtd_deferred_reg_mutex);	if (ret) {		dev_str = wdd->parent ? dev_name(wdd->parent) :			  (const char *)wdd->info->identity;		pr_err("%s: failed to register watchdog device (err = %d)\n",			dev_str, ret);	}	return ret;}EXPORT_SYMBOL_GPL(watchdog_register_device);

watchdog_deferred_registration()

Beforedrivers/watchdog/watchdog_core.cin the filewatchdog_deferred_registration()Found in functionwtd_deferred_reg_doneis true

123456789101112131415
static int __init watchdog_deferred_registration(void){	mutex_lock(&wtd_deferred_reg_mutex);	wtd_deferred_reg_done = true;	while (!list_empty(&wtd_deferred_reg_list)) {		struct watchdog_device *wdd;		wdd = list_first_entry(&wtd_deferred_reg_list,				       struct watchdog_device, deferred);		list_del(&wdd->deferred);		__watchdog_register_device(wdd);	}	mutex_unlock(&wtd_deferred_reg_mutex);	return 0;}

and this function iswatchdog_core.cThe initialization function call.

12345678910111213141516171819202122232425
static int __init watchdog_init(void){	int err;	err = watchdog_dev_init();	if (err < 0)		return err;	watchdog_deferred_registration();	return 0;}static void __exit watchdog_exit(void){	watchdog_dev_exit();	ida_destroy(&watchdog_ida);}subsys_initcall_sync(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");MODULE_LICENSE("GPL");

That is, ifwatchdog_core.cIf this module is not loaded, then lazy load it,watchdog_register_device()Callwatchdog_deferred_registration_add(wdd);

__watchdog_register_device()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
static int __watchdog_register_device(struct watchdog_device *wdd){	int ret, id = -1;    // Parameter validation	if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)		return -EINVAL;	/* Mandatory operations need to be supported */    // Operation function validation (must implement start and stop/max_hw_heartbeat_ms	if (!wdd->ops->start || (!wdd->ops->stop && !wdd->max_hw_heartbeat_ms))		return -EINVAL;    // Check the validity of the timeout duration	watchdog_check_min_max_timeout(wdd);	/*	 * Note: now that all watchdog_device data has been verified, we	 * will not check this anymore in other functions. If data gets	 * corrupted in a later stage then we expect a kernel panic!	 */	/* Use alias for watchdog id if possible */	if (wdd->parent) {// Get watchdog ID (prefer device tree alias)		ret = of_alias_get_id(wdd->parent->of_node, "watchdog");		if (ret >= 0)			id = ida_simple_get(&watchdog_ida, ret,					    ret + 1, GFP_KERNEL);	}	if (id < 0)// Handling when ID allocation fails		id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);	if (id < 0)		return id;	wdd->id = id;	ret = watchdog_dev_register(wdd);// Register watchdog character device	if (ret) {		ida_simple_remove(&watchdog_ida, id);		if (!(id == 0 && ret == -EBUSY))			return ret;		/* Retry in case a legacy watchdog module exists */        // Handle special cases of legacy driver conflicts (may be occupied when id=0)		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;		}	}	/* Module parameter to force watchdog policy on reboot. */	if (stop_on_reboot != -1) {		if (stop_on_reboot)			set_bit(WDOG_STOP_ON_REBOOT, &wdd->status);		else			clear_bit(WDOG_STOP_ON_REBOOT, &wdd->status);	}    // Register system restart notification callback	if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) {		if (!wdd->ops->stop)			pr_warn("watchdog%d: stop_on_reboot not supported\n", wdd->id);		else {			wdd->reboot_nb.notifier_call = watchdog_reboot_notifier;			ret = register_reboot_notifier(&wdd->reboot_nb);			if (ret) {				pr_err("watchdog%d: Cannot register reboot notifier (%d)\n",					wdd->id, ret);				watchdog_dev_unregister(wdd);				ida_simple_remove(&watchdog_ida, id);				return ret;			}		}	}    // Register system restart handler function	if (wdd->ops->restart) {		wdd->restart_nb.notifier_call = watchdog_restart_notifier;		ret = register_restart_handler(&wdd->restart_nb);		if (ret)			pr_warn("watchdog%d: Cannot register restart handler (%d)\n",				wdd->id, ret);	}	return 0;}

__watchdog_register_devicecall inwatchdog_dev_registerfunction registrationwddThe character device corresponding to the pointed watchdog device.

If registration fails (ret is not 0), first check whether it is because the ID is 0 and -EBUSY is returned. If not, directly return ret to the caller;

If so, reassign an ID (1 to MAX_DOGS) and then register the device.watchdog_dev_registerThe function is as follows.

watchdog_dev_register()

123456789101112131415161718192021222324
/* *	watchdog_dev_register: register a watchdog device *	@wdd: watchdog device * *	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 *wdd){	int ret;	// 1. Register character device (create /dev/watchdogX node	ret = watchdog_cdev_register(wdd);	if (ret)		return ret;	// 2. Register pre-timeout handling mechanism	ret = watchdog_register_pretimeout(wdd);	if (ret)		watchdog_cdev_unregister(wdd); // Roll back character device registration on failure	return ret;}

In the above code, the most important is usingwatchdog_cdev_registerthe function to register the character device, which is the key to implementing the unified watchdog device driver layer.

Watchdog unified device driver layer

watchdog_cdev_register()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
/* *	watchdog_cdev_register: register watchdog character device *	@wdd: watchdog device * *	Register a watchdog character device including handling the legacy *	/dev/watchdog node. /dev/watchdog is actually a miscdevice and *	thus we set it up like that. */static int watchdog_cdev_register(struct watchdog_device *wdd){    // Core data structure (including device state, timers, etc.)	struct watchdog_core_data *wd_data;	int err;	wd_data = kzalloc(sizeof(struct watchdog_core_data), GFP_KERNEL);	if (!wd_data)		return -ENOMEM;	mutex_init(&wd_data->lock);// Initialize protection lock    // Establish bidirectional association (device ↔ core data)	wd_data->wdd = wdd;	wdd->wd_data = wd_data;    // Validate kernel workqueue validity (relied upon by the heartbeat maintenance mechanism)	if (IS_ERR_OR_NULL(watchdog_kworker)) {		kfree(wd_data);		return -ENODEV;	}    // Device node initialization	device_initialize(&wd_data->dev);	wd_data->dev.devt = MKDEV(MAJOR(watchdog_devt), wdd->id);// Allocate device number	wd_data->dev.class = &watchdog_class;	wd_data->dev.parent = wdd->parent;	wd_data->dev.groups = wdd->groups;	wd_data->dev.release = watchdog_core_data_release;	dev_set_drvdata(&wd_data->dev, wdd);	dev_set_name(&wd_data->dev, "watchdog%d", wdd->id);// Name device node    // Timer mechanism initialization	kthread_init_work(&wd_data->work, watchdog_ping_work);// Heartbeat workqueue	hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD); // Initialize high-resolution timer	wd_data->timer.function = watchdog_timer_expired;    // Legacy device registration (/dev/watchdog), registered as a miscellaneous device	if (wdd->id == 0) {		old_wd_data = wd_data;		watchdog_miscdev.parent = wdd->parent;		err = misc_register(&watchdog_miscdev);// Register misc device (compatible with legacy interface)		if (err != 0) {			pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",				wdd->info->identity, WATCHDOG_MINOR, err);			if (err == -EBUSY)// Handle special case of device number conflict (returns EBUSY when an old driver exists)				pr_err("%s: a legacy watchdog module is probably present.\n",					wdd->info->identity);			old_wd_data = NULL;			put_device(&wd_data->dev);			return err;		}	}	/* Fill in the data structures */    // Character device registration (create /dev/watchdogX node)	cdev_init(&wd_data->cdev, &watchdog_fops); // Bind file operation interface	/* Add the device */	err = cdev_device_add(&wd_data->cdev, &wd_data->dev);// Add to system	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_wd_data = NULL;			put_device(&wd_data->dev);		}		return err;	}	wd_data->cdev.owner = wdd->ops->owner;	/* Record time of most recent heartbeat as 'just before now'. */	wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1);	watchdog_set_open_deadline(wd_data);	/*	 * If the watchdog is running, prevent its driver from being unloaded,	 * and schedule an immediate ping.	 */	if (watchdog_hw_running(wdd)) {// Runtime status handling		__module_get(wdd->ops->owner); // Increase module reference count		get_device(&wd_data->dev);		if (handle_boot_enabled)// Start the timer according to configuration (maintain heartbeat before userspace takes over)			hrtimer_start(&wd_data->timer, 0,				      HRTIMER_MODE_REL_HARD);		else			pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n",				wdd->id);	}	return 0;}

watchdog_ping_work()

watchdog_cdev_register()In the defined workqueuewatchdog_ping_workThe function performs watchdog-related task operations. The function code is as follows:

1234567891011121314151617
static void watchdog_ping_work(struct kthread_work *work){    // Obtain the core data structure through the work item	struct watchdog_core_data *wd_data;	wd_data = container_of(work, struct watchdog_core_data, work);    // Lock to ensure atomicity of operations (prevent concurrent access)	mutex_lock(&wd_data->lock);    // Determine whether a heartbeat signal needs to be sent:	// 1. Device exists	// 2. Watchdog is active or hardware is running	if (watchdog_worker_should_ping(wd_data))		__watchdog_ping(wd_data->wdd);// Perform the actual heartbeat operation    // Release the mutex lock	mutex_unlock(&wd_data->lock);}

watchdog_fops

watchdog_cdev_register()throughcdev_init(&wd_data->cdev, &watchdog_fops);Set the device operation functions in the cdev structure towatchdog_fops

12345678
static const struct file_operations watchdog_fops = {	.owner		= THIS_MODULE,	.write		= watchdog_write,	.unlocked_ioctl	= watchdog_ioctl,	.compat_ioctl	= compat_ptr_ioctl,	.open		= watchdog_open,	.release	= watchdog_release,};
.open = watchdog_open

openThe operation function corresponding to the system call is specified aswatchdog_open. When a user-space program attempts to open a device file associated with this file operation structure, the kernel callswatchdog_openfunction to perform initialization operations related to device opening, such as resource allocation, state initialization, etc., to ensure the device can operate normally in subsequent read, write, control, and other operations.

watchdog_openThe function is as follows:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
/* *	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. *	Watch out: the /dev/watchdog device is single open, so we make sure *	it can only be opened once. */static int watchdog_open(struct inode *inode, struct file *file){	struct watchdog_core_data *wd_data;	struct watchdog_device *wdd;	bool hw_running;	int err;	/* Get the corresponding watchdog device */	if (imajor(inode) == MISC_MAJOR)// Traditional devices are obtained via miscdevice, new devices are obtained via character device structure		wd_data = old_wd_data;	else		wd_data = container_of(inode->i_cdev, struct watchdog_core_data,				       cdev);	/* the watchdog is single open! */    /* Singleton pattern detection - ensure the device can only be opened once */	if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status))/* Singleton pattern detection - ensure the device can only be opened once */		return -EBUSY;	wdd = wd_data->wdd;	/*	 * If the /dev/watchdog device is open, we don't want the module	 * to be unloaded.	 */	hw_running = watchdog_hw_running(wdd);/* Module reference management */    // If the hardware is not running, increase the module reference count to prevent unloading	if (!hw_running && !try_module_get(wdd->ops->owner)) {		err = -EBUSY;		goto out_clear;	}    /* Start the watchdog hardware */	err = watchdog_start(wdd);	if (err < 0)		goto out_mod;    /* Save the core data structure to file private data */	file->private_data = wd_data;    /* Update device reference count */	if (!hw_running)		get_device(&wd_data->dev);// Increase device reference count	/*	 * open_timeout only applies for the first open from	 * userspace. Set open_deadline to infinity so that the kernel	 * will take care of an always-running hardware watchdog in	 * case the device gets magic-closed or WDIOS_DISABLECARD is	 * applied.	 */	wd_data->open_deadline = KTIME_MAX;	/* dev/watchdog is a virtual (and thus non-seekable) filesystem */	return stream_open(inode, file);out_mod:	module_put(wd_data->wdd->ops->owner);out_clear:	clear_bit(_WDOG_DEV_OPEN, &wd_data->status);	return err;}
.write = watchdog_write,

The operation function corresponding to the write system call is specified aswatchdog_write. When a user-space program, for a device file associated with this file operation structure (e.g.,/dev/watchdog) performs a write operation, the kernel calls the function specified herewatchdog_writefunction to handle the specific write request. This function is used to perform watchdog feeding operations and other write-related functions.watchdog_writeThe function is as follows:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
/* *	watchdog_write: writes to the watchdog. *	@file: file from VFS *	@data: user address of data *	@len: length of data *	@ppos: pointer to the file offset * *	A write to a watchdog device is defined as a keepalive ping. *	Writing the magic 'V' sequence allows the next close to turn *	off the watchdog (if 'nowayout' is not set). */static ssize_t watchdog_write(struct file *file, const char __user *data,						size_t len, loff_t *ppos){	struct watchdog_core_data *wd_data = file->private_data;	struct watchdog_device *wdd;	int err;	size_t i;	char c;	if (len == 0)		return 0;	/*	 * Note: just in case someone wrote the magic character	 * five months ago...	 */    /* Clear the magic character marker left over from the last time */	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++) {/* Scan user data for the magic character 'V' */		if (get_user(c, data + i))// Safely copy data from user space			return -EFAULT;		if (c == 'V')// Magic character detected			set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);// Allow safe shutdown	}	/* someone wrote to us, so we send the watchdog a keepalive ping */    /* Trigger watchdog heartbeat keepalive */	err = -ENODEV;	mutex_lock(&wd_data->lock);// Lock to protect device state	wdd = wd_data->wdd;	if (wdd)		err = watchdog_ping(wdd);// Perform low-level heartbeat operation	mutex_unlock(&wd_data->lock);// Release lock	if (err < 0)		return err;	return len;// Return the actual write length (returns the requested length on success)}
.unlocked_ioctl = watchdog_ioctl,

ioctlThe operation function corresponding to the system call is specified aswatchdog_ioctl

ioctlIt is an interface in Linux used for specific control operations on devices. User-space programs can send various custom commands to the device through ioctl.

When an ioctl operation is performed on the associated device file, the kernel callswatchdog_ioctlfunction to handle these specific control requests.

For watchdog devices, this function is used to set the timeout, get device status, and other functions that need to be implemented via ioctl.watchdog_ioctlThe function is as follows:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
/* *	watchdog_ioctl: handle the different ioctl's for the watchdog device. *	@file: file handle to the device *	@cmd: watchdog command *	@arg: argument pointer * *	The watchdog API defines a common set of functions for all watchdogs *	according to their available features. */static long watchdog_ioctl(struct file *file, unsigned int cmd,							unsigned long arg){	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);// Acquire device status lock	wdd = wd_data->wdd;	if (!wdd) {		err = -ENODEV;// Device not ready		goto out_ioctl;	}    /* Handle driver-specific ioctl operations first */	err = watchdog_ioctl_op(wdd, cmd, arg);	if (err != -ENOIOCTLCMD)// Handled or error		goto out_ioctl;    /* Standard ioctl command handling */	switch (cmd) {	case WDIOC_GETSUPPORT:	// Get watchdog capability information		err = copy_to_user(argp, wdd->info,			sizeof(struct watchdog_info)) ? -EFAULT : 0;		break;	case WDIOC_GETSTATUS:	// Get device status flags		val = watchdog_get_status(wdd);		err = put_user(val, p);		break;	case WDIOC_GETBOOTSTATUS:	// Get boot status		err = put_user(wdd->bootstatus, p);		break;	case WDIOC_SETOPTIONS:	// Set device options		if (get_user(val, p)) {			err = -EFAULT;			break;		}		if (val & WDIOS_DISABLECARD) {// Disable watchdog			err = watchdog_stop(wdd);			if (err < 0)				break;		}		if (val & WDIOS_ENABLECARD)// Enable watchdog			err = watchdog_start(wdd);		break;	case WDIOC_KEEPALIVE:// Manually trigger heartbeat		if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) {			err = -EOPNOTSUPP;// Device does not support this operation			break;		}		err = watchdog_ping(wdd);		break;	case WDIOC_SETTIMEOUT:// Set timeout period		if (get_user(val, p)) {			err = -EFAULT;			break;		}		err = watchdog_set_timeout(wdd, val);		if (err < 0)			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);/* After setting successfully, immediately send a heartbeat to ensure the new timeout takes effect */		if (err < 0)			break;		fallthrough;	case WDIOC_GETTIMEOUT:		/* timeout == 0 means that we don't know the timeout */		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 < 0)			break;		err = put_user(val, p);		break;	case WDIOC_SETPRETIMEOUT:		if (get_user(val, p)) {			err = -EFAULT;			break;		}		err = watchdog_set_pretimeout(wdd, val);		break;	case WDIOC_GETPRETIMEOUT:		err = put_user(wdd->pretimeout, p);		break;	default:		err = -ENOTTY;// Unknown ioctl command		break;	}out_ioctl:	mutex_unlock(&wd_data->lock);// Release device state lock	return err;}

Writing a watchdog application

  1. Open device file: open/dev/watchdog0, open in read-write mode, and on failure outputopen errorand return -1.
  2. Set timeout period: after successful opening, set the timeout to 2 seconds via ioctl, on failure outputset time out errorand return -2.
  3. Feed watchdog operation: after setting the timeout, loop 5 times to perform the feed watchdog operation (usingWDIOC_KEEPALIVEioctl), on success outputfeed dog okand pause for 1 second, on failure outputfeed dog errorand return -2.
  4. End prompt: after completing the feed watchdog loop, outputhungry dog, and finally the program ends normally with return value 0.
123456789101112131415161718192021222324252627282930313233343536373839404142
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>#include <sys/ioctl.h>#include <linux/watchdog.h>int main(int argc,char *argv[]){	int fd;	int time_out;	int ret;	int i=5;	fd = open("/dev/watchdog0",O_RDWR);	if(fd < 0){		printf("open error\n");		return -1;	}		time_out = 2;	ret = ioctl(fd,WDIOC_SETTIMEOUT,&time_out);	if(ret<0){		printf("set time out error\n");		return -2;	}	while(i--){		ret = ioctl(fd,WDIOC_KEEPALIVE,NULL);		if(ret < 0){		printf("feed dog error\n");		return -2;		}		printf("feed dog ok\n");		sleep(1);	}		printf("hungry dog\n");			return 0;}
Loading comments…