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 Contents | Links |
|---|---|
| 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:
- Initialization Configuration
Set the watchdog timeout period and enable the watchdog timer.
- 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.
- 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:

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.

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 */ 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 struct dentry *dbgfs_dir;}; |
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
- Open device file: open
/dev/watchdog0, open in read-write mode, and on failure outputopen errorand return -1. - Set timeout period: after successful opening, set the timeout to 2 seconds via ioctl, on failure output
set time out errorand return -2. - Feed watchdog operation: after setting the timeout, loop 5 times to perform the feed watchdog operation (using
WDIOC_KEEPALIVEioctl), on success outputfeed dog okand pause for 1 second, on failure outputfeed dog errorand return -2. - End prompt: after completing the feed watchdog loop, output
hungry dog, and finally the program ends normally with return value 0.
123456789101112131415161718192021222324252627282930313233343536373839404142 | 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;} |

