Linux 看门狗

Watchdog

Posted by LXG on December 7, 2022

概念

在Linux 内核下, watchdog的基本工作原理是:当watchdog启动后(即/dev/watchdog 设备被打开后),如果在某一设定的时间间隔内/dev/watchdog没有被执行写操作, 硬件watchdog电路或软件定时器就会重新启动系统。

/dev/watchdog 是一个主设备号为10, 从设备号130的字符设备节点。 Linux内核不仅为各种不同类型的watchdog硬件电路提供了驱动,还提供了一个基于定时器的纯软件watchdog驱动

硬件看门狗和软件看门狗区别

  1. 通常情况下,watchdog需要硬件支持,但是如果确实没有相应的硬件,还想使用watchdog功能,则可以使用liunx模拟的watchdog,即软件watchdog。
  2. 硬件watchdog必须有硬件电路支持, 设备节点/dev/watchdog对应着真实的物理设备, 不同类型的硬件watchdog设备由相应的硬件驱动管理。软件watchdog由一内核模块softdog.ko 通过定时器机制实现,/dev/watchdog并不对应着真实的物理设备,只是为应用提供了一个与操作硬件watchdog相同的接口。
  3. 硬件watchdog比软件watchdog有更好的可靠性。 软件watchdog基于内核的定时器实现,当内核或中断出现异常时,软件watchdog将会失效。而硬件watchdog由自身的硬件电路控制, 独立于内核。无论当前系统状态如何,硬件watchdog在设定的时间间隔内没有被执行写操作,仍会重新启动系统。
  4. 对于应用程序而言, 操作软件、硬件watchdog的方式基本相同:打开设备/dev/watchdog, 在重启时间间隔内对/dev/watchdog执行写操作。即软件、硬件watchdog对应用程序而言基本是透明的
  5. 在任一时刻, 只能有一个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

a133_ap_reset

内核配置


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驱动分为以下三层:

  1. 统一driver层: watchdog_dev.c 给应用程序提供接口
  2. 核心层: watchdog_core.c 提供看门狗通用API
  3. 具体的设备层: 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);