源码中的文档:
不论是软狗还是硬狗,大体思路都差不多:
准备一个定时器,定时10s,超时之后启动地球毁灭程序~~~。为了防止地球毁灭,需要时不时的将计时器的计数值清零,称为喂狗。一般看门狗定时器是无论系统跑飞还是卡死都能正常运行的,一般超时后直接拉CPU的reset引脚,让系统复位。
先看probe函数:
static int msm_watchdog_probe(struct platform_device *pdev)
{
int ret;
struct msm_watchdog_data *wdog_dd;
struct md_region md_entry;
if (!pdev->dev.of_node || !enable)
return -ENODEV;
wdog_dd = kzalloc(sizeof(struct msm_watchdog_data), GFP_KERNEL);
if (!wdog_dd)
return -EIO;
ret = msm_wdog_dt_to_pdata(pdev, wdog_dd);
if (ret)
goto err;
wdog_data = wdog_dd;
wdog_dd->dev = &pdev->dev;
platform_set_drvdata(pdev, wdog_dd);
cpumask_clear(&wdog_dd->alive_mask);
wdog_dd->watchdog_task = kthread_create(watchdog_kthread, wdog_dd,
"msm_watchdog");
if (IS_ERR(wdog_dd->watchdog_task)) {
ret = PTR_ERR(wdog_dd->watchdog_task);
goto err;
}
init_watchdog_data(wdog_dd);
return 0;
err:
kzfree(wdog_dd);
return ret;
}
probe主要就是申请了msm_watchdog_data的内存,将pdev和pdata做了绑定,再通过msm_wdog_dt_to_pdata将设备树的信息获取过来。值得关注的是还创建了一个名为msm_watchdog的内核线程:
线程执行watchdog_kthread,只是创建,还没投入执行。而后继续调用init_watchdog_data继续完善核心数据结构。
从设备树获取watchdog配置:
static int msm_wdog_dt_to_pdata(struct platform_device *pdev,
struct msm_watchdog_data *pdata)
{
struct device_node *node = pdev->dev.of_node;
struct resource *res;
int ret, cpu, num_scandump_sizes;
//获取看门狗的硬件内存信息
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-base");
if (!res)
return -ENODEV;
pdata->size = resource_size(res);
pdata->phys_base = res->start;
if (unlikely(!(devm_request_mem_region(&pdev->dev, pdata->phys_base,
pdata->size, "msm-watchdog")))) {
dev_err(&pdev->dev, "%s cannot reserve watchdog region\n",
__func__);
return -ENXIO;
}
pdata->base = devm_ioremap(&pdev->dev, pdata->phys_base,
pdata->size);
if (!pdata->base) {
dev_err(&pdev->dev, "%s cannot map wdog register space\n",
__func__);
return -ENXIO;
}
//获取狗叫和狗咬中断资源
pdata->bark_irq = platform_get_irq(pdev, 0);
pdata->bite_irq = platform_get_irq(pdev, 1);
//获取狗叫狗咬的超时信息
ret = of_property_read_u32(node, "qcom,bark-time", &pdata->bark_time);
ret = of_property_read_u32(node, "qcom,pet-time", &pdata->pet_time);
//获取是否喂狗时ping其他cpu
pdata->do_ipi_ping = of_property_read_bool(node, "qcom,ipi-ping");
//获取看门狗是否再系统休眠唤醒时自动停止和恢复
pdata->wakeup_irq_enable = of_property_read_bool(node,
"qcom,wakeup-enable");
//获取狗叫中断是否是CPU私有中断,PPI中断
pdata->irq_ppi = irq_is_percpu(pdata->bark_irq);
dump_pdata(pdata);
return 0;
}
进一步填充watchdog data:
static void init_watchdog_data(struct msm_watchdog_data *wdog_dd)
{
unsigned long delay_time;
uint32_t val;
u64 timeout;
int ret;
//申请狗叫中断
if (wdog_dd->irq_ppi) {
wdog_dd->wdog_cpu_dd = alloc_percpu(struct msm_watchdog_data *);
if (!wdog_dd->wdog_cpu_dd) {
dev_err(wdog_dd->dev, "fail to allocate cpu data\n");
return;
}
*raw_cpu_ptr(wdog_dd->wdog_cpu_dd) = wdog_dd;
ret = request_percpu_irq(wdog_dd->bark_irq, wdog_ppi_bark,
"apps_wdog_bark",
wdog_dd->wdog_cpu_dd);
if (ret) {
dev_err(wdog_dd->dev, "failed to request bark irq\n");
free_percpu(wdog_dd->wdog_cpu_dd);
return;
}
} else {
ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq,
wdog_bark_handler, IRQF_TRIGGER_RISING,
"apps_wdog_bark", wdog_dd);
if (ret) {
dev_err(wdog_dd->dev, "failed to request bark irq\n");
return;
}
}
//将喂狗延时转换成jiffies
delay_time = msecs_to_jiffies(wdog_dd->pet_time);
//初始化min_slack_ticks[ns]
wdog_dd->min_slack_ticks = UINT_MAX;
wdog_dd->min_slack_ns = ULLONG_MAX;
//为了写到狗叫和狗咬寄存器中,必须将设备树中ms单位的时间转成写寄存器时的单位
timeout = (wdog_dd->bark_time * WDT_HZ)/1000;
//狗叫时间来自于bark_time
__raw_writel(timeout, wdog_dd->base + WDT0_BARK_TIME);
//狗咬时间来自于bark_time + 3s
__raw_writel(timeout + 3*WDT_HZ, wdog_dd->base + WDT0_BITE_TIME);
//panic时,系统已经挂了,所以需要借助watchdog之手重启。
wdog_dd->panic_blk.notifier_call = panic_wdog_handler;
atomic_notifier_chain_register(&panic_notifier_list,
&wdog_dd->panic_blk);
//初始化后面会用到的变量
mutex_init(&wdog_dd->disable_lock);
init_waitqueue_head(&wdog_dd->pet_complete);
/*
*这段代码主要做了以下几件事情:
*1.让前面提到的watchdog_task跑起来
*2.让喂狗定时器pet_timer跑起来
*3.使能复位看门狗定时器,让其工作起来
*/
wdog_dd->timer_expired = false;
wake_up_process(wdog_dd->watchdog_task);
init_timer(&wdog_dd->pet_timer);
wdog_dd->pet_timer.data = (unsigned long)wdog_dd;
wdog_dd->pet_timer.function = pet_task_wakeup;
wdog_dd->pet_timer.expires = jiffies + delay_time;
add_timer(&wdog_dd->pet_timer);
val = BIT(EN);
if (wdog_dd->wakeup_irq_enable)
val |= BIT(UNMASKED_INT_EN);
__raw_writel(val, wdog_dd->base + WDT0_EN);
__raw_writel(1, wdog_dd->base + WDT0_RST);
wdog_dd->last_pet = sched_clock();
wdog_dd->enabled = true;
init_watchdog_sysfs(wdog_dd);
if (wdog_dd->irq_ppi)
enable_percpu_irq(wdog_dd->bark_irq, 0);
if (!ipi_en)
cpu_pm_register_notifier(&wdog_cpu_pm_nb);
dev_info(wdog_dd->dev, "MSM Watchdog Initialized\n");
}
走到这里,整个watchdog初始化已经完成了,看看门狗定时器也愉快地跑起来了。留下了一个
pet_timer,一个内核线程watchdog_task。
先看pet_timer,pet_time到了之后,会执行pet_task_wakeup:
static void pet_task_wakeup(unsigned long data)
{
struct msm_watchdog_data *wdog_dd =
(struct msm_watchdog_data *)data;
wdog_dd->timer_expired = true;
wdog_dd->timer_fired = sched_clock();
wake_up(&wdog_dd->pet_complete);
}
主要就是把在等pet_complete的进程全部唤醒起来检查条件了,搜索了源码,就是前面的watchdog_task在列队等待:
wdog_dd->watchdog_task = kthread_create(watchdog_kthread, wdog_dd, "msm_watchdog");
static __ref int watchdog_kthread(void *arg)
{
struct msm_watchdog_data *wdog_dd =
(struct msm_watchdog_data *)arg;
unsigned long delay_time = 0;
//设置该进程位实时进程
struct sched_param param = {.sched_priority = MAX_RT_PRIO-1};
int ret, cpu;
sched_setscheduler(current, SCHED_FIFO, ¶m);
while (!kthread_should_stop()) {
do {
//开始在pet_complete等待,pet_timer会在超时时设置timer_expired位true
ret = wait_event_interruptible(wdog_dd->pet_complete,
wdog_dd->timer_expired);
} while (ret != 0);
wdog_dd->thread_start = sched_clock();
//cpu_present_mask - has bit 'cpu' set iff cpu is populated, 就是所有已经用起来的CPU,不论是不是在线
for_each_cpu(cpu, cpu_present_mask)
wdog_dd->ping_start[cpu] = wdog_dd->ping_end[cpu] = 0;
//ping一下其他CPU,确保其他CPU都能正常响应核间中断
if (wdog_dd->do_ipi_ping)
ping_other_cpus(wdog_dd);
wdog_dd->timer_expired = false;
if (enable) {
delay_time = msecs_to_jiffies(wdog_dd->pet_time);
pet_watchdog(wdog_dd);
}
/* Check again before scheduling
* Could have been changed on other cpu
*/
mod_timer(&wdog_dd->pet_timer, jiffies + delay_time);
}
return 0;
}
static void ping_other_cpus(struct msm_watchdog_data *wdog_dd)
{
int cpu;
cpumask_clear(&wdog_dd->alive_mask);
/* Make sure alive mask is cleared and set in order */
smp_mb();
//遍历所有在线的cpu,在ping_start[cpu]填ping的时间,然后依次等这些CPU执行keep_alive_response
for_each_cpu(cpu, cpu_online_mask) {
if (!cpu_idle_pc_state[cpu] && !cpu_isolated(cpu)) {
//把ping这个cpu的时间填进对应index,然后让cpu执行keep_alive_response
wdog_dd->ping_start[cpu] = sched_clock();
smp_call_function_single(cpu, keep_alive_response,
wdog_dd, 1);
}
}
}
static void keep_alive_response(void *info)
{
int cpu = smp_processor_id();
struct msm_watchdog_data *wdog_dd = (struct msm_watchdog_data *)info;
//将自己对应的位置1
cpumask_set_cpu(cpu, &wdog_dd->alive_mask);
//cpu收到IPI中断后,将收到的时间天道对应的index
wdog_dd->ping_end[cpu] = sched_clock();
/* Make sure alive mask is cleared and set in order */
smp_mb();
}
当所有在线的CPU都执行了keep_alive_response,接着通过pet_watchdog重置一下wathdong定时器并重新启动pet-timer。
所以,高通平台通过启动一个线程定时"ping"其他cpu的方式,确保所有在线的cpu都是活的。
那如果pet_timer定时到了,而喂狗线程得不到运行或者来不及喂狗触发狗咬是什么情况呢?
这要看看当时注册的狗叫(狗咬没注册)中断对应的句柄:
static irqreturn_t wdog_bark_handler(int irq, void *dev_id)
{
struct msm_watchdog_data *wdog_dd = (struct msm_watchdog_data *)dev_id;
unsigned long nanosec_rem;
//获取当前时间
unsigned long long t = sched_clock();
//一下这段操作就是打印出上次喂狗的时间和,当前的时间。
nanosec_rem = do_div(t, 1000000000);
dev_info(wdog_dd->dev, "Watchdog bark! Now = %lu.%06lu\n",
(unsigned long) t, nanosec_rem / 1000);
nanosec_rem = do_div(wdog_dd->last_pet, 1000000000);
dev_info(wdog_dd->dev, "Watchdog last pet at %lu.%06lu\n",
(unsigned long) wdog_dd->last_pet, nanosec_rem / 1000);
//打印上次喂狗时活着的CPU有哪些
if (wdog_dd->do_ipi_ping)
dump_cpu_alive_mask(wdog_dd);
//触发狗咬
msm_trigger_wdog_bite();
panic("Failed to cause a watchdog bite! - Falling back to kernel panic!");
return IRQ_HANDLED;
}
static void dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd)
{
static char alive_mask_buf[MASK_SIZE];
//上次喂狗时,每个cpu执行keep_alive_response之后会对对应位置1
scnprintf(alive_mask_buf, MASK_SIZE, "%*pb1", cpumask_pr_args(
&wdog_dd->alive_mask));
dev_info(wdog_dd->dev, "cpu alive mask from last pet %s\n",
alive_mask_buf);
}
这么看的话,超时没有喂狗,如果有CPU能正常响应狗叫中断的话,是会打印一些信息,然后触发狗咬的,可见高通平台watchdog是直接连叫带咬的。
通过查看源码,可以了解到:
1.pet_timer是个低精度计时器,低精度定时器会在每个tick中断来时被检查,超时就触发软中断,而linux的软中断又会再中断返回时检查执行。所以只要有CPU还能响应中断pet_task_wakeup就能被执行,但是pet_task_wakeup也只是唤醒了喂狗线程而已,并没有直接去允许它,具体什么时候允许要看下次抢占点到了pet-thread能不能成功抢占cpu执行。
2.watchdog_kthread作为一个实时进程,而且优先级最高加上唤醒抢占的机制,一旦喂狗线程被加到某个CPU的就绪列队中,一般很快就会被执行并且基本没有进程可以抢占它:
https://www.cnblogs.com/shihuvini/p/9974771.html
3.所以没法喂狗会发生在以下情况:
喂狗线程所在cpu长时间关抢占,导致即使pet_timer唤醒喂狗线程但得不到执行。
喂狗线程所在的cpu频繁被中断打断,导致没法按时喂狗。
其他cpu长时间处于关中断导致无法响应ipi中断。
所有cpu长时间处于关中断导致直接触发狗咬(pet_timer无法执行,狗叫中断无法响应)。
值得一提的是,关中断其实就是关抢占,显式调用关中断之后关抢占只是为了增加抢占点罢了。
喂狗线程所在cpu的调用栈(在进程上下文ping其他cpu并喂狗):
被喂狗进程ping的cpu的调用栈(在中断上下文响应ping):