定时器看门狗IWDG: 独立于系统之外,因为有独立时钟,所以不受系统影响的系统故障探测器,主要用于监视硬件错误;
窗口看门狗WWDG:系统内部的故障探测器,时钟与系统相同。如果系统时钟不走了,这个狗也就失去了作用了,主要用于监视软件错误。
这里只对定时器看门狗分析,不对窗口看门狗分析!!!
platform设备注册:
static struct resource nuc970_wdt_resource[] = {
[0] = {
.start = NUC970_PA_WDT,
.end = NUC970_PA_WDT + NUC970_SZ_WDT - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_WDT,
.end = IRQ_WDT,
.flags = IORESOURCE_IRQ,
}
};
struct platform_device nuc970_device_wdt = {
.name = "nuc970-wdt",
.id = -1,
.num_resources = ARRAY_SIZE(nuc970_wdt_resource),
.resource = nuc970_wdt_resource,
};
platform_device_register(nuc970_device_wdt)
platform设备注册主要是将看门狗设备的寄存器资源注册到platform总线上。
看门狗信息:
struct watchdog_info {
__u32 options; /* Options the card/driver supports */
__u32 firmware_version; /* Firmware version of the card */
__u8 identity[32]; /* Identity of the board */
};
对看门狗寄存器操作的句柄:
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); //设置看门狗超时时间
unsigned int (*get_timeleft)(struct watchdog_device *); //???
void (*ref)(struct watchdog_device *);
void (*unref)(struct watchdog_device *);
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); //看门狗操作
};
看门狗设备:
struct watchdog_device {
int id;
struct cdev cdev;
struct device *dev;
struct device *parent;
const struct watchdog_info *info; //指向看门狗信息
const struct watchdog_ops *ops; //指向看门狗操作句柄
unsigned int bootstatus;
unsigned int timeout; //看门狗喂狗的超时时间
unsigned int min_timeout; //最小超时时间
unsigned int max_timeout; //最大超时事件
void *driver_data;
struct mutex lock;
unsigned long status; //看门狗的执行状态
/* Bit numbers for status flags */
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
#define WDOG_DEV_OPEN 1 /* Opened via /dev/watchdog ? */
#define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */
#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
#define WDOG_UNREGISTERED 4 /* Has the device been unregistered */
};
上述结构体定义:
static const struct watchdog_info nuc970wdt_info = {
.identity = "nuc970 watchdog",
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
};
static struct watchdog_ops nuc970wdt_ops = {
.owner = THIS_MODULE,
.start = nuc970wdt_start,
.stop = nuc970wdt_stop,
.ping = nuc970wdt_ping,
.set_timeout = nuc970wdt_set_timeout,
};
static struct watchdog_device nuc970_wdd = {
.status = WATCHDOG_NOWAYOUT_INIT_STATUS,
.info = &nuc970wdt_info,
.ops = &nuc970wdt_ops,
};
其中看门狗启动:
static int nuc970wdt_start(struct watchdog_device *wdd)
{
unsigned int val = 0;
val |= (WTRE | WTE | WTR);
if(wdd->timeout < 2) {
val |= 0x5 << 8;
} else if (wdd->timeout < 8) {
val |= 0x6 << 8;
} else {
val |= 0x7 << 8;
}
Unlock_RegWriteProtect();
__raw_writel(val, REG_WDT_CR);
Lock_RegWriteProtect();
return 0;
}
看门狗停止:
static int nuc970wdt_stop(struct watchdog_device *wdd)
{
Unlock_RegWriteProtect();
__raw_writel(0, REG_WDT_CR);
Lock_RegWriteProtect();
return 0;
}
看门狗超时时间设置:
static int nuc970wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
{
unsigned int val;
Unlock_RegWriteProtect();
val = __raw_readl(REG_WDT_CR);
val &= ~WTIS;
if(timeout < 2) {
val |= 0x5 << 8;
} else if (timeout < 8) {
val |= 0x6 << 8;
} else {
val |= 0x7 << 8;
}
__raw_writel(val, REG_WDT_CR);
Lock_RegWriteProtect();
return 0;
}
这里多说一句,关于看门狗的超时时间,必须要结合datasheet才能计算超时时间!!!
喂狗:
static int nuc970wdt_ping(struct watchdog_device *wdd)
{
unsigned int val;
Unlock_RegWriteProtect();
val = __raw_readl(REG_WDT_CR);
val |= WTR;
__raw_writel(val, REG_WDT_CR);
Lock_RegWriteProtect();
return 0;
}
static struct platform_driver nuc970wdt_driver = {
.probe = nuc970wdt_probe,
.remove = nuc970wdt_remove,
.shutdown = nuc970wdt_shutdown,
.suspend = nuc970wdt_suspend,
.resume = nuc970wdt_resume,
.driver = {
.name = "nuc970-wdt",
.owner = THIS_MODULE,
},
};
module_platform_driver(nuc970wdt_driver);
这里将看门狗驱动注册到platform总线上,通过驱动名称“nuc970-wdt”与对应的平台设备匹配,成功将调用nuc970wdt_probe探测函数。
static int nuc970wdt_probe(struct platform_device *pdev)
{
int ret = 0;
struct clk *clkmux, *clklxt;
nuc970_wdt = devm_kzalloc(&pdev->dev, sizeof(struct nuc970_wdt), GFP_KERNEL);
if (!nuc970_wdt)
return -ENOMEM;
nuc970_wdt->pdev = pdev;
nuc970_wdt->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (nuc970_wdt->res == NULL) {
dev_err(&pdev->dev, "no memory resource specified\n");
return -ENOENT;
}
if (!devm_request_mem_region(&pdev->dev, nuc970_wdt->res->start,
resource_size(nuc970_wdt->res), pdev->name)) {
dev_err(&pdev->dev, "failed to get memory region\n");
return -ENOENT;
}
//因看门狗驱动有4个时钟源可供选择,关于时钟架构分析详见另外一篇博客
clkmux = clk_get(NULL, "wdt_eclk_mux");
if (IS_ERR(clkmux)) {
dev_err(&pdev->dev, "failed to get watchdog clock mux\n");
ret = PTR_ERR(clkmux);
return ret;
}
//这里选择时钟源为32K的外部时钟
clklxt = clk_get(NULL, "xin32k");
if (IS_ERR(clklxt)) {
dev_err(&pdev->dev, "failed to get 32k clk\n");
ret = PTR_ERR(clklxt);
return ret;
}
//设置时钟源
clk_set_parent(clkmux, clklxt);
nuc970_wdt->clk = clk_get(NULL, "wdt");
if (IS_ERR(nuc970_wdt->clk)) {
dev_err(&pdev->dev, "failed to get watchdog clock\n");
ret = PTR_ERR(nuc970_wdt->clk);
return ret;
}
//启动看门狗时钟源
clk_prepare(nuc970_wdt->clk);
clk_enable(nuc970_wdt->clk);
//获取看门狗使能
nuc970_wdt->eclk = clk_get(NULL, "wdt_eclk"); //enable
if (IS_ERR(nuc970_wdt->eclk)) {
dev_err(&pdev->dev, "failed to get watchdog eclock\n");
ret = PTR_ERR(nuc970_wdt->eclk);
return ret;
}
//使能看门狗
clk_prepare(nuc970_wdt->eclk);
clk_enable(nuc970_wdt->eclk);
nuc970_wdd.timeout = 2; // default time out = 2 sec (2.03)
nuc970_wdd.min_timeout = 1; // min time out = 1 sec (0.53)
nuc970_wdd.max_timeout = 9; // max time out = 9 sec (8.03)
//nuc970_wdd结构体见下面
watchdog_init_timeout(&nuc970_wdd, heartbeat, &pdev->dev);
watchdog_set_nowayout(&nuc970_wdd, nowayout);
//看门狗注册设备
ret = watchdog_register_device(&nuc970_wdd);
if (ret) {
dev_err(&pdev->dev, "err register watchdog device\n");
clk_disable(nuc970_wdt->clk);
clk_put(nuc970_wdt->clk);
return ret;
}
return 0;
}
watchdog_register_device()函数将当前看门狗驱动注册到看门狗核心core层。
//看门狗注册设备
ret = watchdog_register_device(&nuc970_wdd);
int watchdog_register_device(struct watchdog_device *wdd)
{
int ret, id, devno;
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
return -EINVAL;
/* Mandatory operations need to be supported */
if (wdd->ops->start == NULL || wdd->ops->stop == NULL)
return -EINVAL;
watchdog_check_min_max_timeout(wdd); //校验看门狗的时间
/*
* Note: now that all watchdog_device data has been verified, we
* will not check this anymore in other functions. If data gets
* corrupted in a later stage then we expect a kernel panic!
*/
//从watchdog_ida中获取一个空id
mutex_init(&wdd->lock);
id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);
if (id < 0)
return id;
wdd->id = id;
//看门狗设备注册,主要包括字符设备的初始化cdev_init、添加cdev_add
ret = watchdog_dev_register(wdd);
if (ret) {
ida_simple_remove(&watchdog_ida, id);
if (!(id == 0 && ret == -EBUSY))
return ret;
/* Retry in case a legacy watchdog module exists */
id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL);
if (id < 0)
return id;
wdd->id = id;
ret = watchdog_dev_register(wdd);
if (ret) {
ida_simple_remove(&watchdog_ida, id);
return ret;
}
}
//看门狗设备模型的创建
devno = wdd->cdev.dev;
wdd->dev = device_create(watchdog_class, wdd->parent, devno,
NULL, "watchdog%d", wdd->id);
if (IS_ERR(wdd->dev)) {
watchdog_dev_unregister(wdd);
ida_simple_remove(&watchdog_ida, id);
ret = PTR_ERR(wdd->dev);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(watchdog_register_device);
该函数内部主要分析watchdog_dev_register()看门狗设备注册
//看门狗设备注册,主要包括字符设备的初始化cdev_init、添加cdev_add
ret = watchdog_dev_register(wdd);
int watchdog_dev_register(struct watchdog_device *watchdog)
{
int err, devno;
//如果watchdog->id==0就注册为杂设备
if (watchdog->id == 0) {
old_wdd = watchdog;
watchdog_miscdev.parent = watchdog->parent;
err = misc_register(&watchdog_miscdev);
if (err != 0) {
pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
watchdog->info->identity, WATCHDOG_MINOR, err);
if (err == -EBUSY)
pr_err("%s: a legacy watchdog module is probably present.\n",
watchdog->info->identity);
old_wdd = NULL;
return err;
}
}
/* Fill in the data structures */
devno = MKDEV(MAJOR(watchdog_devt), watchdog->id);
cdev_init(&watchdog->cdev, &watchdog_fops); //绑定函数句柄,供应用层的系统调用
watchdog->cdev.owner = watchdog->ops->owner;
/* Add the device */
err = cdev_add(&watchdog->cdev, devno, 1);
if (err) {
pr_err("watchdog%d unable to add device %d:%d\n",
watchdog->id, MAJOR(watchdog_devt), watchdog->id);
if (watchdog->id == 0) {
misc_deregister(&watchdog_miscdev);
old_wdd = NULL;
}
}
return err;
}
该函数内部要特别注意watchdog_fops结构体,这里贴出源码:
static const struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
.write = watchdog_write, //对接应用层的write
.unlocked_ioctl = watchdog_ioctl, //对接应用层的ioctl
.open = watchdog_open, //对接应用层的open
.release = watchdog_release,
};
通过对watchdog_open、watchdog_write、watchdog_ioctl这几个函数分析,发现他们是供应用层系统调用的API,即当我们在应用层操作如下步骤时:
1> 打开看门狗设备时,经过文件系统,最终会调用这里的watchdog_open函数;
2> 向看门狗设备写操作时,经过文件系统,最终会调用这里的watchdog_write函数;
3> 向看门狗设备ioctl操作时,经过文件系统,最终会调用这里的watchdog_ioctl函数;
#include
#include
#include
#include
#include
#include
#include
#include
int main(void)
{
int ii = 10, j;
int fd = open("/dev/watchdog", O_RDWR);
if (fd == -1) {
perror("watchdog");
exit(EXIT_FAILURE);
}
printf("Open watchdog ok\n");
// Ping WDT for 10 times, then let system reset
while (1) {
printf("ii = %d\n", ii);
if(ii-- > 0)
ioctl(fd, WDIOC_KEEPALIVE, 0);
sleep(1);
}
close(fd);
return 0;
}
看门狗驱动比较简单,相比之前分析的usb驱动,简直是“小巫见大巫”,不过“麻雀虽小五脏俱全”,看门狗驱动包括了所有驱动共有的注册流程,platform平台设备和驱动的匹配,调用探测函数,完成设备的注册,其中也包括设备模型的注册...就到这里,今天过节,要准备做饭咯!!!