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.
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:
Initialization Configuration
Set the watchdog timeout period and enable the watchdog timer
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.
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
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
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
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:
staticintdw_wdt_drv_probe(struct platform_device *pdev) { // Get the device structure pointer for subsequent operations structdevice *dev = &pdev->dev; // Define a pointer to the watchdog device structure structwatchdog_device *wdd; // Define a pointer to the dw_wdt structure pointer, dw_wdt structure may contain specific information about the watchdog device structdw_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;
// 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;
/** 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. */ structwatchdog_device {// Define the structure representing the watchdog device int id;// Device unique identifier structdevice *parent;// Pointer to the parent device conststructattribute_group **groups;// Pointer to an array of attribute groups for managing device attributes conststructwatchdog_info *info;// Pointer to a structure containing device details conststructwatchdog_ops *ops;// Pointer to a structure containing device operation functions conststructwatchdog_governor *gov;// Pointer to a supervision-related structure, possibly for regulating device operation // Startup status flag, with specific meaning defined elsewhere unsignedint bootstatus; unsignedint timeout; unsignedint pretimeout; unsignedint min_timeout; unsignedint max_timeout; unsignedint min_hw_heartbeat_ms; unsignedint max_hw_heartbeat_ms; structnotifier_blockreboot_nb; structnotifier_blockrestart_nb; void *driver_data; structwatchdog_core_data *wd_data; unsignedlong 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 */ structlist_headdeferred; };
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 structwatchdog_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:
/** 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. */ structwatchdog_ops { // Pointer to the module that owns this set of operation functions, typically used for module reference counting and other management. structmodule *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. unsignedint(*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 *, unsignedint);// 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 *, unsignedint);// 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. unsignedint(*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 *, unsignedlong, 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 *, unsignedint, unsignedlong);// 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
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:
// Define a structure named dw_wdt to store various information related to a specific watchdog device structdw_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 structclk *clk; // Pointer to the APB clock source, possibly used for APB-related clock operations (depending on the device) structclk *pclk; // Stores values related to clock frequency, possibly information such as the clock frequency used by the current device. unsignedlong rate; enumdw_wdt_rmodrmod; structdw_wdt_timeouttimeouts[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. structwatchdog_devicewdd; // Pointer to the reset control structure, used for performing reset-related operations and control on the device. structreset_control *rst; /* Save/restore *//* 以下成员可能用于保存和恢复设备的某些状态信息 */ u32 control;// Stores control-related information of the device. u32 timeout;// Stores timeout setting-related information of the device.
The above probe functiondw_wdt_drv_probe()useswatchdog_register_device()function towddthe structure pointed to by thestruct watchdog_devicestructure for registration operation.
/** * 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. */
intwatchdog_register_device(struct watchdog_device *wdd) { constchar *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);
/* 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;
/* * 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. */
intwatchdog_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_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. */
staticintwatchdog_cdev_register(struct watchdog_device *wdd) { // Core data structure (includes device status, timers, etc.) structwatchdog_core_data *wd_data; int err;
// 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); }
return0; }
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
staticvoidwatchdog_ping_work(struct kthread_work *work) { // Obtain core data structure through work item structwatchdog_core_data *wd_data;
// 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
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_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. */
/* 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);
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:
/* * 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). */
/* * 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:
/* * 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. */
/* 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
Open device file: open/dev/watchdog0, open in read-write mode, output on failureopen errorand return -1.
Set timeout: after successful opening, set timeout to 2 seconds via system call, output on failureset time out errorand return -2.
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.
End prompt: After completing the watchdog feeding loop, outputhungry dog, and finally the program terminates normally with a return value of 0.