Linux 看门狗定时器驱动核心内核 API¶
最后审核:2013 年 2 月 12 日
Wim Van Sebroeck <wim@iguana.be>
引言¶
本文档不描述什么是看门狗定时器 (WDT) 驱动程序或设备。它也不描述用户空间可以用来与看门狗定时器通信的 API。如果您想了解这些,请阅读以下文件: Linux 看门狗驱动程序 API 。
那么本文档描述的是什么呢?它描述了想要使用看门狗定时器驱动核心框架的看门狗定时器驱动程序可以使用的 API。此框架提供与用户空间的所有接口,以便不必每次都重复相同的代码。这也意味着看门狗定时器驱动程序只需要提供控制看门狗定时器 (WDT) 的不同例程(操作)。
API¶
每个想要使用看门狗定时器驱动核心的看门狗定时器驱动程序都必须 #include <linux/watchdog.h>(在编写看门狗设备驱动程序时无论如何都需要这样做)。此包含文件包含以下注册/注销例程
extern int watchdog_register_device(struct watchdog_device *);
extern void watchdog_unregister_device(struct watchdog_device *);
watchdog_register_device 例程注册一个看门狗定时器设备。此例程的参数是指向 watchdog_device 结构的指针。此例程成功时返回零,失败时返回负的 errno 代码。
watchdog_unregister_device 例程注销已注册的看门狗定时器设备。此例程的参数是指向已注册的 watchdog_device 结构的指针。
看门狗子系统包含一个注册延迟机制,允许您在启动过程中尽早注册看门狗。
看门狗设备结构如下所示
struct watchdog_device {
int id;
struct device *parent;
const struct attribute_group **groups;
const struct watchdog_info *info;
const struct watchdog_ops *ops;
const struct watchdog_governor *gov;
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;
struct list_head deferred;
};
它包含以下字段
id:由 watchdog_register_device 设置,id 0 是特殊的。它既有 /dev/watchdog0 cdev(动态主设备号,次设备号 0)也有旧的 /dev/watchdog miscdev。id 在调用 watchdog_register_device 时自动设置。
parent:在调用 watchdog_register_device 之前将其设置为父设备(或 NULL)。
groups:创建看门狗设备时要创建的 sysfs 属性组列表。
info:指向 watchdog_info 结构的指针。此结构提供有关看门狗定时器本身的一些附加信息。(例如其唯一名称)
ops:指向看门狗支持的操作列表的指针。
gov:指向分配的看门狗设备预超时调速器或 NULL 的指针。
timeout:看门狗定时器的超时值(以秒为单位)。如果设置了 WDOG_ACTIVE,则这是用户空间未发送心跳请求后系统将重新启动的时间。
pretimeout:看门狗定时器的预超时值(以秒为单位)。
min_timeout:看门狗定时器的最小超时值(以秒为单位)。如果设置,则为“timeout”的最小可配置值。
max_timeout:看门狗定时器的最大超时值(以秒为单位),从用户空间可见。如果设置,则为“timeout”的最大可配置值。如果 max_hw_heartbeat_ms 非零,则不使用。
min_hw_heartbeat_ms:硬件限制,用于心跳之间的最短时间,以毫秒为单位。此值通常为 0;仅当硬件不能容忍心跳之间的较低间隔时才应提供此值。
max_hw_heartbeat_ms:最大硬件心跳,以毫秒为单位。如果设置,如果“timeout”大于 max_hw_heartbeat_ms,则除非设置了 WDOG_ACTIVE 且用户空间至少在“timeout”秒内未能发送心跳,否则基础架构会将心跳发送到看门狗驱动程序。如果驱动程序未实现 stop 函数,则必须设置 max_hw_heartbeat_ms。
reboot_nb:为重新启动通知注册的通知器块,仅供内部使用。如果驱动程序调用 watchdog_stop_on_reboot,看门狗核心将在收到此类通知时停止看门狗。
restart_nb:为机器重新启动注册的通知器块,仅供内部使用。如果看门狗能够重新启动机器,则应定义 ops->restart。可以通过 watchdog_set_restart_priority 更改优先级。
bootstatus:启动后设备的运行状态(使用看门狗 WDIOF_* 状态位报告)。
driver_data:指向看门狗设备驱动程序私有数据的指针。此数据应仅通过 watchdog_set_drvdata 和 watchdog_get_drvdata 例程访问。
wd_data:指向看门狗核心内部数据的指针。
status:此字段包含多个状态位,提供有关设备状态的额外信息(例如:看门狗定时器是否正在运行/活动,或者是否设置了 nowayout 位)。
deferred:wtd_deferred_reg_list 中的条目,用于注册提前初始化的看门狗。
看门狗操作列表定义为
struct watchdog_ops {
struct module *owner;
/* mandatory operations */
int (*start)(struct watchdog_device *);
/* optional operations */
int (*stop)(struct watchdog_device *);
int (*ping)(struct watchdog_device *);
unsigned int (*status)(struct watchdog_device *);
int (*set_timeout)(struct watchdog_device *, unsigned int);
int (*set_pretimeout)(struct watchdog_device *, unsigned int);
unsigned int (*get_timeleft)(struct watchdog_device *);
int (*restart)(struct watchdog_device *);
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
};
重要的是,您首先要定义看门狗定时器驱动程序操作的模块所有者。当看门狗处于活动状态时,此模块所有者将用于锁定模块。(这样可以避免在卸载模块且 /dev/watchdog 仍处于打开状态时发生系统崩溃)。
某些操作是强制性的,而某些操作是可选的。强制操作是
start:这是指向启动看门狗定时器设备的例程的指针。该例程需要一个指向看门狗定时器设备结构的指针作为参数。成功时返回零,失败时返回负的 errno 代码。
并非所有看门狗定时器硬件都支持相同的功能。这就是为什么所有其他例程/操作都是可选的原因。只有在支持的情况下才需要提供它们。这些可选的例程/操作是
stop:使用此例程,将停止看门狗定时器设备。
该例程需要一个指向看门狗定时器设备结构的指针作为参数。成功时返回零,失败时返回负的 errno 代码。某些看门狗定时器硬件只能启动而不能停止。支持此类硬件的驱动程序不必实现 stop 例程。
如果驱动程序没有 stop 函数,看门狗核心将设置 WDOG_HW_RUNNING,并在看门狗设备关闭后开始调用驱动程序的 keepalive ping 函数。
如果看门狗驱动程序未实现 stop 函数,则必须设置 max_hw_heartbeat_ms。
ping:这是将 keepalive ping 发送到看门狗定时器硬件的例程。
该例程需要一个指向看门狗定时器设备结构的指针作为参数。成功时返回零,失败时返回负的 errno 代码。
大多数不支持此作为单独功能的硬件都使用 start 函数来重新启动看门狗定时器硬件。这也是看门狗定时器驱动程序核心的作用:为了将 keepalive ping 发送到看门狗定时器硬件,它将使用 ping 操作(如果可用)或 start 操作(如果 ping 操作不可用)。
(注意:仅当在看门狗的 info 结构的 option 字段中设置了 WDIOF_KEEPALIVEPING 位时,WDIOC_KEEPALIVE ioctl 调用才会处于活动状态)。
status:此例程检查看门狗定时器设备的状态。设备的状态使用看门狗 WDIOF_* 状态标志/位报告。
WDIOF_MAGICCLOSE 和 WDIOF_KEEPALIVEPING 由看门狗核心报告;无需从驱动程序报告这些位。此外,如果驱动程序未提供 status 函数,则看门狗核心将报告 struct watchdog_device 的 bootstatus 变量中提供的状态位。
set_timeout:此例程检查和更改看门狗定时器设备的超时。成功时返回 0,参数超出范围时返回 -EINVAL,无法将值写入看门狗时返回 -EIO。成功时,此例程应将 watchdog_device 的超时值设置为实现的超时值(这可能与请求的值不同,因为看门狗不一定具有 1 秒的分辨率)。
实现 max_hw_heartbeat_ms 的驱动程序将硬件看门狗心跳设置为 timeout 和 max_hw_heartbeat_ms 中的最小值。这些驱动程序将 watchdog_device 的超时值设置为请求的超时值(如果它大于 max_hw_heartbeat_ms),或设置为实现的超时值。(注意:需要在看门狗的 info 结构的 options 字段中设置 WDIOF_SETTIMEOUT)。
如果看门狗驱动程序不必执行任何操作,只需设置 watchdog_device.timeout,则可以省略此回调。
如果未提供 set_timeout,但设置了 WDIOF_SETTIMEOUT,则看门狗基础架构会在内部将 watchdog_device 的超时值更新为请求的值。
如果使用了预超时功能(WDIOF_PRETIMEOUT),那么 `set_timeout` 也必须负责检查预超时是否仍然有效,并相应地设置定时器。这在核心代码中无法避免竞争条件,因此这是驱动程序的职责。
set_pretimeout:此例程检查并更改看门狗的预超时值。它是可选的,因为并非所有看门狗都支持预超时通知。超时值不是绝对时间,而是实际超时发生前的秒数。成功时返回 0,参数超出范围时返回 -EINVAL,无法将值写入看门狗时返回 -EIO。值为 0 会禁用预超时通知。
(注意:需要在看门狗的 info 结构的 options 字段中设置 WDIOF_PRETIMEOUT)。
如果看门狗驱动程序不需要执行任何操作,只需设置 watchdog_device.pretimeout,则可以省略此回调。这意味着,如果未提供 set_pretimeout 但设置了 WDIOF_PRETIMEOUT,则看门狗基础设施会在内部将 watchdog_device 的预超时值更新为请求的值。
get_timeleft:此例程返回重置前剩余的时间。
restart:此例程重新启动机器。成功时返回 0,失败时返回负 errno 代码。
ioctl:如果存在此例程,则将在我们进行内部 ioctl 调用处理之前首先调用它。如果不支持命令,则此例程应返回 -ENOIOCTLCMD。传递给 ioctl 调用的参数为:watchdog_device,cmd 和 arg。
状态位应(最好)使用 `set_bit` 和 `clear_bit` 等类似的位操作来设置。定义的状态位如下:
WDOG_ACTIVE:此状态位指示从用户的角度来看,看门狗定时器设备是否处于活动状态。当设置此标志时,用户空间应向驱动程序发送心跳请求。
WDOG_NO_WAY_OUT:此位存储看门狗的 nowayout 设置。如果设置了此位,则看门狗定时器将无法停止。
WDOG_HW_RUNNING:如果硬件看门狗正在运行,则由看门狗驱动程序设置。如果看门狗定时器硬件无法停止,则必须设置此位。如果在启动后看门狗定时器正在运行,并且在打开看门狗设备之前,也可能设置此位。如果设置,看门狗基础设施将在 WDOG_ACTIVE 未设置时向看门狗硬件发送保持活动信号。注意:当您注册设置了此位的看门狗定时器设备时,打开 /dev/watchdog 将跳过启动操作,而是发送保持活动请求。
要设置 WDOG_NO_WAY_OUT 状态位(在注册看门狗定时器设备之前),您可以:
在您的 watchdog_device 结构中静态设置:
.status = WATCHDOG_NOWAYOUT_INIT_STATUS,
(这将设置与 CONFIG_WATCHDOG_NOWAYOUT 相同的值)或者
使用以下辅助函数:
static inline void watchdog_set_nowayout(struct watchdog_device *wdd, int nowayout)
- 注意
看门狗定时器驱动程序核心支持 magic close 功能和 nowayout 功能。要使用 magic close 功能,您必须在看门狗的 info 结构的 options 字段中设置 WDIOF_MAGICCLOSE 位。
nowayout 功能将覆盖 magic close 功能。
要获取或设置驱动程序特定的数据,应使用以下两个辅助函数:
static inline void watchdog_set_drvdata(struct watchdog_device *wdd,
void *data)
static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
watchdog_set_drvdata 函数允许您添加驱动程序特定的数据。此函数的参数是您要向其添加驱动程序特定数据的看门狗设备以及指向数据本身的指针。
watchdog_get_drvdata 函数允许您检索驱动程序特定的数据。此函数的参数是您要从中检索数据的看门狗设备。该函数返回指向驱动程序特定数据的指针。
要初始化超时字段,可以使用以下函数:
extern int watchdog_init_timeout(struct watchdog_device *wdd,
unsigned int timeout_parm,
struct device *dev);
watchdog_init_timeout 函数允许您使用模块超时参数或从设备树检索 timeout-sec 属性(如果模块超时参数无效)来初始化超时字段。最佳实践是将默认超时值设置为 watchdog_device 中的超时值,然后使用此函数设置用户“首选”的超时值。此例程成功时返回零,失败时返回负 errno 代码。
要在重新启动时禁用看门狗,用户必须调用以下辅助函数:
static inline void watchdog_stop_on_reboot(struct watchdog_device *wdd);
要在注销看门狗时禁用看门狗,用户必须调用以下辅助函数。请注意,只有在未设置 nowayout 标志的情况下,此操作才会停止看门狗。
static inline void watchdog_stop_on_unregister(struct watchdog_device *wdd);
要更改重新启动处理程序的优先级,应使用以下辅助函数:
void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority);
用户应遵循以下准则来设置优先级:
0:应在最后时刻调用,具有有限的重新启动能力
128:默认的重新启动处理程序,如果预计没有其他处理程序可用,或者如果重新启动足以重新启动整个系统,则使用此处理程序
255:最高优先级,将抢占所有其他重新启动处理程序
要引发预超时通知,应使用以下函数:
void watchdog_notify_pretimeout(struct watchdog_device *wdd)
该函数可以在中断上下文中调用。如果启用了看门狗预超时管理框架(kbuild CONFIG_WATCHDOG_PRETIMEOUT_GOV 符号),则由预先分配给看门狗设备的预配置的预超时管理器执行操作。如果未启用看门狗预超时管理框架,watchdog_notify_pretimeout() 会将通知消息打印到内核日志缓冲区。
要设置看门狗的最后一次已知硬件保持活动时间,应使用以下函数:
int watchdog_set_last_hw_keepalive(struct watchdog_device *wdd,
unsigned int last_ping_ms)
必须在看门狗注册后立即调用此函数。它将最后一次已知的硬件心跳设置为发生在当前时间之前的 last_ping_ms。仅当在调用探测时看门狗已经在运行时才需要调用此函数,并且只有在距离上次 ping 经过 min_hw_heartbeat_ms 时间后才能 ping 看门狗。