一
Watchdog
概念
Watchdog主要应用于嵌入式系统,用于系统出现严重故障(如内核死锁,进入死循环,CPU跑飞等)不能恢复时,在无人为介入的情况下可以自动重新启动系统。
在传统Linux 内核下, watchdog的基本工作原理是:当watchdog启动后(即/dev/watchdog 设备被打开后),如果在某一设定的时间间隔内/dev/watchdog没有被执行写操作, 硬件watchdog电路或软件定时器就会重新启动系统。
Watchdog根据实现方式又可以分为硬件watchdog和软件watchdog。硬件watchdog必须有硬件电路支持,设备节点/dev/watchdog对应真实的物理设备。软件watchdog通过通过内核定时器来实现,/dev/watchdog并不对应真实的物理设备。
硬件watchdog比软件watchdog有更好的可靠性。软件watchdog最大的优势是成本低,因此在可靠性要求不是很高一般民用产品被广泛使用。硬件watchdog的优势是可靠性高,因此在对可靠性要求严格的工业产品中被广泛使用。
但是在高通平台Android系统中,watchdog的实现有所不同,稍后我们会分析,这里只需知道其并没有提供/dev/watchdog。
当然在系统出现严重故障不能恢复时触发Watchdog,重启系统,仅仅是一个补救措施,虽然有效,但是过于简单粗暴,用户体验不佳
。
解决问题的最好方法是不让问题发生,因此我们需要针对watchdog进行和分析,尽量不让问题不发生。
注意Android系统中还有一套watchdog实现,也是使用软件实现的,用于检测SystemServer中各Service是否正常运行。大家不要搞混了。
如没有特别说明,本文后续提到的watchdog都特指高通平台Android系统kernel中watchdog。
二Watchdog的实现
2.0 Device Tree中watchdog的定义
wdog: qcom,wdt@17817000 {
compatible = "qcom,msm-watchdog";
reg = <0x17817000 0x1000>; //
没有查到对应寄存器的说明
reg-names = "wdt-base";
interrupts = <0 3 0>, <0 4 0>; //
狗叫和狗咬的中断,由于目前的实现是狗叫的同时就进行狗咬,所以只用到了狗叫的中断
qcom,bark-time = <11000>; //
超过
11
秒没有喂狗,连叫带咬,系统重启
qcom,pet-time = <10000>; //
每
10
秒喂狗一次
qcom,ipi-ping; //
喂狗时需要
ping
一下系统中的其他
cpu
,确保所有
cpu
都处于正常状态
qcom,wakeup-enable; //
看门狗具有唤醒系统的能力,如果不具备唤醒能力的话,需要在系统睡眠时关闭看门狗,唤醒时再重新打开看门狗
qcom,scandump-size = <0x40000>; // ramdump
相关
};
2.1核心数据结构struct msm_watchdog_data
Watchdog
的显示在
drivers/soc/qcom/watchdog_v2.c
源文件中。
struct msm_watchdog_data {
unsigned int __iomem phys_base; //
对应
dt
中的
reg
size_t size;
void __iomem *base; //
将的
phy_base
映射到虚拟地址空间
void __iomem *wdog_absent_base;
struct device *dev; //
指向
watchdog
的
device
unsigned int pet_time; //
对应
dt
中的
qcom,pet-time
unsigned int bark_time; //
对应
dt
中的
qcom,bark-time
unsigned int bark_irq; //
狗叫中断
unsigned int bite_irq; //
狗咬中断
bool do_ipi_ping; //
对应
dt
中的
qcom,ipi-ping
bool wakeup_irq_enable; //
对应
dt
中的
qcom,wakeup-enable
unsigned long long last_pet; //
记录上次喂狗时间
unsigned min_slack_ticks;
unsigned long long min_slack_ns;
void *scm_regsave;
cpumask_t alive_mask;
struct mutex disable_lock;
bool irq_ppi;
struct msm_watchdog_data __percpu **wdog_cpu_dd; //
当
irq_ppi
为
true
时才会用到
struct notifier_block panic_blk; //
将会注册到
panic_notifier_list
内核通知链,当内核
panic
会回调
bool enabled; //
标示
watchdog
是否使能
bool user_pet_enabled; //
标示
watchdog
是否对用户空间开放,我们没有定义
qcom,userspace-watchdog
,没有对用户空间开放,因此不去关注
struct task_struct *watchdog_task; // watchdog
的内核进程,名为
msm-watchdog
struct timer_list pet_timer; //
喂狗的定时器
wait_queue_head_t pet_complete; //
喂狗的内核等待队列
bool timer_expired; //
标示喂狗定时器是否到期,
timer
到期后置为
true
,唤醒喂狗的内核等待队列会后置为
false
bool user_pet_complete;
unsigned int scandump_size;
};
2.2 Watchdog的初始化
下列函数略有删减
static int msm_watchdog_probe(struct platform_device *pdev)
{
int ret;
struct msm_watchdog_data *wdog_dd;
if (!pdev->dev.of_node || !enable)
return -ENODEV;
wdog_dd = kzalloc(sizeof(struct msm_watchdog_data), GFP_KERNEL); //
分配
struct msm_watchdog_data
结构体
if (!wdog_dd)
return -EIO;
ret = msm_wdog_dt_to_pdata(pdev, wdog_dd); //
解析
device tree
,设置相应的
struct msm_watchdog_data
成员变量
if (ret)
goto err;
wdog_data = wdog_dd; //
将分配的
struct msm_watchdog_data
结构体
保存到全局变量
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"); //
创建名为
msm-watchdog
的内核进程,进程入口函数
watchdog_kthread
,
wdog_dd
是
watchdog_kthread
的参数,
kthread_create
仅创建进程,并不立即运行
if (IS_ERR(wdog_dd->watchdog_task)) {
ret = PTR_ERR(wdog_dd->watchdog_task);
goto err;
}
init_watchdog_data(wdog_dd); //
继续完善
struct msm_watchdog_data
结构体,并做进一步初始化
return 0;
err:
kzfree(wdog_dd);
return ret;
}
static void init_watchdog_data(struct msm_watchdog_data *wdog_dd)
{
unsigned long delay_time;
uint32_t val;
u64 timeout;
int ret;
{
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;
}
}
delay_time = msecs_to_jiffies(wdog_dd->pet_time); //
喂狗延时
wdog_dd->min_slack_ticks = UINT_MAX;
wdog_dd->min_slack_ns = ULLONG_MAX;
configure_bark_dump(wdog_dd);
timeout = (wdog_dd->bark_time * WDT_HZ)/1000; // 11s * WDT_HZ
__raw_writel(timeout, wdog_dd->base + WDT0_BARK_TIME); //
配置狗叫的时间,
11
秒
__raw_writel(timeout + 3*WDT_HZ, wdog_dd->base + WDT0_BITE_TIME); //
配置狗叫的时间,
14
秒
wdog_dd->panic_blk.notifier_call = panic_wdog_handler; //
手机
panic
时
watchdog
的回调函数
atomic_notifier_chain_register(&panic_notifier_list,
&wdog_dd->panic_blk); //
注册回调函数,
panic_notifier_list
内核通知链将在
panic
函数中被调用
mutex_init(&wdog_dd->disable_lock);
init_waitqueue_head(&wdog_dd->pet_complete); //
初始化喂狗的内核等待队列
wdog_dd->timer_expired = false;
wdog_dd->user_pet_complete = true;
wdog_dd->user_pet_enabled = false;
wake_up_process(wdog_dd->watchdog_task); //
唤醒
msm-watchdog
内核进程
init_timer_deferrable(&wdog_dd->pet_timer); //
初始化喂狗定时器,
deferrable
表示定时器对时间敏感度不是很高,内核可以将接近的几个
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) //
设置
watchdog
有唤醒
cpu
的能力
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; //
标示
watchdog
使能
init_watchdog_sysfs(wdog_dd); //
创建
sysfs
节点
dev_info(wdog_dd->dev, "MSM Watchdog Initialized\n");
return;
}
2.3 Watchdog的工作流程
- 每次喂狗定时器超时后,执行定时器函数pet_task_wakeup,设置timer_expired为ture,唤醒pet_complete等待队列
static void pet_task_wakeup(unsigned long data)
{
struct msm_watchdog_data *wdog_dd =
(struct msm_watchdog_data *)data;
wdog_dd->timer_expired = true;
wake_up(&wdog_dd->pet_complete);
}
- 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};
sched_setscheduler(current, SCHED_FIFO, ¶m); //
将
msm_watchdog
进程设置为实时进程,使用先进先出的调度策略
while (!kthread_should_stop()) { //
判断进程是应该停止
while (wait_event_interruptible( //
判断
timer_expired
是否为
true
,为
true
时退出等待,为
false
时等待在
pet_complete
等待队列上;直到别处调用
wake_up
调用唤醒,则重新根据
timer_expired
是否为
true
,进行等待或继续往下执行
wdog_dd->pet_complete,
wdog_dd->timer_expired) != 0)
;
if (wdog_dd->do_ipi_ping)
ping_other_cpus(wdog_dd); // ping
其他
cpu
,确保所有
cpu
都是活的
while (wait_event_interruptible( // user_pet_complete
为
false
,永不等待
wdog_dd->pet_complete,
wdog_dd->user_pet_complete) != 0)
;
wdog_dd->timer_expired = false; //
将
timer_expired
设为
false
wdog_dd->user_pet_complete = !wdog_dd->user_pet_enabled;
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 pet_watchdog(struct msm_watchdog_data *wdog_dd)
{
int slack, i, count, prev_count = 0;
unsigned long long time_ns;
unsigned long long slack_ns;
unsigned long long bark_time_ns = wdog_dd->bark_time * 1000000ULL;
for (i = 0; i < 2; i++) { //
读取
watchdog
状态寄存器
count = (__raw_readl(wdog_dd->base + WDT0_STS) >> 1) & 0xFFFFF;
if (count != prev_count) {
prev_count = count;
i = 0;
}
}
slack = ((wdog_dd->bark_time * WDT_HZ) / 1000) - count;
if (slack < wdog_dd->min_slack_ticks)
wdog_dd->min_slack_ticks = slack;
__raw_writel(1, wdog_dd->base + WDT0_RST); //
重置
watchdog
,即喂狗
time_ns = sched_clock();
slack_ns = (wdog_dd->last_pet + bark_time_ns) - time_ns;
if (slack_ns < wdog_dd->min_slack_ns)
wdog_dd->min_slack_ns = slack_ns;
wdog_dd->last_pet = time_ns;
}
- 没有按时喂狗,触发bark中断,执行中断处理函数。
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);
printk(KERN_INFO "Watchdog bark! Now = %lu.%06lu\n", (unsigned long) t,
nanosec_rem / 1000); //
打印狗叫的时间
nanosec_rem = do_div(wdog_dd->last_pet, 1000000000);
printk(KERN_INFO "Watchdog last pet at %lu.%06lu\n", (unsigned long)
wdog_dd->last_pet, nanosec_rem / 1000); //
打印上一次喂狗的时间
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;
}
- 狗叫时,系统已经异常,因此无法走正常关机流程,因此需要写watchdog寄存器,由硬件来重启手机。
void msm_trigger_wdog_bite(void)
{
if (!wdog_data)
return;
pr_info("Causing a watchdog bite!");
__raw_writel(1, wdog_data->base + WDT0_BITE_TIME); //
一个
clk
后,狗咬,由硬件处理
mb();
__raw_writel(1, wdog_data->base + WDT0_RST); //
重置
watchdog
mb();
/* Delay to make sure bite occurs */
mdelay(10000); //
等待狗咬完成手机重启
pr_err("Wdog - STS: 0x%x, CTL: 0x%x, BARK TIME: 0x%x, BITE TIME: 0x%x",
__raw_readl(wdog_data->base + WDT0_STS),
__raw_readl(wdog_data->base + WDT0_EN),
__raw_readl(wdog_data->base + WDT0_BARK_TIME),
__raw_readl(wdog_data->base + WDT0_BITE_TIME)); //
手机重启失败,打印
watchdog
一些寄存器信息
}
- panic_wdog_handler其实不属于watchdog的流程,而是kernel panic后,手机关机或重启异常时借助watchdog来完成手机重启的。
static int panic_wdog_handler(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct msm_watchdog_data *wdog_dd = container_of(this,
struct msm_watchdog_data, panic_blk);
if (panic_timeout == 0) { //
我们的系统中
panic_timeout
等于
5
,因此走
else
流程
__raw_writel(0, wdog_dd->base + WDT0_EN);
mb();
} else { //
配置
15
秒后,
watchdog
超时,重启系统
__raw_writel(WDT_HZ * (panic_timeout + 10),
wdog_dd->base + WDT0_BARK_TIME);
__raw_writel(WDT_HZ * (panic_timeout + 10),
wdog_dd->base + WDT0_BITE_TIME);
__raw_writel(1, wdog_dd->base + WDT0_RST);
}
return NOTIFY_DONE;
}
2.4 watchdog工作示意图