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 字符设备(动态主设备号,次设备号为 0),也有旧的 /dev/watchdog 杂项设备。在调用 watchdog_register_device 时会自动设置 id

  • parent: 在调用 watchdog_register_device 之前,将其设置为父设备(或 NULL)。

  • groups: 创建看门狗设备时要创建的 sysfs 属性组列表。

  • info: 指向 watchdog_info 结构的指针。此结构提供了关于看门狗定时器本身的一些额外信息。(例如其唯一名称)

  • ops: 指向看门狗支持的看门狗操作列表的指针。

  • gov: 指向分配的看门狗设备预超时调节器(governor)的指针,或 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' 秒内未能发送心跳。如果驱动程序未实现停止功能,则必须设置 max_hw_heartbeat_ms

  • reboot_nb: 用于重启通知的通知块,仅供内部使用。如果驱动程序调用 watchdog_stop_on_reboot,看门狗核心将在收到此类通知时停止看门狗。

  • restart_nb: 用于机器重启的通知块,仅供内部使用。如果看门狗能够重启机器,它应该定义 ops->restart。优先级可以通过 watchdog_set_restart_priority 更改。

  • bootstatus: 设备启动后的状态(通过看门狗 WDIOF_* 状态位报告)。

  • driver_data: 指向看门狗设备的驱动程序私有数据的指针。此数据只能通过 watchdog_set_drvdatawatchdog_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 代码。有些看门狗定时器硬件只能启动而不能停止。支持此类硬件的驱动程序不必实现停止例程。

    如果驱动程序没有停止功能,看门狗核心将在看门狗设备关闭后设置 WDOG_HW_RUNNING 并开始调用驱动程序的保活(keepalive)ping 功能。

    如果看门狗驱动程序未实现停止功能,则必须设置 max_hw_heartbeat_ms

  • ping: 这是向看门狗定时器硬件发送保活 ping 的例程。

    该例程需要一个指向看门狗定时器设备结构的指针作为参数。它在成功时返回零,在失败时返回负的 errno 代码。

    大多数不支持将其作为单独功能的硬件都使用 start 功能重启看门狗定时器硬件。这也是看门狗定时器驱动核心所做的:向看门狗定时器硬件发送保活 ping 时,它将使用 ping 操作(如果可用)或 start 操作(如果 ping 操作不可用)。

    (注意:WDIOC_KEEPALIVE ioctl 调用仅在看门狗信息结构的 option 字段中设置了 WDIOF_KEEPALIVEPING 位时才有效。)

  • status: 此例程检查看门狗定时器设备的状态。设备的状态通过看门狗 WDIOF_* 状态标志/位报告。

    WDIOF_MAGICCLOSEWDIOF_KEEPALIVEPING 由看门狗核心报告;驱动程序无需报告这些位。此外,如果驱动程序未提供状态函数,看门狗核心将报告 struct watchdog_devicebootstatus 变量中提供的状态位。

  • set_timeout: 此例程检查并更改看门狗定时器设备的超时。成功时返回 0,参数超出范围时返回 -EINVAL,无法写入看门狗值时返回 -EIO。成功时,此例程应将 watchdog_device 的超时值设置为实际达到的超时值(可能与请求的值不同,因为看门狗不一定具有 1 秒的分辨率)。

    实现 max_hw_heartbeat_ms 的驱动程序将硬件看门狗心跳设置为超时和 max_hw_heartbeat_ms 中的最小值。这些驱动程序将 watchdog_device 的超时值设置为请求的超时值(如果它大于 max_hw_heartbeat_ms),或者设置为实际达到的超时值。(注意:需要在看门狗信息结构的 options 字段中设置 WDIOF_SETTIMEOUT。)

    如果看门狗驱动程序除了设置 watchdog_device.timeout 之外不需要执行任何操作,则可以省略此回调。

    如果未提供 set_timeout 但设置了 WDIOF_SETTIMEOUT,则看门狗基础设施会在内部将 watchdog_device 的超时值更新为请求的值。

    如果使用预超时功能(WDIOF_PRETIMEOUT),则 set_timeout 还必须负责检查预超时是否仍然有效并相应地设置定时器。这在核心中无法无竞争地完成,因此是驱动程序的职责。

  • set_pretimeout: 此例程检查并更改看门狗的预超时值。它是可选的,因为并非所有看门狗都支持预超时通知。超时值不是绝对时间,而是实际超时发生前的时间(以秒为单位)。成功时返回 0,参数超出范围时返回 -EINVAL,无法写入看门狗值时返回 -EIO。值为 0 会禁用预超时通知。

    (注意:需要在看门狗信息结构的 options 字段中设置 WDIOF_PRETIMEOUT。)

    如果看门狗驱动程序除了设置 watchdog_device.pretimeout 之外不需要执行任何操作,则可以省略此回调。这意味着如果未提供 set_pretimeout 但设置了 WDIOF_PRETIMEOUT,看门狗基础设施会在内部将 watchdog_device 的预超时值更新为请求的值。

  • get_timeleft: 此例程返回重置前剩余的时间。

  • restart: 此例程重启机器。它在成功时返回 0,在失败时返回负的 errno 代码。

  • ioctl: 如果此例程存在,它将在我们进行内部 ioctl 调用处理之前首先被调用。如果不支持某个命令,此例程应返回 -ENOIOCTLCMD。传递给 ioctl 调用的参数是:watchdog_devicecmdarg

状态位应(最好)使用 set_bitclear_bit 等位操作设置。定义的状态位包括

  • WDOG_ACTIVE: 此状态位指示看门狗定时器设备是否从用户角度来看处于活跃状态。当此标志设置时,用户空间应向驱动程序发送心跳请求。

  • WDOG_NO_WAY_OUT: 此位存储看门狗的 nowayout 设置。如果设置了此位,看门狗定时器将无法停止。

  • WDOG_HW_RUNNING: 如果硬件看门狗正在运行,由看门狗驱动程序设置。如果看门狗定时器硬件无法停止,则必须设置此位。如果看门狗定时器在引导后、看门狗设备打开之前正在运行,也可以设置此位。如果设置了此位,看门狗基础设施将在 WDOG_ACTIVE 未设置时向看门狗硬件发送保活信号。注意:当您注册设置了此位的看门狗定时器设备时,打开 /dev/watchdog 将跳过 start 操作,而是发送保活请求。

    要设置 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)功能。要使用“魔术关闭”功能,您必须在看门狗信息结构的 options 字段中设置 WDIOF_MAGICCLOSE 位。

“无法退出”功能将覆盖“魔术关闭”功能。

要获取或设置驱动程序特定数据,应使用以下两个辅助函数

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 函数允许您检索驱动程序特定数据。此函数的参数是您要从中检索数据的看门狗设备。该函数返回指向驱动程序特定数据的指针。

要初始化 timeout 字段,可以使用以下函数

extern int watchdog_init_timeout(struct watchdog_device *wdd,
                                 unsigned int timeout_parm,
                                 struct device *dev);

watchdog_init_timeout 函数允许您使用模块超时参数或从设备树中检索 timeout-sec 属性(如果模块超时参数无效)来初始化 timeout 字段。最佳实践是将默认超时值设置为 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 毫秒发生。仅当在调用探测(probe)时看门狗已在运行,并且看门狗只能在上次 ping 后经过 min_hw_heartbeat_ms 时间后才能被 ping 时,才需要调用此函数。