39、看门狗设备驱动详解

看门狗设备驱动详解

1. 处理预超时和调节器

在 Linux 内核的多个子系统中都出现了调节器(governor)的概念,如热调节器、CPU 频率调节器,现在还有看门狗调节器。调节器本质上是一个实现策略管理的驱动,它会根据系统的某些状态或事件做出反应。

每个子系统实现其调节器驱动的方式可能不同,但核心思想是一致的。调节器通过唯一的名称和正在使用的调节器(策略管理器)来标识,并且可以在运行时更改,通常是通过 sysfs 接口。

要在 Linux 内核中添加对看门狗预超时和调节器的支持,可以启用 CONFIG_WATCHDOG_PRETIMEOUT_GOV 内核配置选项。内核中实际上有两个看门狗调节器驱动: drivers/watchdog/pretimeout_noop.c drivers/watchdog/pretimeout_panic.c ,它们的唯一名称分别是 noop panic 。可以通过启用 CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC 来默认使用其中一个。

本部分的主要目标是将预超时事件传递给当前活动的看门狗调节器。这可以通过 watchdog_notify_pretimeout() 接口实现,其原型如下:

void watchdog_notify_pretimeout(struct watchdog_device *wdd)

一些看门狗设备会在预超时事件发生时生成一个中断请求(IRQ)。主要思路是在这个 IRQ 处理程序中调用 watchdog_notify_pretimeout() 。在底层,该接口会在系统注册的看门狗调节器全局列表中查找调节器的名称,并调用其 .pretimeout 回调函数。

看门狗调节器结构体如下:

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_notify_pretimeout() 被调用。此外,该驱动会根据看门狗是否有有效的 IRQ 来使用不同的 watchdog_info 结构体。如果有有效 IRQ,则使用在 .options 中设置了 WDIOF_PRETIMEOUT 标志的结构体,这意味着设备具有预超时功能;否则,使用未设置该标志的结构体。

2. 基于 GPIO 的看门狗

有时,出于功率效率等原因,使用外部看门狗设备可能比使用 SoC 本身提供的看门狗更好,因为有些 SoC 的内部看门狗比外部看门狗消耗更多的功率。大多数情况下,这种外部看门狗设备通过 GPIO 线控制,并且有可能重置系统。它通过切换与其连接的 GPIO 线来进行“ping”操作。这种配置在 UDOO QUAD 中使用(未在其他 UDOO 变体上检查)。

Linux 内核可以通过启用 CONFIG_GPIO_WATCHDOG 配置选项来处理这种设备,这将加载底层驱动 drivers/watchdog/gpio_wdt.c 。如果启用,它将通过将连接的 GPIO 线从 1 切换到 0 再切换到 1 来定期“ping”硬件。如果硬件没有定期收到“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-gpio gpios 是一个控制看门狗设备的 GPIO 说明符。 hw_algo 应该是 toggle level toggle 表示应该使用低到高或高到低的转换来“ping”外部看门狗设备,并且当 GPIO 线悬空或连接到三态缓冲器时,看门狗将被禁用,为此,将 GPIO 配置为输入就足够了; level 表示施加一个信号电平(高或低)就足以“ping”看门狗。

工作原理如下:当用户空间代码通过 /dev/watchdog 设备文件“ping”看门狗时,底层驱动(实际上是 gpio_wdt.c )将根据 hw_algo 的设置切换 GPIO 线(如果是 toggle ,则为 1 - 0 - 1)或分配特定的电平(如果是 level ,则为高或低)。例如,UDOO QUAD 使用的 APX823 - 31W5 是一个由 GPIO 控制的看门狗,其事件输出连接到 i.MX6 的 PORB 线(实际上是复位线),其原理图可在 这里 找到。

3. 看门狗用户空间接口
3.1 启动和停止看门狗

在基于 Linux 的系统中,看门狗的标准用户空间接口是 /dev/watchdog 文件。通过该文件,守护进程可以通知内核看门狗驱动用户空间仍然处于活动状态。看门狗在打开该文件后立即启动,并通过定期向该文件写入数据来进行“ping”操作。

当通知发生时,底层驱动将通知看门狗设备,这将导致重置其超时时间;然后看门狗将等待另一个超时时间,之后再重置系统。但是,如果出于任何原因,用户空间在超时时间到期之前没有进行通知,看门狗将重置系统(导致重新启动)。这种机制提供了一种确保系统可用性的方法。

启动看门狗的示例代码如下:

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);

需要注意的是,当内核的 CONFIG_WATCHDOG_NOWAYOUT 配置选项启用时,看门狗根本无法停止,必须始终为其提供服务,否则它将重置系统。此外,看门狗驱动应在其选项中设置 WDIOF_MAGICCLOSE 标志,否则魔术关闭功能将不起作用。

3.2 发送保持活动的“ping”信号

有两种方法可以“踢”或“喂”看门狗:
1. 向 /dev/watchdog 写入任何字符:向看门狗设备文件写入数据被定义为保持活动的“ping”操作。建议不要写入字符 V (因为它有特殊含义),即使它在字符串中。
2. 使用 WDIOC_KEEPALIVE ioctl: ioctl(fd, WDIOC_KEEPALIVE, 0); ,该 ioctl 的参数将被忽略。看门狗驱动应在使用此 ioctl 之前在其选项中设置 WDIOF_KEEPALIVEPING 标志,以便该操作生效。

良好的做法是每隔看门狗超时值的一半时间“喂”一次看门狗。例如,如果其超时时间是 30 秒,则应每 15 秒“喂”一次。

3.3 获取看门狗的功能和标识

要获取看门狗的功能和/或标识,需要获取与看门狗关联的底层 struct watchdog_info 结构体。这可以通过 WDIOC_GETSUPPORT ioctl 实现,示例代码如下:

struct watchdog_info ident;
ioctl(fd, WDIOC_GETSUPPORT, &ident);
printf("WDIOC_GETSUPPORT:\n");
printf("\tident.identity = %s\n",ident.identity);
printf("\tident.firmware_version = %d\n", ident.firmware_version);
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");

在对看门狗执行某些操作之前,必须使用此方法检查其功能。

3.4 设置和获取超时时间和预超时时间

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

设置超时时间的示例代码如下:

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

查询当前超时时间可以使用 WDIOC_GETTIMEOUT ioctl,示例代码如下:

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 错误。

3.5 获取剩余时间

WDIOC_GETTIMELEFT ioctl 允许检查在看门狗重置系统之前计数器上还剩下多少时间。此外,看门狗驱动应通过提供 .get_timeleft() 回调函数来支持此功能,否则将得到 EOPNOTSUPP 错误。示例代码如下:

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

timeleft 变量将在 ioctl 返回时被填充。

3.6 获取(启动/重启)状态

有两个 ioctl 命令可以用于获取状态: WDIOC_GETSTATUS WDIOC_GETBOOTSTATUS 。这些命令的处理方式取决于驱动的实现,驱动实现主要分为两种类型:
- 旧驱动 :通过杂项设备提供看门狗功能。这些驱动不使用通用的看门狗框架接口,而是提供自己的 file_ops .ioctl 操作。此外,这些驱动仅支持 WDIOC_GETSTATUS ,而有些可能同时支持 WDIOC_GETSTATUS WDIOC_GETBOOTSTATUS 。两者的区别在于,前者将返回设备状态寄存器的原始内容,而后者应该更智能一些,它会解析原始内容并仅返回启动状态标志。这些驱动需要迁移到新的通用看门狗框架。需要注意的是,一些支持这两个命令的驱动可能对两个 ioctl 返回相同的值(相同的 case 语句),而其他驱动可能返回不同的值(每个命令有自己的 case 语句)。
- 新驱动 :使用通用的看门狗框架。这些驱动依赖于该框架,不再关心 file_ops 。所有操作都在 drivers/watchdog/watchdog_dev.c 文件中完成。对于这类驱动, WDIOC_GETSTATUS WDIOC_GETBOOTSTATUS 由看门狗核心分别处理。

对于通用实现, WDIOC_GETBOOTSTATUS 将返回底层 watchdog_device.bootstatus 字段的值。对于 WDIOC_GETSTATUS ,如果看门狗提供了 .status 操作,则将调用该操作并将其返回值复制给用户;否则,将对 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;
}

在用户空间可以使用以下代码:

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

可以像在获取看门狗功能和标识部分那样对单个标志进行检查。

4. 看门狗的 sysfs 接口

如果在内核中启用了 CONFIG_WATCHDOG_SYSFS 配置选项,看门狗框架允许通过 sysfs 接口从用户空间管理看门狗设备。根目录为 /sys/class/watchdogX/ ,其中 X 是系统中看门狗设备的索引。每个看门狗在 sysfs 中的目录包含以下内容:
| 属性 | 说明 |
| ---- | ---- |
| nowayout | 如果设备支持 nowayout 功能,则返回 1,否则返回 0。 |
| status | 相当于 WDIOC_GETSTATUS ioctl,该 sysfs 文件报告看门狗的内部状态位。 |
| timeleft | 相当于 WDIOC_GETTIMELEFT ioctl,该 sysfs 条目返回看门狗重置系统之前剩余的时间(实际为秒数)。 |
| timeout | 给出当前编程的超时值。 |
| identity | 包含看门狗设备的标识字符串。 |
| bootstatus | 相当于 WDIOC_GETBOOTSTATUS ioctl,该条目告知系统重置是否由看门狗设备引起。 |
| state | 给出看门狗设备的活动/非活动状态。 |

5. 处理预超时事件

可以通过 sysfs 设置调节器。调节器是一个策略管理器,它根据一些外部(输入)参数采取特定的操作。有热调节器、CPU 频率调节器,现在还有看门狗调节器。每个调节器都在其自己的驱动中实现。

可以使用以下命令检查某个看门狗(例如 watchdog0 )可用的调节器:

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

综上所述,通过对看门狗设备驱动的各个方面进行详细介绍,包括内核层面的预超时和调节器处理、基于 GPIO 的看门狗实现,以及用户空间接口的启动停止、功能获取、状态查询等操作,我们可以更好地理解和使用看门狗设备,确保系统的稳定性和可用性。

看门狗设备驱动详解

6. 看门狗操作流程总结

为了更清晰地理解看门狗的操作,我们可以总结其主要操作流程,以下是一个 mermaid 格式的流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(打开 /dev/watchdog 文件):::process
    B --> C{文件打开成功?}:::decision
    C -->|是| D(看门狗启动):::process
    C -->|否| E{错误类型}:::decision
    E -->|ENOENT| F(输出 "Watchdog device not enabled."):::process
    E -->|EACCES| G(输出 "Run watchdog as root."):::process
    E -->|其他| H(输出 "Watchdog device open failed [错误信息]"):::process
    F --> I([结束]):::startend
    G --> I
    H --> I
    D --> J(定期向 /dev/watchdog 写入数据或使用 WDIOC_KEEPALIVE ioctl):::process
    J --> K{超时前有通知?}:::decision
    K -->|是| J
    K -->|否| L(看门狗重置系统):::process
    D --> M{需要停止看门狗?}:::decision
    M -->|是| N(写入魔术字符 'V'):::process
    N --> O(关闭 /dev/watchdog 文件):::process
    O --> I
    M -->|否| J

这个流程图展示了看门狗从启动到可能的停止或重置系统的整个过程。在实际使用中,我们需要根据系统的需求和状态,合理地进行这些操作。

7. 不同类型驱动的对比

我们已经了解到看门狗驱动有旧驱动和新驱动两种类型,下面通过一个表格来对比它们的特点:
| 驱动类型 | 框架使用 | 功能支持 | 处理方式 | 迁移需求 |
| ---- | ---- | ---- | ---- | ---- |
| 旧驱动 | 不使用通用看门狗框架 | 部分仅支持 WDIOC_GETSTATUS,部分支持两者 | 提供自己的 file_ops 和 .ioctl 操作 | 需要迁移到新框架 |
| 新驱动 | 使用通用看门狗框架 | 支持 WDIOC_GETSTATUS 和 WDIOC_GETBOOTSTATUS | 由看门狗核心分别处理 | 无 |

从这个表格中可以看出,新驱动在功能支持和处理方式上更加统一和规范,而旧驱动则需要进行迁移以适应新的框架。

8. 看门狗使用的注意事项

在使用看门狗设备时,有一些重要的注意事项需要牢记:
- 停止看门狗的限制 :当内核的 CONFIG_WATCHDOG_NOWAYOUT 配置选项启用时,看门狗无法停止,必须持续为其提供服务,否则会导致系统重置。同时,看门狗驱动需要设置 WDIOF_MAGICCLOSE 标志,魔术关闭功能才能正常工作。
- 字符写入的限制 :在向 /dev/watchdog 写入数据时,应避免写入字符 V ,因为它具有特殊含义。
- 预超时值的设置 :设置预超时值时,要确保其不为 0 且不大于当前超时时间,否则会得到 -EINVAL 错误。
- 功能检查的必要性 :在对看门狗执行操作之前,务必使用 WDIOC_GETSUPPORT ioctl 检查其功能,以避免因不支持的操作而导致错误。

9. 实际应用场景举例

以下是一些看门狗在实际应用中的场景:
- 工业控制系统 :在工业生产中,控制系统的稳定性至关重要。看门狗可以确保系统在出现软件故障或异常时能够及时重置,避免生产过程出现严重问题。例如,在自动化生产线中,如果某个控制程序出现死循环,看门狗会在超时后重置系统,使生产线尽快恢复正常运行。
- 嵌入式设备 :嵌入式设备通常需要长时间稳定运行,如智能家居设备、智能电表等。看门狗可以防止设备因软件崩溃而失去响应,保证设备的正常功能。例如,智能门锁在运行过程中,如果出现软件故障导致无法正常开锁,看门狗会重置系统,使门锁恢复正常工作。

10. 总结与展望

通过对看门狗设备驱动的详细介绍,我们了解了其在内核层面和用户空间的各种操作和功能。从处理预超时和调节器,到基于 GPIO 的看门狗实现,再到用户空间的启动、停止、功能获取和状态查询等操作,我们可以看到看门狗在保障系统稳定性和可用性方面的重要作用。

在未来,随着技术的不断发展,看门狗设备可能会有更多的功能和优化。例如,可能会出现更加智能的调节器,能够根据系统的实时状态自动调整看门狗的行为;或者在低功耗设备中,看门狗的功耗会进一步降低,以满足节能的需求。同时,对于旧驱动的迁移工作也需要持续推进,以确保整个系统的兼容性和稳定性。

总之,看门狗设备驱动是一个复杂而重要的领域,我们需要不断学习和掌握相关知识,以更好地应用看门狗技术,保障系统的正常运行。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值