Cover image for Linux Watchdog

Linux Watchdog


Timeline

Timeline

2026-01-06

init

This article introduces the basic concepts and operation flow of the Linux watchdog, along with its subsystem framework designed with a three-layer architecture in the kernel. It also discusses device tree configuration and the key data structures and function implementations of the device driver layer, core layer, and unified device driver layer in detail.

Linux Driver Notes

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

Watchdog Basics

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

Its core component is a programmable counter, which ensures the reliability of the system’s operating state through periodic reset operations. When the system encounters abnormal situations, such as program runaway due to electromagnetic interference, infinite loops caused by software logic errors, or system deadlock due to 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 issues 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 (i.e., the main program periodically feeds the watchdog). After each feeding, the counter resets.

  1. Exception Handling Mechanism

If the system goes out of control and cannot be fed (i.e., the program runs away or deadlocks), when the counter reaches zero, forced safety measures are triggered (i.e., the watchdog reset circuit activates), and the system returns to its initial safe state (i.e., hardware reset).

Modern processors commonly adoptSoC integration solutions to implement the watchdog module. Taking the iTOP-RK3568 development platform as an example, its chip integrates two watchdogs. Refer to the RK3568 Technical Reference Manual (TRM) as shown below:

RK3568 Watchdog
RK3568 Watchdog

In the figure above, it can be seen that the chip includes 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 in the figure below.

Watchdog Subsystem Framework
Watchdog Subsystem Framework

The topmost layer is the user space, the second layer is the kernel space, and the third layer is the hardware layer.

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 have been performed on the watchdog driver, including encapsulation and summarization, thus forming a complete framework. This framework mainly consists of three important parts, which are:

  • 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 for interaction with user space

watchdog device tree configuration

1
2
3
4
5
6
7
8
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
  • regspecifies the address range of the watchdog registers
  • clocksandclock-namesdefines the related clocks,interruptsconfigures the interrupt information.

watchdog device driver layer

Search under the Linux source kernel directorysnps,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:

1
2
3
4
5
6
7
8
9
10
11
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 as a platform driver

dw_wdt_drv_probe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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_wdt structure pointer, dw_wdt structure may contain specific information about the watchdog device
struct dw_wdt *dw_wdt;
int ret;

// Use devm_kzalloc function to allocate memory for storing the dw_wdt structure
// dev is the device structure pointer, used to provide the context for memory allocation
// sizeof(*dw_wdt) is the size of memory to be allocated, 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 resources
// use devm_ioremap_the resource function maps memory resources to 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.
*/
/*
* attempt to request the watchdog-specific timer clock source。if asynchronous mode is enabled,this clock source must be provided。
* otherwise,fall back to the general-purpose timer/bus clock configuration,In this configuration,The first found clock provides for both the timer andAPB the signal clock。
*/
dw_wdt->clk = devm_clk_get(dev, "tclk");// Get the clock named "tclk"
if (IS_ERR(dw_wdt->clk)) {// If the acquisition fails
dw_wdt->clk = devm_clk_get(dev, NULL);// Attempt 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 in 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 's phandle reference is optional。If it is not found,we consider the device configured in synchronous clock mode。
*/
// Attempt to get the clock named "pclk", devm_clk_the get_optional function is used to obtain an optional clock
dw_wdt->pclk = devm_clk_get_optional(dev, "pclk");
if (IS_ERR(dw_wdt->pclk)) {// If the acquisition 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, 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 acquisition 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 operation function set of the watchdog device
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);// According to 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 property of the watchdog device; nowayout may be used to control certain 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 via the device tree。
*/
if (dw_wdt_is_enabled(dw_wdt)) {// Check if the watchdog is already enabled
// If already enabled, get the current timeout and set it in the watchdog device
wdd->timeout = dw_wdt_get_timeout(dw_wdt);
// Set the status bit of the watchdog device to indicate 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_Perform cleanup 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/** 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 the structure representing the watchdog device
int id;// Device unique identifier
struct device *parent;// Pointer to the parent device
const struct attribute_group **groups;// Pointer to an array of attribute groups for managing 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 for regulating device operation
// Startup status flag, with specific meaning defined elsewhere
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_infoThe member points to a structure containing detailed information about the watchdog device

1
2
3
4
5
6
7
8
9
10
11
12
// Define a structure named watchdog_info to store specific information related to the watchdog device
struct watchdog_info {
// Used to store option flag information supported by the watchdog device or its driver
// Indicates various supported functions, features, or working modes through different bit settings.
__u32 options; /* Options the card/driver supports */
// Used to store the firmware version number of the watchdog device.
// This version number changes accordingly with firmware updates to distinguish different firmware versions.
__u32 firmware_version; /* Firmware version of the card */
// Used to store string information that identifies the circuit board or the device itself where the watchdog device is located.
// Can store up to 32 bytes of character content for uniquely identifying the device, such as device name, model, etc.
__u8 identity[32]; /* Identity of the board */
};
struct watchdog_ops

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/** 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, typically used for module reference counting and other management.
struct module *owner;

/* mandatory operations */
/* The following are mandatory operation function pointers: */
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 optional operation function pointers: */
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 timeout period of the watchdog device, 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 pre-timeout period of the watchdog device, 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, takes a pointer to the watchdog device structure and restart-related parameters, returns the operation result
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);// Function pointer to handle ioctl system calls, takes a pointer to the watchdog device structure, ioctl command code, and parameters, returns the processing result
};
struct watchdog_governor

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

1
2
3
4
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 before the watchdog device is about to reach its timeout.

Typically, a watchdog device sets a timeout period. If the corresponding ‘petting’ operation (i.e., resetting the timer or related operations to indicate normal system operation) is not performed within this specified time, the watchdog triggers actions such as system reset. And pretimeoutThe function pointed to by the function is called at a pre-stage (pre-timeout stage) close to this timeout to perform some early processing actions.

struct dw_wdt

dw_wdt_drv_probeThe function defines a pointer to a customstruct dw_wdttype pointerdw_wdt, which contains specific information related to this particular watchdog device. The structure is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Define a structure named dw_wdt to store various information related to a specific watchdog device
struct dw_wdt {
// Pointer to the memory-mapped address of the device registers, through which the device registers can be accessed
void __iomem *regs;
// Pointer to the main clock source, used to obtain the main clock information of the device
struct clk *clk;
// Pointer to the APB clock source, possibly used for APB-related clock operations (depending on the device)
struct clk *pclk;
// Stores values related to clock frequency, 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 performing reset-related operations and control on the device.
struct reset_control *rst;
/* Save/restore */ /* 以下成员可能用于保存和恢复设备的某些状态信息 */
u32 control;// Stores control-related information of the device.
u32 timeout;// Stores timeout setting-related information of the device.

#ifdef CONFIG_DEBUG_FS
struct dentry *dbgfs_dir;
#endif
};

Watchdog core layer

watchdog_register_device()

The above probe functiondw_wdt_drv_probe()useswatchdog_register_device()function towddthe structure pointed to by thestruct watchdog_devicestructure for registration operation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 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_done is true, then execute__watchdog_the 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()

Indrivers/watchdog/watchdog_core.cin the filewatchdog_deferred_registration()found in the functionwtd_deferred_reg_doneis true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 is called bywatchdog_core.cthe initialization function of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.cthis module is not loaded, then lazy load,watchdog_register_device()callwatchdog_deferred_registration_add(wdd);

__watchdog_register_device()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
static int __watchdog_register_device(struct watchdog_device *wdd)
{
int ret, id = -1;

// parameter validity check
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
return -EINVAL;

/* Mandatory operations need to be supported */
// operation function verification (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
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 old driver conflicts (id=0 may be occupied)
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_deviceCalled inwatchdog_dev_registerfunction registerswddthe character device corresponding to the watchdog device pointed to.

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

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

watchdog_dev_register()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* 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 to usewatchdog_cdev_registerfunction to register the character device, which is key to implementing the watchdog unified device driver layer

watchdog unified device driver layer

watchdog_cdev_register()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
* 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 (includes device status, 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 work queue validity (depends on 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 work queue
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), register as 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 (return EBUSY when 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)) {// Running status handling
__module_get(wdd->ops->owner); // Increase module reference count
get_device(&wd_data->dev);
if (handle_boot_enabled)// Start timer according to configuration (maintain heartbeat before user space 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 work queuewatchdog_ping_workThe function performs watchdog-related task operations. The function code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void watchdog_ping_work(struct kthread_work *work)
{
// Obtain core data structure through 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 to send a heartbeat signal:
// 1. Device exists
// 2. Watchdog is activated or hardware is running
if (watchdog_worker_should_ping(wd_data))
__watchdog_ping(wd_data->wdd);// Execute actual heartbeat operation
// Release mutex lock
mutex_unlock(&wd_data->lock);
}

watchdog_fops

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

1
2
3
4
5
6
7
8
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 calls thewatchdog_openfunction to perform initialization operations related to opening the device, such as resource allocation and state initialization, to ensure the device can operate normally in subsequent read, write, and control operations.

watchdog_openThe function is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
* 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, while new devices are obtained via the 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 - ensures the device can only be opened once. */
if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status))/* Singleton pattern detection - ensures 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 the file's private data. */
file->private_data = wd_data;

/* Update the device reference count. */
if (!hw_running)
get_device(&wd_data->dev);// Increase the 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 performs operations on a device file associated with this file operation structure (such as/dev/watchdog) When executing the write operation, the kernel will call the one specified herewatchdog_writefunction to handle the specific write request. This function is used to perform watchdog feeding and other write-related operations.watchdog_writeThe function is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
* 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 mark left 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 maintenance */
err = -ENODEV;
mutex_lock(&wd_data->lock);// Lock to protect device state
wdd = wd_data->wdd;
if (wdd)
err = watchdog_ping(wdd);// Execute underlying heartbeat operation
mutex_unlock(&wd_data->lock);// Release the lock

if (err < 0)
return err;

return len;// Return the actual write length (return the requested length on success)
}
.unlocked_ioctl = watchdog_ioctl,

ioctlThe operation function corresponding to the system call is specified aswatchdog_ioctl

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

When performing an ioctl operation on the associated device file, the kernel calls thewatchdog_ioctlfunction to handle these specific control requests.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
* 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;
}

/* Prioritize handling driver-specific ioctl operations */
err = watchdog_ioctl_op(wdd, cmd, arg);
if (err != -ENOIOCTLCMD)// Processed 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
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);/* Send heartbeat immediately after successful setting 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;
}

Watchdog application programming

  1. Open device file: open/dev/watchdog0, open in read-write mode, output on failureopen errorand return -1.
  2. Set timeout: after successful opening, set timeout to 2 seconds via system call, output on failureset time out errorand return -2.
  3. Pet dog operation: after setting timeout, loop 5 times to perform pet dog operation (using appropriate system call), output on successfeed dog okand pause for 1 second, output on failurefeed dog errorand return -2.
  4. End prompt: After completing the watchdog feeding loop, outputhungry dog, and finally the program terminates normally with a return value of 0.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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;

}