概念
在Linux 内核下, watchdog的基本工作原理是:当watchdog启动后(即/dev/watchdog 设备被打开后),如果在某一设定的时间间隔内/dev/watchdog没有被执行写操作, 硬件watchdog电路或软件定时器就会重新启动系统。
/dev/watchdog 是一个主设备号为10, 从设备号130的字符设备节点。 Linux内核不仅为各种不同类型的watchdog硬件电路提供了驱动,还提供了一个基于定时器的纯软件watchdog驱动
硬件看门狗和软件看门狗区别
- 通常情况下,watchdog需要硬件支持,但是如果确实没有相应的硬件,还想使用watchdog功能,则可以使用liunx模拟的watchdog,即软件watchdog。
- 硬件watchdog必须有硬件电路支持, 设备节点/dev/watchdog对应着真实的物理设备, 不同类型的硬件watchdog设备由相应的硬件驱动管理。软件watchdog由一内核模块softdog.ko 通过定时器机制实现,/dev/watchdog并不对应着真实的物理设备,只是为应用提供了一个与操作硬件watchdog相同的接口。
- 硬件watchdog比软件watchdog有更好的可靠性。 软件watchdog基于内核的定时器实现,当内核或中断出现异常时,软件watchdog将会失效。而硬件watchdog由自身的硬件电路控制, 独立于内核。无论当前系统状态如何,硬件watchdog在设定的时间间隔内没有被执行写操作,仍会重新启动系统。
- 对于应用程序而言, 操作软件、硬件watchdog的方式基本相同:打开设备/dev/watchdog, 在重启时间间隔内对/dev/watchdog执行写操作。即软件、硬件watchdog对应用程序而言基本是透明的
- 在任一时刻, 只能有一个watchdog驱动模块被加载,管理/dev/watchdog 设备节点。如果系统没有硬件watchdog电路,则可以加载软件watchdog驱动softdog.ko。
守护进程 watchdogd
service watchdogd /system/bin/watchdogd 5 10 class core disabled seclabel u:r:watchdogd:s0
system/core/watchdogd/
#include <errno.h>
#include <fcntl.h>
#include <linux/watchdog.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cutils/properties.h> // add by lixiaogang
#include <android-base/logging.h>
#define DEV_NAME "/dev/watchdog"
#define PROP_WATCHDOG "persist.sys.watchdog" // add by lixiaogang
int main(int argc, char** argv) {
android::base::InitLogging(argv, &android::base::KernelLogger);
int interval = 10;
if (argc >= 2) interval = atoi(argv[1]);
int margin = 10;
if (argc >= 3) margin = atoi(argv[2]);
LOG(INFO) << "watchdogd started (interval " << interval << ", margin " << margin << ")!";
int fd = open(DEV_NAME, O_RDWR | O_CLOEXEC);
if (fd == -1) {
PLOG(ERROR) << "Failed to open " << DEV_NAME;
return 1;
}
int timeout = interval + margin;
int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
if (ret) {
PLOG(ERROR) << "Failed to set timeout to " << timeout;
ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
if (ret) {
PLOG(ERROR) << "Failed to get timeout";
} else {
if (timeout > margin) {
interval = timeout - margin;
} else {
interval = 1;
}
LOG(WARNING) << "Adjusted interval to timeout returned by driver: "
<< "timeout " << timeout << ", interval " << interval << ", margin "
<< margin;
}
}
char property[PROPERTY_VALUE_MAX];
while (true) {
// modify by lixiaogang for debug start
// Allow user to have a strong opinion about state
property_get(PROP_WATCHDOG, property, "1");
if (!strcmp(property, "1")) {
LOG(INFO) << "=================dog================";
write(fd, "", 1); //喂狗
} else {
LOG(INFO) << "=================no dog================";
}
//write(fd, "", 1);
// modify by lixiaogang end
sleep(interval);
}
}
Linux 驱动指令
kernel/linux-4.9/include/uapi/linux/watchdog.h
#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
全志 A133 WDT
内核配置
CONFIG_WATCHDOG=y
CONFIG_WATCHDOG_CORE=y
CONFIG_SUNXI_WATCHDOG=y
Device Tree
soc: soc@03000000 {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <2>;
ranges;
device_type = "soc";
wdt: watchdog@030090a0 {
compatible = "allwinner,sun50i-wdt";
reg = <0x0 0x030090a0 0x0 0x20>;
interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
}
drivers/watchdog/sunxi_wdt.c
static int sunxi_wdt_probe(struct platform_device *pdev)
{
dev_err(&pdev->dev, "sunxi_wdt_probe \n");
struct sunxi_wdt_dev *sunxi_wdt;
const struct of_device_id *device;
struct resource *res;
int err;
sunxi_wdt = devm_kzalloc(&pdev->dev, sizeof(*sunxi_wdt), GFP_KERNEL);
if (!sunxi_wdt)
return -EINVAL;
platform_set_drvdata(pdev, sunxi_wdt);
device = of_match_device(sunxi_wdt_dt_ids, &pdev->dev);
if (!device)
return -ENODEV;
sunxi_wdt->wdt_regs = device->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(sunxi_wdt->wdt_base))
return PTR_ERR(sunxi_wdt->wdt_base);
sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;
sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
sunxi_wdt->wdt_dev.parent = &pdev->dev;
watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev);
watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);
watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
err = watchdog_register_device(&sunxi_wdt->wdt_dev);
if (unlikely(err))
return err;
dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
sunxi_wdt->wdt_dev.timeout, nowayout);
return 0;
}
static const struct of_device_id sunxi_wdt_dt_ids[] = {
{ .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },
{ .compatible = "allwinner,sun4i-wdt", .data = &sun4i_wdt_reg },
{ .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },
{ .compatible = "allwinner,sun50i-wdt", .data = &sun6i_wdt_reg },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
static struct platform_driver sunxi_wdt_driver = {
.probe = sunxi_wdt_probe,
.remove = sunxi_wdt_remove,
.shutdown = sunxi_wdt_shutdown,
#ifdef CONFIG_PM
.suspend = sunxi_wdt_suspend,
.resume = sunxi_wdt_resume,
#endif
.driver = {
.name = DRV_NAME,
.of_match_table = sunxi_wdt_dt_ids,
},
};
module_platform_driver(sunxi_wdt_driver);
module_param(timeout, uint, 0);
MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");
MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");
MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");
MODULE_VERSION(DRV_VERSION);
看门狗驱动架构
watchdog驱动分为以下三层:
- 统一driver层: watchdog_dev.c 给应用程序提供接口
- 核心层: watchdog_core.c 提供看门狗通用API
- 具体的设备层: sunxi_wdt.c 操作具体的SOC寄存器
watchdog_dev.c
static const struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
.write = watchdog_write,
.unlocked_ioctl = watchdog_ioctl,
.open = watchdog_open,
.release = watchdog_release,
};
sunxi_wdt.c
static const struct watchdog_ops sunxi_wdt_ops = {
.owner = THIS_MODULE,
.start = sunxi_wdt_start,
.stop = sunxi_wdt_stop,
.ping = sunxi_wdt_ping,
.set_timeout = sunxi_wdt_set_timeout,
.restart = sunxi_wdt_restart,
};
watchdog_core.h
extern int watchdog_dev_register(struct watchdog_device *);
extern void watchdog_dev_unregister(struct watchdog_device *);
extern int __init watchdog_dev_init(void);
extern void __exit watchdog_dev_exit(void);