Skip to content

Latest commit

 

History

History
494 lines (355 loc) · 32.4 KB

File metadata and controls

494 lines (355 loc) · 32.4 KB

十三、看门狗设备驱动

看门狗是一种硬件(有时由软件模拟)设备,旨在确保给定系统的可用性。 它有助于确保系统始终在关键挂起时重新启动,从而允许监视系统的“正常”行为。

无论它是基于硬件的,还是由软件模拟的,大多数情况下,看门狗只是一个用合理的超时初始化的计时器,应该由运行在被监控系统上的软件定期刷新。 如果由于任何原因,软件在计时器到期(运行到超时)之前停止/失败刷新计时器(并且没有显式关闭它),这将触发整个系统(在我们的例子中是计算机)的(硬件)重置。 这样的机制甚至可以帮助从内核恐慌中恢复。 在本章结束时,您将能够执行以下操作:

  • 阅读/理解现有的 Watchdog 内核驱动,并使用它在用户空间中公开的内容。
  • 编写新的看门狗设备驱动。
  • 掌握一些不太熟悉的概念,如看门狗调控器预超时

在本章中,我们还将通过以下主题介绍 Linux 内核监视器子系统背后的概念:

  • 看门狗数据结构和 API
  • 看门狗用户空间接口

技术要求

在我们开始阅读本章之前,需要具备以下要素:

看门狗数据结构和 API

在本节中,我们将介绍 Watchdog 框架,并了解它是如何在幕后工作的。 看门狗子系统有几个数据结构。 主要参数是struct watchdog_device,它是看门狗设备的 Linux 内核表示,包含有关它的所有信息。 在include/linux/watchdog.h中定义如下:

struct watchdog_device {
    int id;
    struct device *parent;
    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;
    struct watchdog_core_data *wd_data;
    unsigned long status;
    [...]
};

以下是对此数据结构中字段的说明:

  • id:设备注册期间内核分配的看门狗 ID。

  • parent:表示此设备的父级。

  • info:此struct watchdog_info结构指针提供有关看门狗计时器本身的一些附加信息。 这是在看门狗充电设备上调用WDIOC_GETSUPPORTioctl 以检索其功能时返回给用户的结构。 我们稍后将详细介绍此结构。

  • ops:指向看门狗操作列表的指针。 我们稍后将再次介绍此数据结构。

  • gov:指向看门狗预超时调控器的指针。 调控器只不过是根据特定事件或系统参数做出反应的策略管理器。

  • bootstatus:启动时看门狗设备的状态。 这是触发系统重置的原因的位掩码。 稍后在描述struct watchdog_info结构时将列举可能的值。

  • timeout:这是看门狗设备的超时值(秒)。

  • pretimeout: The concept of pretimeout can be explained as an event that occurs sometime before the real timeout occurs, so if the system is in an unhealthy state, it triggers an interrupt before the real timeout reset. These interrupts are usually non-maskable ones (NMI, which stands for Non-Maskable Interrupt), and this can be used to secure important data and shut down specific applications or panic the system (which allows us to gather useful information prior to the reset, instead of a blind and sudden reboot).

    在此上下文中,pretimeout字段实际上是触发实际超时中断之前的时间间隔(秒数)。 这不是距离预超时的秒数。 例如,如果将超时设置为60秒,将预超时值设置为10秒,则会在50秒内触发预超时事件。 将预超时值设置为0会禁用它。

  • min_timeoutmax_timeout分别是看门狗设备的最小和最大超时值(以秒为单位)。 这些实际上是有效超时范围的上下限。 如果值为 0,则框架将检查看门狗驱动本身。

  • wd_data:指向看门狗核心内部数据的指针。 此字段必须通过watchdog_set_drvdata()watchdog_get_drvdata()帮助器访问。

  • status is a field that contains the device's internal status bits. Possible values are listed here:

    --WDOG_ACTIVE:指示看门狗是否正在运行/活动。

    --WDOG_NO_WAY_OUT:通知是否设置了nowayout功能。 您可以使用watchdog_set_nowayout()设置nowayout功能;其签名为void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)

    --WDOG_STOP_ON_REBOOT:重启时应停止。

    --WDOG_HW_RUNNING:通知硬件看门狗正在运行。 您可以使用watchdog_hw_running()帮助器检查是否设置了此标志。 但是,您应该在看门狗的启动功能的成功路径上设置此标志(如果出于任何原因在那里启动或发现看门狗已经启动,则在探测功能中设置此标志)。 为此,您可以使用set_bit()帮助器。

    --WDOG_STOP_ON_UNREGISTER:指定取消注册时应停止监视器。 您可以使用watchdog_stop_on_unregister()帮助器来设置此标志。

正如我们前面介绍的那样,让我们详细研究在include/uapi/linux/watchdog.h中定义的struct watchdog_info结构,因为它实际上是用户空间 API 的一部分:

struct watchdog_info {
    u32 options;
    u32 firmware_version;
    u8 identity[32];
};

此结构也是在WDIOC_GETSUPPORTioctl 的成功路径上返回到用户空间的结构。 在此结构中,字段具有以下含义:

  • options represents the supported capabilities of the card/driver. It is a bitmask of the capabilities supported by the watchdog device/driver since some watchdog cards offer more than just a countdown. Some of these flags may also be set in the watchdog_device.bootstatus field in response to the GET_BOOT_STATUS ioctl. These flags are listed as follows, with dual explanations given where necessary:

    --WDIOF_SETTIMEOUT表示可以设置看门狗设备的超时。 如果设置了此标志,则必须定义set_timeout回调。

    --WDIOF_MAGICCLOSE表示驱动支持魔术关闭字符功能。 由于关闭看门狗字符设备文件不会停止看门狗,此功能意味着在此看门狗文件中写入V字符(也称为魔术字符或魔术V)序列将允许下一次关闭以关闭看门狗(如果未设置nowayout)。

    --WDIOF_POWERUNDER表示设备可以监控/检测坏电源或电源故障。 当在watchdog_device.bootstatus中设置时,该标志表示触发复位的是机器显示欠压这一事实。

    --WDIOF_POWEROVER,另一方面,意味着设备可以监控工作电压。 在watchdog_device.bootstatus中设置时,表示系统复位可能是由于过电压状态。 请注意,如果一个电平在下,一个电平在上,则两个位都将被设置。

    --WDIOF_OVERHEAT表示看门狗设备可以监控芯片/SoC 温度。 在watchdog_device.bootstatus中设置时,表示上次通过看门狗重新启动机器的原因是由于超过了热限制。

    --WDIOF_FANFAULT通知我们此看门狗设备可以监控风扇。 设置时,表示看门狗卡监控的系统风扇出现故障。

    有些设备甚至都有单独的事件输入。 如果定义,这些输入上的电信号为,这也会导致复位。 这就是WDIOF_EXTERN1WDIOF_EXTERN2的目的。 在watchdog_device.bootstatus中设置时,表示机器最后一次重启是因为外部继电器/源 1 或 2。

    --WDIOF_PRETIMEOUT表示该看门狗设备支持预超时功能。

    --WDIOF_KEEPALIVEPING表示该驱动支持WDIOC_KEEPALIVEioctl(可以通过 ioctl ping);否则,ioctl 将返回-EOPNOTSUPP。 在watchdog_device.bootstatus中设置时,此标志表示看门狗自上次查询以来看到了保持活动状态的 ping。

    --WDIOF_CARDRESET:这是一个特殊标志,只能出现在watchdog_device.bootstatus中。 这意味着上一次重启是由看门狗本身引起的(实际上是它的超时)。

  • firmware_version是卡的固件版本。

  • identity应该是描述设备的字符串。

另一种数据结构是struct watchdog_ops,其定义如下:

struct watchdog_ops { struct module *owner;
    /* mandatory operations */
    int (*start)(struct watchdog_device *);
    int (*stop)(struct watchdog_device *);
    /* optional operations */
    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 *,                    unsigned long, void *);
    long (*ioctl)(struct watchdog_device *, unsigned int,
                  unsigned long);
};

前面的结构包含看门狗设备上允许的操作列表。 每个操作的含义如下所示:

  • startstop:这些是分别启动和停止看门狗的强制操作。
  • ping回调用于向看门狗发送保活 ping。 此方法是可选的。 如果未定义,则看门狗将通过.start操作重新启动,因为这将意味着看门狗没有自己的 ping 方法。
  • status是返回看门狗设备状态的可选例程。 如果定义,它的返回值将被发送以响应WDIOC_GETBOOTSTATUSioctl。
  • set_timeout是设置看门狗超时值的回调(以秒为单位)。 如果已定义,还应设置X选项标志;否则,任何设置超时的尝试都将导致-EOPNOTSUPP错误。
  • set_pretimeout是设置预超时的回调。 如果已定义,还应设置WDIOF_PRETIMEOUT选项标志;否则,任何设置预超时的尝试都将导致-EOPNOTSUPP错误。
  • get_timeleft是一个可选操作,返回重置前的剩余秒数。
  • restart:这实际上是重启机器(不是看门狗设备)的例程。 如果设置,您可能想要在看门狗设备上调用watchdog_set_restart_priority(),以便在向系统注册看门狗之前设置此重启处理程序的优先级。
  • ioctl:您不应该实现此回调,除非您必须这样做-例如,如果您需要处理额外的/非标准的 ioctl 命令。 如果已定义,此方法将覆盖看门狗内核的默认 ioctl,除非它返回-ENOIOCTLCMD

此结构包含设备根据其功能支持的回调函数。

现在我们已经熟悉了数据结构,我们可以切换到 Watchdog API,特别是看看如何在系统中注册和取消注册这样的设备 w。

注册/注销看门狗设备

看门狗框架提供两个基本功能,用于向系统注册/注销看门狗设备。 这些是watchdog_register_device()watchdog_unregister_device(),它们各自的原型如下所示:

int watchdog_register_device(struct watchdog_device *wdd)
void watchdog_unregister_device(struct watchdog_device *wdd)

前面的注册方法在成功路径上返回 0,在失败时返回负的errno代码。 另一方面,watchdog_unregister_device()执行相反的操作。 为了不再为注销而烦恼,您可以使用此函数的托管版本devm_watchdog_register_device,其原型如下所示:

int devm_watchdog_register_device(struct device *dev,                                   struct watchdog_device *wdd)

前面的托管版本将在分离驱动时自动处理注销。

注册方法(无论它是什么,是否托管)将检查是否提供了wdd->ops->restart函数,并将此方法注册为重新启动处理程序。 因此,在向系统注册看门狗设备之前,驱动应使用watchdog_set_restart_priority()帮助器设置重启优先级,因为知道重启处理程序的优先级值应遵循以下准则:

  • 0:这是最低优先级,这意味着在系统中没有提供其他重启处理程序时,将看门狗的重启功能作为最后手段使用。
  • 128:这是默认优先级,表示如果预计没有其他处理程序可用和/或如果重启足以重启整个系统,则默认使用此重启处理程序。
  • 255:这是最高优先级,抢占所有其他处理程序。

设备注册应该仅在您处理完我们讨论的所有元素之后才能完成;也就是说,在提供了与监视器设备的有效.info.ops和超时相关的字段之后。 在此之前,应该为watchdog_device结构分配内存空间。 将此结构包装在更大的每个驱动的数据结构中是很好的做法,如下面的示例所示,该示例摘录自drivers/watchdog/imx2_wdt.c

[...]
struct imx2_wdt_device {
    struct clk *clk;
    struct regmap *regmap;
    struct watchdog_device wdog;
    bool ext_reset;
};

您可以看到看门狗设备数据结构是如何嵌入到更大的结构struct imx2_wdt_device中的。 现在是probe方法,初始化所有内容,并在更大的结构中设置看门狗设备:

static int init imx2_wdt_probe(struct platform_device *pdev)
{
    struct imx2_wdt_device *wdev;
    struct watchdog_device *wdog; int ret;
    [...]
    wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
    if (!wdev)
        return -ENOMEM;
    [...]
    Wdog = &wdev->wdog;
    if (imx2_wdt_is_running(wdev)) {
        imx2_wdt_set_timeout(wdog, wdog->timeout); 
        set_bit(WDOG_HW_RUNNING, &wdog->status);
    }
    ret = watchdog_register_device(wdog);
    if (ret) {
        dev_err(&pdev->dev, "cannot register watchdog device\n");
        [...]
    }
    return 0;
}
static int exit imx2_wdt_remove(struct platform_device *pdev)
{
    struct watchdog_device *wdog = platform_get_drvdata(pdev);
    struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
    watchdog_unregister_device(wdog);
    if (imx2_wdt_is_running(wdev)) {
      imx2_wdt_ping(wdog);
      dev_crit(&pdev->dev, "Device removed: Expect reboot!\n");
    }
    return 0;
}
[...]

此外,在move方法中可以使用较大的结构来跟踪设备状态,特别是其中嵌入的看门狗数据结构。 这就是前面的代码摘录强调的内容。

到目前为止,我们已经介绍了 Watchdog 基础知识,介绍了基本数据结构,并描述了主要 API。 现在,我们可以了解一些奇特的特性,如预超时和调控器,以便在看门狗事件中定义系统的行为。

处理预超时和调控器

调控器的概念出现在 Linux 内核的几个子系统中(热调控器、CPUFreq 调控器,现在是看门狗调控器)。 它只是一个实现策略管理(有时以算法的形式)的驱动,它对系统的某些状态/事件做出反应。

每个子系统实现其调控器驱动的方式可能与其他子系统不同,但其主要思想是相同的。 此外,调控器由唯一的名称和正在使用的调控器(策略管理器)标识。 它们可以动态更改,通常是从 sysfs 界面中更改。

现在,回到看门狗预超时和调控器。 通过启用CONFIG_WATCHDOG_PRETIMEOUT_GOV内核配置选项,可以将对它们的支持添加到 Linux 内核中。 内核中实际上有两个看门狗调控器驱动:drivers/watchdog/pretimeout_noop.cdrivers/watchdog/pretimeout_panic.c。 它们的唯一名称分别是nooppanic。 默认情况下,可以通过启用CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOPCONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC来使用这两个选项。

本节的主要目标是将预超时事件传递给当前处于活动状态的看门狗调控器。 这可以通过watchdog_notify_pretimeout()接口实现,该接口具有以下原型:

void watchdog_notify_pretimeout(struct watchdog_device *wdd)

正如我们已经讨论过的,一些看门狗设备会生成 IRQ 以响应预超时事件。 主要思想是从该 IRQ 处理程序内部调用watchdog_notify_pretimeout()。 在幕后,此接口将在内部找到监视程序调控器(通过在系统中注册的监视程序调控器的全局列表中查找其名称),并调用其.pretimeout回调。

仅供参考,以下是看门狗调控器结构的外观(您可以通过查看drivers/watchdog/pretimeout_noop.cdrivers/watchdog/pretimeout_panic.c中的源代码找到有关看门狗调控器驱动的更多信息):

struct watchdog_governor {
    const char name[WATCHDOG_GOV_NAME_MAXLEN];
    void (*pretimeout)(struct watchdog_device *wdd);
};

显然,它的字段必须由底层的看门狗调控器驱动填写。 有关预超时通知的实际用法,可以参考在drivers/watchdog/imx2_wdt.c中定义的 i.MX6 看门狗驱动的 IRQ 处理程序。 这方面的摘录已在上一节早些时候显示。 在那里,您将注意到从 Watchdog(实际上是预超时)IRQ 处理程序内部调用watchdog_notify_pretimeout()。 此外,您会注意到,驱动使用不同的watchdog_info结构,具体取决于看门狗是否有有效的 IRQ。 如果存在有效的结构,则使用在.options中设置了WDIOF_PRETIMEOUT标志的结构,这意味着该设备具有预超时功能。 否则,它使用未设置WDIOF_PRETIMEOUT标志的结构。

既然我们已经熟悉了调控器和预超时的概念,我们就可以考虑学习另一种实现 Watchdog 的方法,例如基于 GPIO 的方法。

基于 GPIO 的监视器

有时,使用外部看门狗设备而不是 SoC 本身提供的设备可能更好,例如出于功率效率的原因,因为有些 SoC 的内部看门狗比外部看门狗需要更多的电源。 大多数情况下(如果不是总是的话),这种外部看门狗设备通过 GPIO 线进行控制,并且有可能重置系统。 通过切换其连接的 GPIO 线路对其执行 ping 操作。 这种配置在 UDOO Quad 中使用(在其他 UDOO 变体上未选中)。

Linux 内核可以通过启用CONFIG_GPIO_WATCHDOG config选项来处理该设备,该选项将拉出底层驱动drivers/watchdog/gpio_wdt.c。 如果启用,它将通过从1-to-0-to-1切换连接到 GPIO 线路的硬件来周期性地ping连接到 GPIO 线路的硬件。 如果硬件没有定期收到其 ping,它将重置系统。 您应该使用它,而不是使用 sysfs 直接与 GPIO 对话;它提供了比 GPIO 更好的 sysfs 用户空间接口,并且它与内核框架的集成比您的用户空间代码更好。

对此的支持仅来自设备树,有关其绑定的更好文档可以在Documentation/devicetree/bindings/watchdog/gpio-wdt.txt中找到,显然来自内核源代码。

以下是一个绑定示例:

watchdog: watchdog {
    compatible = "linux,wdt-gpio";
    gpios = <&gpio3 9 GPIO_ACTIVE_LOW>;
    hw_algo = "toggle";
    hw_margin_ms = <1600>;
};

compatible属性必须始终为linux,wdt-gpiogpios是控制看门狗设备的 GPIO 说明符。 hw_algo应为togglelevel。 前者意味着应使用低到高或高到低转换来 ping 外部看门狗设备,并且当 GPIO 线保持浮动或连接到三态缓冲器时,看门狗被禁用。 要实现这一点,将 GPIO 配置为输入就足够了。 第二个algo表示施加信号电平(高或低)足以 ping 通看门狗。

其工作方式如下:当用户空间代码通过/dev/watchdog设备文件 ping 看门狗时,底层驱动(实际上为gpio_wdt.c)将切换 GPIO 线路(如果hw_algotoggle,则为1-0-1),或者为该 GPIO 线路分配特定级别(如果hw_algolevel,则为高级别或低级别)。 例如,UDOO 四元组使用 GPIO 控制的看门狗APX823-31W5,其事件输出连接到 i.MX6 PORB 线路(实际上是复位线)。 其原理图可在此处查看:http://udoo.org/download/files/schematics/UDOO_REV_D_schematics.pdf

现在,我们完成了内核端的 Watchdog。 我们回顾了底层数据结构,处理了它的 API,引入了预超时的概念,甚至处理了基于 GPIO 的 Watchdog 替代方案。 在下一节中,我们将研究用户空间实现,它是看门狗服务的一种 conSUMER。

看门狗用户空间界面

在基于 Linux 的系统上,监视器的标准用户空间接口是/dev/watchdog文件,守护程序将通过该文件通知内核监视器驱动用户空间仍处于活动状态。 Watchdog 在文件打开后立即启动,并通过定期写入此文件来 ping。

当通知发生时,底层驱动将通知看门狗设备,这将导致重置其超时;然后看门狗将在重置系统之前等待另一个timeout持续时间。 但是,如果由于任何原因,用户空间在超时之前没有执行通知,监视程序将重置系统(导致重启)。 此机制 PRO 提供了一种强制系统可用性的方法。 让我们从基础开始,学习如何启动和停止 Watchdog。

启动和停止看门狗

打开/dev/watchdog设备文件后,监视程序会自动启动,如下例所示:

int fd;
fd = open("/dev/watchdog", O_WRONLY);
if (fd == -1) {
    if (errno == ENOENT)
        printf("Watchdog device not enabled.\n");
    else if (errno == EACCES)
        printf("Run watchdog as root.\n");
    else
        printf("Watchdog device open failed %s\n", strerror(errno));
    exit(-1);
}

只是,关闭看门狗设备文件并不能阻止它。 关闭文件后,您会惊讶地发现系统会重置。 要正确停止看门狗,首先需要将魔术字符V写入看门狗设备文件。 这将指示内核在下次关闭设备文件时关闭看门狗,如下所示:

const char v = 'V';
printf("Send magic character: V\n"); ret = write(fd, &v, 1);
if (ret < 0)
    printf("Stopping watchdog ticks failed (%d)...\n", errno);

然后,您需要关闭看门狗设备文件才能停止它:

printf("Close for stopping..\n");
close(fd);

重要音符

在通过关闭文件设备来停止 Watchdog 时有一个例外:它是在内核的CONFIG_WATCHDOG_NOWAYOUTconfig 选项被启用时发生的。 启用此选项后,看门狗根本无法停止。 因此,您需要一直维修它,否则它会重置系统。 此外,看门狗驱动应该在其选项中设置WDIOF_MAGICCLOSE标志;否则,魔术关闭功能将不起作用。

现在我们已经了解了如何启动和停止看门狗,现在应该学习如何刷新设备,以防止系统突然重新启动。

Ping/踢看门狗-发送保活 ping

踢或喂看门狗有两种方式:

  1. 将任何字符写入/dev/watchdog:向看门狗设备文件写入被定义为保活 ping。 建议根本不要写V字符(因为它有特殊的含义),即使它在字符串中也是如此。
  2. 使用WDIOC_KEEPALIVEioctl,ioctl(fd, WDIOC_KEEPALIVE,``0);:忽略 ioctl 的参数。 看门狗驱动应该在此 ioctl 之前在其选项中设置WDIOF_KEEPALIVEPING标志,以使其正常工作。

最好的做法是每隔一半的超时值就向看门狗提供信息。 这意味着如果它的超时是30s,您应该每隔15s喂它一次。 现在,让我们了解一下如何收集有关监管机构如何管理我们系统的信息。

获取监视器功能和身份

获取监视程序功能和/或标识包括获取与监视程序关联的底层struct watchdog_info结构。 如果您还记得,此信息结构是强制的,由看门狗驱动提供。

要实现这一点,您需要使用WDIOC_GETSUPPORTioctl。 以下是一个示例:

struct watchdog_info ident;
ioctl(fd, WDIOC_GETSUPPORT, &ident);
printf("WDIOC_GETSUPPORT:\n");
/* Printing the watchdog's identity, its unique name actually */
printf("\tident.identity = %s\n",ident.identity);
/* Printing the firmware version */
printf("\tident.firmware_version = %d\n",        ident.firmware_version);
/* Printing supported options (capabilities) in hex format */
printf("WDIOC_GETSUPPORT: ident.options = 0x%x\n",       ident.options);

我们可以进一步测试功能中的一些字段,如下所示:

if (ident.options & WDIOF_KEEPALIVEPING)
    printf("\tKeep alive ping reply.\n");
if (ident.options & WDIOF_SETTIMEOUT)
    printf("\tCan set/get the timeout.\n");

您可以(或者我应该说“必须”)使用它,以便在对其执行某些操作之前检查看门狗功能。 现在,我们可以更进一步,了解如何获取并设置更多奇特的 Watchdog 属性。

设置和获取超时和预超时

在设置/获取超时之前,看门狗信息应设置WDIOF_SETTIMEOUT标志。 有一些驱动可以使用WDIOC_SETTIMEOUTioctl 动态修改看门狗超时。 这些驱动必须在其看门狗信息结构中设置WDIOF_SETTIMEOUT标志,并提供.set_timeout回调。

虽然此处的参数是以秒为单位表示超时值的整数,但返回值是应用于硬件设备的实际超时值,因为由于硬件限制,它可能与 ioctl 中请求的超时值不同:

int timeout = 45;
ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
printf("The timeout was set to %d seconds\n", timeout);

在查询当前超时时,您应该使用WDIOC_GETTIMEOUTioctl,如下例所示:

int timeout;
ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
printf("The timeout is %d seconds\n", timeout);

最后,在预超时,看门狗驱动应该在选项中设置WDIOF_PRETIMEOUT,并在其操作中提供.set_pretimeout回调。 然后,您应该将WDIOC_SETPRETIMEOUT与预超时值一起用作参数:

pretimeout = 10;
ioctl(fd, WDIOC_SETPRETIMEOUT, &pretimeout);

如果所需的预超时值为0或大于当前超时,则会出现-EINVAL错误。

现在我们已经了解了如何在看门狗设备上获取和设置超时/预超时,我们可以学习如何设置看门狗触发前的剩余时间 g。

得到剩下的时间

WDIOC_GETTIMELEFTioctl 允许在复位发生之前检查看门狗计数器上的剩余时间。 此外,看门狗驱动应该通过提供.get_timeleft()回调来支持此功能;否则将出现EOPNOTSUPP错误。 下面的示例显示了如何使用此 ioctl:

int timeleft;
ioctl(fd, WDIOC_GETTIMELEFT, &timeleft);
printf("The remaining timeout is %d seconds\n", timeleft);

在 ioctl 的返回路径上填充timeleft变量。

一旦监视器启动,它就会在配置为重新启动时触发重新启动。 在下一节中,我们将学习如何获取上次重新启动的原因,以便查看由 Watchdog 导致的重新启动的原因是什么。

获取(引导/重新引导)状态

在本节中有两个 ioctl 命令可供使用。 这些是WDIOC_GETSTATUSWDIOC_GETBOOTSTATUS。 处理这些问题的方式取决于驱动实现,有两种类型的驱动实现:

  • 通过其他设备提供看门狗功能的旧驱动。 这些驱动不使用通用看门狗框架接口,并提供自己的file_ops和自己的.ioctl操作。 此外,这些驱动只支持WDIOC_GETSTATUS,而其他驱动可能同时支持WDIOC_GETSTATUSWDIOC_GETBOOTSTATUS。 两者的不同之处在于,前者将返回设备状态寄存器的原始内容,而后者应该更智能一些,因为它解析原始内容,并且只返回引导状态标志。 这些驱动需要迁移到新的通用看门狗框架。请注意,某些支持两个命令的驱动可能会为两个 ioctls 返回相同的值(相同的case语句),而其他驱动可能会返回不同的值(每个命令都有自己的case语句)。
  • 新驱动使用通用看门狗框架。 这些驱动依赖于框架,不再关心file_ops。 所有操作都在drivers/watchdog/watchdog_dev.c文件中完成(您可以查看一下,尤其是 ioctl 命令是如何实现的)。 对于这些类型的驱动,WDIOC_GETSTATUSWDIOC_GETBOOTSTATUS分别由看门狗内核处理。 本节将介绍这些驱动。

现在,让我们将重点放在通用实现上。 对于这些驱动,WDIOC_GETBOOTSTATUS将返回底层watchdog_device.bootstatus字段的值。 对于WDIOC_GETSTATUS,如果提供了看门狗.status操作,则将调用它并将其返回值复制给用户;否则,将使用中的AND操作调整watchdog_device.bootstatus的内容,以清除(或标记)无意义的位。 下面的代码片段显示了它是如何在内核空间中完成的:

static unsigned int watchdog_get_status(struct                                         watchdog_device *wdd)
{
    struct watchdog_core_data *wd_data = wdd->wd_data;
    unsigned int status;
    if (wdd->ops->status)
        status = wdd->ops->status(wdd);
    else
        status = wdd->bootstatus &
                      (WDIOF_CARDRESET | WDIOF_OVERHEAT |
                       WDIOF_FANFAULT | WDIOF_EXTERN1 |
                       WDIOF_EXTERN2 | WDIOF_POWERUNDER |
                       WDIOF_POWEROVER);
    if (test_bit(_WDOG_ALLOW_RELEASE, &wd_data->status))
        status |= WDIOF_MAGICCLOSE;
    if (test_and_clear_bit(_WDOG_KEEPALIVE, &wd_data->status))
        status |= WDIOF_KEEPALIVEPING;
    return status;
}

前面的代码是一个通用的看门狗核心函数,用于获取看门狗状态。 它实际上是一个包装器,负责调用底层的ops.status回调。 现在,回到我们的用户空间使用情况。 我们可以做到以下几点:

int flags = 0;
int flags;
ioctl(fd, WDIOC_GETSTATUS, &flags);
/* or ioctl(fd, WDIOC_GETBOOTSTATUS, &flags); */

显然,我们可以像前面在获取监视器功能和身份部分中所做的那样,继续进行个别 l 标志检查。

到目前为止,我们已经编写了使用看门狗设备的代码。 下一节将向我们展示如何在不编写 ng 代码的情况下从用户空间处理 Watchdog,本质上是使用 sysfs 接口。

看门狗 sysfs 接口

Watchdog 框架提供了通过 sysfs 接口从用户空间管理看门狗设备的可能性。 如果内核中启用了CONFIG_WATCHDOG_SYSFS配置选项,并且根目录为/sys/class/watchdogX/,则这是可能的。 X是系统中看门狗设备的索引。 Sysfs 中的每个监视器目录都有以下内容:

  • nowayout:如果设备支持nowayout功能,则给出1,否则给出0
  • status:这是相当于WDIOC_GETSTATUSioctl 的 sysfs。 此 sysfs 文件报告监视程序的内部状态位。
  • timeleft:这是相当于WDIOC_GETTIMELEFTioctl 的 sysfs。 此 sysfs 条目返回监视程序重置系统前的剩余时间(实际为秒数)。
  • timeout:给出编程超时的当前值。
  • identity:包含监视程序设备的标识字符串。
  • bootstatus:这是相当于WDIOC_GETBOOTSTATUSioctl 的 sysfs。 此条目通知系统复位是否是由看门狗设备引起的。
  • state:给出看门狗设备的活动/非活动状态。

现在已经描述了前面的监视器属性,我们可以从用户空间集中讨论预超时管理。

处理预超时事件

调速器的设置通过 sysfs 完成。 调控器只是一个策略管理器,它根据一些外部(但输入)参数执行某些操作。 有热调控器、CPUFreq 调控器,现在还有看门狗调控器。 每个调控器都在其自己的驱动中实现。

您可以使用以下命令检查监视器的可用调控器(比方说watchdog0):

# cat /sys/class/watchdog/watchdog0/pretimeout_available_governors
noop panic

现在,我们可以检查是否可以选择预超时调控器:

# cat /sys/class/watchdog/watchdog0/pretimeout_governor
panic
# echo -n noop > /sys/class/watchdog/watchdog0/pretimeout_governor
# cat /sys/class/watchdog/watchdog0/pretimeout_governor
noop

要检查预超时值,只需执行以下操作:

# cat /sys/class/watchdog/watchdog0/pretimeout
10

现在,我们熟悉如何从用户空间使用 Watchdog sysfs 界面。 虽然我们不在内核中,但我们可以利用整个框架,特别是处理看门狗参数。

摘要

在本章中,我们讨论了看门狗设备的各个方面:它们的 API、GPIO 替代方案,以及它们如何帮助保持系统的可靠性。 我们了解了如何启动、如何(在可能的情况下)停止以及如何维护看门狗设备。 此外,我们还引入了预超时和看门狗专用调控器的概念。

在下一章中,我们将讨论一些 Linux 内核开发和调试技巧,例如分析内核死机消息和内核跟踪。