基本原理


该看门狗的设备驱动实现原理很简单,比较主要的有两点:

一、定时器喂狗

通过定时器根据配置文件配置的喂狗方式(如脉冲切换、电平切换),对指定的 gpio 进行脉冲切换或电平切换实现喂狗。

  1. 脉冲切换
    指的是喂狗时,会给 gpio 一个 1us 宽度的高电平或低电平(取决于配置的 gpio 电平状态)。如设置为 1600ms,那么每 800ms 就会产生一个这样的脉冲信号。

  2. 电平切换
    指的是喂狗时,会以固定的时间间隔,翻转 gpio 的电平状态,如设置看门狗超时时间为 1600ms,那么每 800ms 就会翻转一次 gpio 的电平状态,实现喂狗。

二、软硬件喂狗时间解耦

驱动将喂狗时间分为硬件喂狗时间和软件喂狗时间,很好的解决了软硬件时间的耦合问题,对上提供一个统一的喂狗时间,不受硬件芯片的实际喂狗时间限制,应用软件设计时不需要考虑底层采用了什么硬件。

在应用软件启动喂狗之后,驱动会启动定时器按照硬件看门狗芯片的最长超时复位时间的一半进行喂狗,如看门狗在 1600ms 内没有收到喂狗信号,就会产生复位动作,那么驱动就会取 1600ms 的一半,即定时器设置 800ms 为周期进行喂狗。

而应用软件可以设置喂狗时间范围为 1s ~ 65535s,驱动默认为 60s,如果应用软件没有在所设置的时间内调用 WDIOC_KEEPALIVE 进行喂狗(如60s) ,那么驱动程序就会停止给硬件喂狗,从而让硬件看门狗芯片产生复位信号,也就是说在 60s 内,驱动还是会通过定时器给硬件看门狗芯片继续喂狗,超过 60s 后,应用没有喂狗,那么定时器就会被停止。

  1. 硬件喂狗时间
    不同的硬件看门狗芯片的看门狗超时时间都不一样,如 1.6s,那么就需要在 1.6s 内切换一次 gpio 信号进行喂狗,驱动取的是一半的时间,即 1.6s / 2 = 0.8s 喂狗,以便于留有一定的冗余时间。

  2. 软件喂狗时间
    指的是应用程序进行喂狗的时间,内核预设最短时间为 1s,最大时间为 65535s,默认喂狗时间为 60s。

// 应用层进行喂狗的回调: 对应应用层 WDIOC_KEEPALIVE
static int gpio_wdt_ping(struct watchdog_device *wdd)
{
// 更新最后一次喂狗时间
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
priv->last_jiffies = jiffies;
return 0;
}
static void gpio_wdt_hwping(unsigned long data)
{
struct watchdog_device *wdd = (struct watchdog_device *)data;
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); // 如果应用层已经启动喂狗, 则判断是否超出喂狗时间
// 注意这里的喂狗时间仅仅只是软件驱动喂狗时间,而不是硬件看门狗时间。
// 这里的好处是可以将应用层的喂狗时间与硬件看门狗喂狗的时间解耦出来。
// 若软件喂狗时间超时,那不继续喂硬件看门狗就可以让硬件看门狗复位了。
if (priv->armed && time_after(jiffies, priv->last_jiffies +
msecs_to_jiffies(wdd->timeout * 1000))) {
dev_crit(wdd->parent, "Timer expired. System will reboot soon!\n");
return;
} // 重置定时器, 使之可以继续定时
mod_timer(&priv->timer, jiffies + priv->hw_margin); // 根据喂狗方式, 选择电平切换方式或电平脉冲方式喂狗
switch (priv->hw_algo) {
case HW_ALGO_TOGGLE:
/* Toggle output pin */
priv->state = !priv->state; // 使用电平切换方式
gpio_set_value_cansleep(priv->gpio, priv->state);
break;
case HW_ALGO_LEVEL:
/* Pulse */ // 使用电平脉冲方式
gpio_set_value_cansleep(priv->gpio, !priv->active_low);
udelay(1);
gpio_set_value_cansleep(priv->gpio, priv->active_low);
break;
}
}

源码分析


以下源码对原生的 Linux 驱动在调用 of_get_gpio_flags() 时,为了适应全志平台做了一点小改动。

/*
* Driver for watchdog device controlled through GPIO-line
*
* Author: 2013, Alexander Shiyan <shc_work@mail.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/ #include <linux/err.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/watchdog.h>
#include <linux/sunxi-gpio.h> #define SOFT_TIMEOUT_MIN 1
#define SOFT_TIMEOUT_DEF 60
#define SOFT_TIMEOUT_MAX 0xffff enum {
HW_ALGO_TOGGLE, // 切换方式
HW_ALGO_LEVEL, // 脉冲方式
}; struct gpio_wdt_priv {
int gpio;
bool active_low;
bool state;
bool always_running;
bool armed;
unsigned int hw_algo;
unsigned int hw_margin;
unsigned long last_jiffies;
struct timer_list timer;
struct watchdog_device wdd;
}; static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
{
// 根据电平有效性来决定设置什么电平,来关闭看门狗
gpio_set_value_cansleep(priv->gpio, !priv->active_low); // 如果是电平切换方式,则恢复输入状态
if (priv->hw_algo == HW_ALGO_TOGGLE)
gpio_direction_input(priv->gpio);
} static void gpio_wdt_hwping(unsigned long data)
{
struct watchdog_device *wdd = (struct watchdog_device *)data;
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); // 如果应用层已经启动喂狗, 则判断是否超出喂狗时间
// 注意这里的喂狗时间仅仅只是软件驱动喂狗时间,而不是硬件看门狗时间。
// 这里的好处是可以将应用层的喂狗时间与硬件看门狗喂狗的时间解耦出来。
// 若软件喂狗时间超时,那不继续喂硬件看门狗就可以让硬件看门狗复位了。
if (priv->armed && time_after(jiffies, priv->last_jiffies +
msecs_to_jiffies(wdd->timeout * 1000))) {
dev_crit(wdd->parent, "Timer expired. System will reboot soon!\n");
return;
} // 重置定时器, 使之可以继续定时
mod_timer(&priv->timer, jiffies + priv->hw_margin); // 根据喂狗方式, 选择电平切换方式或电平脉冲方式喂狗
switch (priv->hw_algo) {
case HW_ALGO_TOGGLE:
/* Toggle output pin */
priv->state = !priv->state; // 使用电平切换方式
gpio_set_value_cansleep(priv->gpio, priv->state);
break;
case HW_ALGO_LEVEL:
/* Pulse */ // 使用电平脉冲方式
gpio_set_value_cansleep(priv->gpio, !priv->active_low);
udelay(1);
gpio_set_value_cansleep(priv->gpio, priv->active_low);
break;
}
} // 驱动层自行启动看门狗, 区别于应用层启动看门狗
static void gpio_wdt_start_impl(struct gpio_wdt_priv *priv)
{
priv->state = priv->active_low;
gpio_direction_output(priv->gpio, priv->state);
priv->last_jiffies = jiffies;
// 这里调用了定时器的回调函数, 内部有 mod_timer() 可以实现启动定时器
gpio_wdt_hwping((unsigned long)&priv->wdd);
} // 应用层启动看门狗的回调, 对应应用层 WDIOC_SETOPTIONS->WDIOS_ENABLECARD
static int gpio_wdt_start(struct watchdog_device *wdd)
{
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
gpio_wdt_start_impl(priv);
priv->armed = true; // 区分应用层启动还是设备驱动启动的看门狗
return 0;
} // 应用层关闭看门狗的回调, 对应应用层 WDIOC_SETOPTIONS->WDIOS_DISABLECARD
static int gpio_wdt_stop(struct watchdog_device *wdd)
{
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); priv->armed = false; // 如果配置 always_running = true 则不会停止定时器喂狗
// 如果没有配置, 或者配置 always_running = false, 就会停止定时器喂狗并关闭看门狗
if (!priv->always_running) {
mod_timer(&priv->timer, 0); // 停止定时器
gpio_wdt_disable(priv); // 关闭看门狗
} return 0;
} // 应用层进行喂狗的回调: 对应应用层 WDIOC_KEEPALIVE
static int gpio_wdt_ping(struct watchdog_device *wdd)
{
// 更新最后一次喂狗时间
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
priv->last_jiffies = jiffies;
return 0;
} // 应用层设置超时时间的回调: 对应应用层 WDIOC_SETTIMEOUT
static int gpio_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
{
// 更新超时时间
wdd->timeout = t; // 喂一次狗, 实际上在 watchdog_dev.c 里面调用此回调之后
// 还会再调用一次 .ping 进行喂狗一次, 所以这里可有可无
return gpio_wdt_ping(wdd);
} // 配置此看门狗支持的操作
static const struct watchdog_info gpio_wdt_ident = {
// WDIOF_MAGICCLOSE: 支持看门狗被应用层关闭
// WDIOF_KEEPALIVEPING: 支持应用层喂狗
// WDIOF_SETTIMEOUT: 支持应用层设置超时时间
.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
.identity = "GPIO Watchdog",
}; // 看门狗对接上层应用的回调接口
static const struct watchdog_ops gpio_wdt_ops = {
.owner = THIS_MODULE,
.start = gpio_wdt_start, // 启动看门狗,对应应用层 WDIOC_SETOPTIONS->WDIOS_ENABLECARD
.stop = gpio_wdt_stop, // 关闭看门狗,对应应用层 WDIOC_SETOPTIONS->WDIOS_DISABLECARD
.ping = gpio_wdt_ping, // 进行喂狗操作,对应应用层 WDIOC_KEEPALIVE
.set_timeout = gpio_wdt_set_timeout, // 设置看门狗超时时间, 设置的同时也会喂一次狗, 对应应用层 WDIOC_SETTIMEOUT
}; // 匹配上平台设备, 则会被执行
static int gpio_wdt_probe(struct platform_device *pdev)
{
struct gpio_wdt_priv *priv;
struct gpio_config gpio_flags;
unsigned int hw_margin;
unsigned long f = 0;
const char *algo;
int ret; // 分配空间,存储私有数据
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM; // 作为私有数据放入到平台设备
platform_set_drvdata(pdev, priv); // 读取连接看门狗芯片 WDI 引脚的 gpio
priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, (enum of_gpio_flags *)&gpio_flags);
if (!gpio_is_valid(priv->gpio))
return priv->gpio; // 根据当前 gpio 的电平状态作为电平有效性依据, 来确定关闭看门狗时该 gpio 的电平
priv->active_low = gpio_flags.data & OF_GPIO_ACTIVE_LOW; // 读取清除看门狗计数的方式
ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo);
if (ret)
return ret; if (!strcmp(algo, "toggle")) { // 高低电平切换方式, 引脚设置为输入, 这样不会误触发看门狗
priv->hw_algo = HW_ALGO_TOGGLE;
f = GPIOF_IN;
} else if (!strcmp(algo, "level")) { // 高电平或低电平脉冲方式, 并根据电平有效性来关闭喂狗
priv->hw_algo = HW_ALGO_LEVEL;
f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
} else {
return -EINVAL;
} // 申请 gpio 并配置初始状态
ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f, dev_name(&pdev->dev));
if (ret)
return ret; // 读取看门狗电路会触发复位的时间(毫秒), 该参数决定定时器定时时间
ret = of_property_read_u32(pdev->dev.of_node,"hw_margin_ms", &hw_margin);
if (ret)
return ret; /* Disallow values lower than 2 and higher than 65535 ms */
// 不能小于2: 安全的喂狗时间取 1/2, 因此不能小于 2
// 不能大于 65535 : 不清楚为什么这么限制,意味着最大不能超过 65.535 秒
if (hw_margin < 2 || hw_margin > 65535)
return -EINVAL; // 取超时时间的一半作为喂狗时间比较安全
/* Use safe value (1/2 of real timeout) */
priv->hw_margin = msecs_to_jiffies(hw_margin / 2); // 读取看门狗配置,是否需要一直运行,配置为 TRUE 意味着此看门狗是不能被应用层关闭的
priv->always_running = of_property_read_bool(pdev->dev.of_node, "always-running"); // 作为私有数据放入到看门狗设备
watchdog_set_drvdata(&priv->wdd, priv); // 设置看门狗支持的操作和相应的回调接口, 响应应用层的具体操作, 如启动、停止、设置超时时间等等。
priv->wdd.info = &gpio_wdt_ident;
priv->wdd.ops = &gpio_wdt_ops;
priv->wdd.min_timeout = SOFT_TIMEOUT_MIN;
priv->wdd.max_timeout = SOFT_TIMEOUT_MAX;
priv->wdd.parent = &pdev->dev; // 初始化软件喂狗超时时间的配置, 这里会被设置为默认值 60 秒.
// 注意, 这里配置的只是应用层多长时间内没有喂狗
if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0)
priv->wdd.timeout = SOFT_TIMEOUT_DEF; // 安装定时器, 通过定时器来实现给硬件看门狗喂狗
setup_timer(&priv->timer, gpio_wdt_hwping, (unsigned long)&priv->wdd); // 设置重启时关闭看门狗
// 如果应用层启动了看门狗, 则重启时就会触发 stop 接口
// 通过 watchdog_core.c 注册了重启函数调用链
// 当应用层通过 watchdog_dev.c 启动了看门狗,在系统重启时在通知了链里面会触发 stop 接口
watchdog_stop_on_reboot(&priv->wdd); // 作为看门狗设备驱动注册到系统中
ret = watchdog_register_device(&priv->wdd);
if (ret)
return ret; // 如果需要一直运行, 则启动喂狗操作
if (priv->always_running)
gpio_wdt_start_impl(priv); return 0;
} // 在驱动程序移除时执行
static int gpio_wdt_remove(struct platform_device *pdev)
{
struct gpio_wdt_priv *priv = platform_get_drvdata(pdev); // 删除定时器, 不再继续喂狗
del_timer_sync(&priv->timer);
// 注销当前看门狗设备驱动
watchdog_unregister_device(&priv->wdd); return 0;
} // 匹配 dts 的名字
static const struct of_device_id gpio_wdt_dt_ids[] = {
{ .compatible = "linux,wdt-gpio", },
{ }
};
MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids); static struct platform_driver gpio_wdt_driver = {
.driver = {
.name = "gpio-wdt",
.of_match_table = gpio_wdt_dt_ids,
},
.probe = gpio_wdt_probe,
.remove = gpio_wdt_remove,
}; #ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
static int __init gpio_wdt_init(void)
{
//注册平台驱动
return platform_driver_register(&gpio_wdt_driver);
}
arch_initcall(gpio_wdt_init);
#else
module_platform_driver(gpio_wdt_driver);
#endif MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
MODULE_DESCRIPTION("GPIO Watchdog");
MODULE_LICENSE("GPL");

【分析笔记】Linux gpio_wdt.c 看门狗设备驱动源码分析的更多相关文章

  1. (转)Linux设备驱动之HID驱动 源码分析

    //Linux设备驱动之HID驱动 源码分析 http://blog.chinaunix.net/uid-20543183-id-1930836.html HID是Human Interface De ...

  2. OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

    http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...

  3. linux设备驱动程序-i2c(0)-i2c设备驱动源码实现

    (基于4.14内核版本) 为了梳理清楚linux内核中的i2c实现框架,从本文开始,博主将分几个章节分别解析i2c总线在linux内核中的形成过程.匹配过程.以及设备驱动程序源码实现. 在介绍linu ...

  4. Bytom Dapp 开发笔记(三):Dapp Demo前端源码分析

    本章内容会针对比原官方提供的dapp-demo,分析里面的前端源码,分析清楚整个demo的流程,然后针对里面开发过程遇到的坑,添加一下个人的见解还有解决的方案. 储蓄分红合约简述 为了方便理解,这里简 ...

  5. 9.1 IIC驱动源码分析

    学习目标:分析linux内核源码下的i2c总线驱动 drivers/i2c/busses/i2c-s3c2410.c 和 driver/i2c/chips/eeprom.c 设备驱动: 一.i2c驱动 ...

  6. 从flink-example分析flink组件(3)WordCount 流式实战及源码分析

    前面介绍了批量处理的WorkCount是如何执行的 <从flink-example分析flink组件(1)WordCount batch实战及源码分析> <从flink-exampl ...

  7. Netty源码分析 (十二)----- 心跳服务之 IdleStateHandler 源码分析

    什么是心跳机制? 心跳说的是在客户端和服务端在互相建立ESTABLISH状态的时候,如何通过发送一个最简单的包来保持连接的存活,还有监控另一边服务的可用性等. 心跳包的作用 保活Q:为什么说心跳机制能 ...

  8. linux 2.6 互斥锁的实现-源码分析

    http://blog.csdn.net/tq02h2a/article/details/4317211 看了看linux 2.6 kernel的源码,下面结合代码来分析一下在X86体系结构下,互斥锁 ...

  9. 基于Netty的RPC架构学习笔记(五):netty线程模型源码分析(二)

    文章目录 小技巧(如何看开源框架的源码) 源码解析 阅读源码技巧 打印查看 通过打断点调试 查看调用栈 小技巧(如何看开源框架的源码) 一断点 二打印 三看调用栈 四搜索 源码解析 //设置nioso ...

  10. 基于Netty的RPC架构学习笔记(四):netty线程模型源码分析(一)

    文章目录 如何提高NIO的工作效率 举个

随机推荐

  1. JS逆向实战8——某网实战(基于golang-colly)

    其实本章算不上逆向教程 只是介绍golang的colly框架而已 列表页分析 根据关键字搜索 通过抓包分析可知 下一页所请求的参数如下 上图标红的代表所需参数 所以其实我们真正需要的也就是Search ...

  2. vs 自定义代码块

    代码自动生成,让代码飞~ 我目前用的是vs2013,c,c++用的多,vs自带了一套代码块规则,下面我们就以for举例子. 1 原生代码块怎么使用 输入for后按Tab键: 这时候可以修改 size_ ...

  3. C#11之原始字符串

    最近.NET7.0和C#11相继发布,笔者也是第一时间就用上了C#11,其中C#11的有一个更新能解决困扰我多年的问题,也就是文章的标题原始字符串. 在使用C#11的原始字符串时,发现的一些有意思的东 ...

  4. C#在Xp系统执行.exe程序的报错怎么查看原因

    我的电脑---->管理---->事件查看器----->应用程序,查看错误来源

  5. nacos的使用

    一:下载nacos 打开github搜索nacos,选择历史版本,建议下载1.4版本的,较稳定 https://github.com/alibaba/nacos 二:下载完后解压文件,两种方式打开 1 ...

  6. 我引用中没有Spire.Pdf,但是发现无法解析的“Spire.Pdf”的不同版本之间存在冲突

    问题: 导出错误!未能加载文件或程序集"Spire.Pdf, Version=8.6.1.0, Culture=neutral, PublicKeyToken=663f351905198cb ...

  7. 关于js更改编码问题

    前言 前几天调试喜马拉雅的js加密算法,找到固定一段加密算法后调试,发现结果与实际不一致,后来发现是js显示的编码不一致,而我用的密钥是直接通过 chrome控制台复制下来的,这就导致最后结果不一致. ...

  8. day16 异常处理生成器

    day16 异常处理生成器 今日内容概要 异常处理 异常处理实战应用 生成器对象 生成器对象实现range方法 生成器表达式 今日内容详细 一.异常处理 1.异常常见类型 SyntaxError语法错 ...

  9. 【Oracle】Oracle读取RAW二进制类型并实现与十六进制的相互转换

    1.十六进制转二进制 select HEXTORAW('7264B1CD0582734D8E27E0FBDA15B2A5') from dual; 2.二进制转十六进制 select AUUID_0, ...

  10. 【每日一题】【双指针/栈/reverse】2022年2月19日-判断是否为回文字符串

    给定一个长度为 n 的字符串,请编写一个函数判断该字符串是否回文.如果是回文请返回true,否则返回false.   字符串回文指该字符串正序与其逆序逐字符一致.   数据范围:0 < n \l ...