0. 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。
1. 高通watchdog的种类
看门狗(WD 或 WDOG)是一种固定长度计数器,使系统能够从意外的硬件或软件灾难中恢复。
除非系统定期重置看门狗定时器,否则看门狗定时器会假设发生灾难,并根据哪个看门狗触发来重置子系统或整个系统。
一般来说,看门狗的实现有多种类型,硬件看门狗、软件看门狗、吠叫、咬合等。
看门狗类型 | 超时时间 | Owner | Expires during | Result |
---|---|---|---|---|
Nonsecure (NS) WD bark | 11s | HLOS | IRQ to HLOS | HLOS falls to Panic |
Nonsecure (NS) WD bite | 12s | HLOS | FIQ to TZ | TZ asserts PS_HOLD |
Secure WD bark | 6s | TZ | FIQ to TZ | TZ just pets secure WD |
Secure WD bite | 22s | TZ | asserting PS_HOLD | PMIC resets the system |
AOP hardware WD bark | 10ms | AOP | IRQ to AOP | AOP falls to error fatal |
AOP hardware WD bite | 30ms | AOP | IRQ to application processor | HLOS falls to Panic |
Software WD timeout | 10s | User tasks on SS | calls Error fatal | SS Error fatal1 2 |
(SS) hardware WD bark | 2.25s | Dog task on SS | FIQ to error handler | SS Error fatal/pet WD1 2 |
(SS) Nonmaskable interrupt (NMI) due to HW WD | 2.4s | Dog task on SS | NMI to SS | NMI on SS1 |
(SS) Hardware WD bite | 2.5s | Dog task on SS | IRQ to HLOS | SS hardware reset1 |
注意:本文不涉及子系统的watchdog类型以及hardware watchdog,且专注于在开发过程中遇到的最多的watchdog,也就是Watchdog for APPS CPU。
2. 高通watchdog的实现
2.1 devicetree中watchdog的定义
wdog: qcom,wdt@f410000 {
compatible = "qcom,msm-watchdog";
reg = <0xf410000 0x1000>;
reg-names = "wdt-base";
interrupts = <0 0 IRQ_TYPE_LEVEL_HIGH>,
<0 1 IRQ_TYPE_LEVEL_HIGH>;
qcom,bark-time = <11000>; // 超过 11 秒没有喂狗,连叫带咬,系统重启
qcom,pet-time = <9360>; // 每 9 秒喂狗一次
qcom,ipi-ping; // 喂狗时需要 ping 一下系统中的其他 cpu ,确保所有 cpu 都处于正常状态
qcom,wakeup-enable; // 看门狗具有唤醒系统的能力,如果不具备唤醒能力的话,需要在系统睡眠时关闭看门狗,唤醒时再重新打开看门狗
};
注意:qcom,bark-time
和qcom,pet-time
可能已失效,在defconfig中利用CONFIG_QCOM_WATCHDOG_BARK_TIME
和CONFIG_QCOM_WATCHDOG_PET_TIME
设置。
2.2 核心数据结构struct msm_watchdog_data
struct msm_watchdog_data {
void __iomem *base; //watchdog的reg设备内存映射到虚拟地址空间的地址
struct device *dev;
struct qcom_wdt_ops *ops; // watchdog函数,下面介绍
unsigned int pet_time; //pet的时间
unsigned int bark_time; //bark的时间
unsigned int bark_irq; //bark中断
bool do_ipi_ping; //对应dts中的qcom.ipi-ping
bool in_panic;
bool wakeup_irq_enable; //对应dts中的qcom,wakeup-enable
bool irq_ppi;
unsigned long long last_pet; //记录上次的喂狗时间
cpumask_t alive_mask;
struct mutex disable_lock;
struct msm_watchdog_data * __percpu *wdog_cpu_dd; // 当 irq_ppi 为 true 时才会用到
struct notifier_block panic_blk; // 将会注册到 panic_notifier_list 内核通知链,当内核 panic 会回调
struct notifier_block die_blk;
struct notifier_block wdog_cpu_pm_nb;
struct notifier_block restart_blk;
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 long long timer_fired;
unsigned long long thread_start;
unsigned long long ping_start[NR_CPUS];
unsigned long long ping_end[NR_CPUS];
int cpu_idle_pc_state[NR_CPUS];
bool freeze_in_progress;
spinlock_t freeze_lock;
struct timer_list user_pet_timer;
bool hibernate;
};
2.3 watchdog核心操作api
struct qcom_wdt_ops {
int (*set_bark_time)(u32 time, struct msm_watchdog_data *wdog_dd);
int (*set_bite_time)(u32 time, struct msm_watchdog_data *wdog_dd);
int (*reset_wdt)(struct msm_watchdog_data *wdog_dd);
int (*enable_wdt)(u32 val, struct msm_watchdog_data *wdog_dd);
int (*disable_wdt)(struct msm_watchdog_data *wdog_dd);
int (*show_wdt_status)(struct msm_watchdog_data *wdog_dd);
};
static struct qcom_wdt_ops qcom_soc_wdt_ops = {
.set_bark_time = qcom_soc_set_wdt_bark,
.set_bite_time = qcom_soc_set_wdt_bite,
.reset_wdt = qcom_soc_reset_wdt,
.enable_wdt = qcom_soc_enable_wdt,
.disable_wdt = qcom_soc_disable_wdt,
.show_wdt_status = qcom_soc_show_wdt_status
};
2.3.1 qcom_soc_set_wdt_bark
#define WDT0_BARK_TIME 0x10
static inline int qcom_soc_set_wdt_bark(u32 time,
struct msm_watchdog_data *wdog_dd)
{
__raw_writel((time * WDT_HZ)/1000, wdog_dd->base + WDT0_BARK_TIME);
/* Make sure register write is complete before proceeding */
mb();
return 0;
}
直接将bark time 写入到base+0x10的地址,对应的物理地址就是0xf410000+0x10。
2.3.2 qcom_soc_set_wdt_bite
#define WDT0_BITE_TIME 0x14
static inline int qcom_soc_set_wdt_bite(u32 time,
struct msm_watchdog_data *wdog_dd)
{
__raw_writel((time * WDT_HZ)/1000, wdog_dd->base + WDT0_BITE_TIME);
/* Make sure register write is complete before proceeding */
mb();
return 0;
}
直接将pet time 写入到base+WDT0_BITE_TIME的地址,对应的物理地址就是0xf410000+0x14。
2.3.3 qcom_soc_reset_wdt
#define WDT0_RST 0x04
static inline int qcom_soc_reset_wdt(struct msm_watchdog_data *wdog_dd)
{
__raw_writel(1, wdog_dd->base + WDT0_RST);
/* Make sure register write is complete before proceeding */
mb();
return 0;
}
2.3.4 qcom_soc_enable_wdt
#define WDT0_EN 0x08
static inline int qcom_soc_disable_wdt(struct msm_watchdog_data *wdog_dd)
{
__raw_writel(0, wdog_dd->base + WDT0_EN);
/* Make sure register write is complete before proceeding */
mb();
return 0;
}
2.3.5 qcom_soc_disable_wdt
#define WDT0_EN 0x08
static inline int qcom_soc_disable_wdt(struct msm_watchdog_data *wdog_dd)
{
__raw_writel(0, wdog_dd->base + WDT0_EN);
/* Make sure register write is complete before proceeding */
mb();
return 0;
}
2.3.6 qcom_soc_show_wdt_status
static inline int qcom_soc_show_wdt_status(struct msm_watchdog_data *wdog_dd)
{
dev_err(wdog_dd->dev, "Wdog - STS: 0x%x, CTL: 0x%x, BARK TIME: 0x%x, BITE TIME: 0x%x\n",
__raw_readl(wdog_dd->base + WDT0_STS),
__raw_readl(wdog_dd->base + WDT0_EN),
__raw_readl(wdog_dd->base + WDT0_BARK_TIME),
__raw_readl(wdog_dd->base + WDT0_BITE_TIME));
return 0;
}
2.4 watchdog的初始化入口
// drivers/soc/qcom/qcom_soc_wdt.c
static int qcom_soc_wdt_probe(struct platform_device *pdev)
{
struct resource *res;
struct msm_watchdog_data *wdog_dd;
wdog_dd = devm_kzalloc(&pdev->dev, sizeof(*wdog_dd), GFP_KERNEL); // 申请内存
if (!wdog_dd)
return -ENOMEM;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-base"); // 解析设备树根据wdt-base获取resources
if (!res)
return -ENODEV;
wdog_dd->base = devm_ioremap_resource(&pdev->dev, res); // ioremap映射到虚拟内存空间
if (!wdog_dd->base) {
dev_err(&pdev->dev, "%s cannot map wdog register space\n",
__func__);
return -ENXIO;
}
wdog_dd->ops = &qcom_soc_wdt_ops; // 设置操作api函数集
return qcom_wdt_register(pdev, wdog_dd, "msm-watchdog"); // 注册watchdog
}
2.5 注册watchdog
int qcom_wdt_register(struct platform_device *pdev,
struct msm_watchdog_data *wdog_dd,
char *wdog_dd_name)
{
struct md_region md_entry; // minidump相关
int ret;
if (!pdev || !wdog_dd || !wdog_dd_name) {
pr_err("wdt_register input incorrect\n");
return -EINVAL;
}
qcom_wdt_dt_to_pdata(pdev, wdog_dd); // 解析设备树资源
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(qcom_wdt_kthread, wdog_dd,
wdog_dd_name); // 创建名为 msm-watchdog 的内核进程,进程入口函数 qcom_wdt_kthread , wdog_dd 是 qcom_wdt_kthread 的参数, kthread_create 仅创建进程,并不立即运行
if (IS_ERR(wdog_dd->watchdog_task)) {
ret = PTR_ERR(wdog_dd->watchdog_task);
goto err;
}
ret = qcom_wdt_init(wdog_dd, pdev); // 继续初始化watchdog
if (ret) {
kthread_stop(wdog_dd->watchdog_task);
goto err;
}
/* Add wdog info to minidump table */
strlcpy(md_entry.name, "KWDOGDATA", sizeof(md_entry.name));
md_entry.virt_addr = (uintptr_t)wdog_dd;
md_entry.phys_addr = virt_to_phys(wdog_dd);
md_entry.size = sizeof(*wdog_dd);
if (msm_minidump_add_region(&md_entry) < 0) // 设置minidump的region,region名为md_KWDOGDATA
dev_err(wdog_dd->dev, "Failed to add Wdt data in Minidump\n");
return 0;
err:
return ret;
}
2.5.1 qcom_wdt_dt_to_pdata
该函数用于解析设备树资源
static void qcom_wdt_dt_to_pdata(struct platform_device *pdev,
struct msm_watchdog_data *pdata)
{
pdata->bark_irq = platform_get_irq(pdev, 0);
pdata->irq_ppi = irq_is_percpu(pdata->bark_irq); //irq_ppi是代表的私有外设中断,这里就是判断watchdog的bark irq是否为ppi类型的
pdata->bark_time = QCOM_WATCHDOG_BARK_TIME;
pdata->pet_time = QCOM_WATCHDOG_PET_TIME;
pdata->do_ipi_ping = QCOM_WATCHDOG_IPI_PING;
pdata->wakeup_irq_enable = QCOM_WATCHDOG_WAKEUP_ENABLE;
qcom_wdt_dump_pdata(pdata); //输出watchdog的一些日志到kmsg中,bark time和 pet time设置多少一定会打印
}
2.5.2 qcom_wdt_init
static int qcom_wdt_init(struct msm_watchdog_data *wdog_dd,
struct platform_device *pdev)
{
unsigned long delay_time;
uint32_t val;
int ret;
void *wdog_cpu_dd_v;
// 如果bark irq为ppi
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, "failed to allocate cpu data\n");
return -ENOMEM;
}
wdog_cpu_dd_v = raw_cpu_ptr((void __percpu *)wdog_dd->wdog_cpu_dd);
*((struct msm_watchdog_data **)wdog_cpu_dd_v) = wdog_dd;
ret = request_percpu_irq(wdog_dd->bark_irq, qcom_wdt_ppi_bark,
"apps_wdog_bark",
(void __percpu *)wdog_dd->wdog_cpu_dd);
if (ret) {
dev_err(wdog_dd->dev, "failed to request bark irq\n");
free_percpu((void __percpu *)wdog_dd->wdog_cpu_dd);
return ret;
}
// 博主公司的项目配置的走这里,bark irq是SGI类型
} else {
ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq,
qcom_wdt_bark_handler,
IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
"apps_wdog_bark", wdog_dd); // 注册watchdog irq
if (ret) {
dev_err(wdog_dd->dev, "failed to request bark irq: %d\n", ret);
return -EINVAL;
}
}
wdog_data->hibernate = false;
ret = register_pm_notifier(&qcom_wdt_notif_block); //pm相关
if (ret)
return ret;
delay_time = msecs_to_jiffies(wdog_dd->pet_time); // pet时间
wdog_dd->ops->set_bark_time(wdog_dd->bark_time, wdog_dd); // 设置bark时间
wdog_dd->ops->set_bite_time(wdog_dd->bark_time + 10 * 1000, wdog_dd); // 设置bite时间,默认为bark_time+10s
wdog_dd->panic_blk.priority = INT_MAX - 1;
wdog_dd->panic_blk.notifier_call = qcom_wdt_panic_handler; // 注册panic内核通知链
atomic_notifier_chain_register(&panic_notifier_list,
&wdog_dd->panic_blk);
qcom_wdt_register_die_notifier(wdog_dd); // 注册die内核通知链
wdog_dd->restart_blk.priority = 255;
wdog_dd->restart_blk.notifier_call = restart_wdog_handler;
register_restart_handler(&wdog_dd->restart_blk); // 注册restart通知链
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;
spin_lock_init(&wdog_dd->freeze_lock);
wdog_dd->freeze_in_progress = false;
wake_up_process(wdog_dd->watchdog_task); // 启动内核线程,也就是 qcom_wdt_kthread
timer_setup(&wdog_dd->pet_timer, qcom_wdt_pet_task_wakeup, 0);
wdog_dd->pet_timer.expires = jiffies + delay_time; // 设置喂狗的timer
add_timer(&wdog_dd->pet_timer);
timer_setup(&wdog_dd->user_pet_timer, qcom_wdt_user_pet_bite, 0);
val = BIT(EN);
if (wdog_dd->wakeup_irq_enable)
val |= BIT(UNMASKED_INT_EN);
ret = wdog_dd->ops->enable_wdt(val, wdog_dd); // 使能watchdog
if (ret) {
atomic_notifier_chain_unregister(&panic_notifier_list,
&wdog_dd->panic_blk);
qcom_wdt_unregister_die_notifier(wdog_dd);
unregister_restart_handler(&wdog_dd->restart_blk);
if (wdog_dd->irq_ppi) {
free_percpu_irq(wdog_dd->bark_irq,
(void __percpu *)wdog_dd->wdog_cpu_dd);
free_percpu((void __percpu *)wdog_dd->wdog_cpu_dd);
}
del_timer_sync(&wdog_dd->pet_timer);
dev_err(wdog_dd->dev, "Failed Initializing QCOM Apps Watchdog\n");
return ret;
}
wdog_dd->ops->reset_wdt(wdog_dd); // reset一次watchdog
wdog_dd->last_pet = sched_clock();
wdog_dd->enabled = true;
qcom_wdt_init_sysfs(wdog_dd); // 建立sysfs文件节点
if (wdog_dd->irq_ppi)
enable_percpu_irq(wdog_dd->bark_irq, 0);
if (!IPI_CORES_IN_LPM) {
wdog_dd->wdog_cpu_pm_nb.notifier_call = qcom_wdt_cpu_pm_notify;
cpu_pm_register_notifier(&wdog_dd->wdog_cpu_pm_nb);
}
dev_info(wdog_dd->dev, "QCOM Apps Watchdog Initialized\n");
return 0;
}
2.6 panic/die/restart通知链
2.6.1 qcom_wdt_panic_handler
#ifdef CONFIG_QCOM_WDOG_BITE_EARLY_PANIC
#define WDOG_BITE_EARLY_PANIC 1
#else
#define WDOG_BITE_EARLY_PANIC 0 // 博主公司项目走到这儿
#endif
static void qcom_wdt_reset_on_oops(struct msm_watchdog_data *wdog_dd,
int timeout)
{
wdog_dd->ops->reset_wdt(wdog_dd); // reset wdt
wdog_dd->ops->set_bark_time((timeout + 10) * 1000,
wdog_dd); // 设置bark time为15s
wdog_dd->ops->set_bite_time((timeout + 10) * 1000,
wdog_dd); // 设置bite time为15s
}
static int qcom_wdt_panic_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);
wdog_dd->in_panic = true;
if (WDOG_BITE_EARLY_PANIC) { //博主公司项目进不来
pr_info("Triggering early bite\n");
qcom_wdt_trigger_bite();
}
if (panic_timeout == 0) { // panic_timeout是一个全局变量,受CONFIG_PANIC_TIMEOUT控制,博主公司项目设置为5s
wdog_dd->ops->disable_wdt(wdog_dd);
} else {
qcom_wdt_reset_on_oops(wdog_dd, panic_timeout); // 重置wdt
}
return NOTIFY_DONE;
}
2.6.2 qcom_wdt_die_handler
static int qcom_wdt_die_handler(struct notifier_block *this,
unsigned long val, void *data)
{
struct msm_watchdog_data *wdog_dd = container_of(this,
struct msm_watchdog_data, die_blk);
qcom_wdt_reset_on_oops(wdog_dd, 5); // 和panic通知链一样,重置wdt为15s
return NOTIFY_DONE;
}
2.6.3 restart_wdog_handler
#ifdef CONFIG_QCOM_FORCE_WDOG_BITE_ON_PANIC
#define WDOG_BITE_ON_PANIC 1 //博主公司项目走这里
#else
#define WDOG_BITE_ON_PANIC 0
#endif
static int restart_wdog_handler(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct msm_watchdog_data *wdog_dd = container_of(this,
struct msm_watchdog_data, restart_blk);
if (WDOG_BITE_ON_PANIC && wdog_dd->in_panic) {
/*
* Trigger a watchdog bite here and if this fails,
* device will take the usual restart path.
*/
pr_info("Triggering late bite\n");
qcom_wdt_trigger_bite();
}
return NOTIFY_DONE;
}
2.7 watchdog主线程 qcom_wdt_kthread
static __ref int qcom_wdt_kthread(void *arg)
{
struct msm_watchdog_data *wdog_dd = arg;
unsigned long delay_time = 0;
struct sched_param param = {.sched_priority = MAX_RT_PRIO-1}; //线程优先级为最大优先级-1
int ret, cpu;
sched_setscheduler(current, SCHED_FIFO, ¶m); //设置线程调度策略为实时调度策略,线程优先级为最大优先级-1
while (!kthread_should_stop()) { //检查线程是否应该停止
do {
ret = wait_event_interruptible(wdog_dd->pet_complete,
wdog_dd->timer_expired); //等待看门狗定时器到期
} while (ret != 0);
wdog_dd->thread_start = sched_clock(); //记录线程启动时间并初始化 CPU 状态
for_each_cpu(cpu, cpu_present_mask)
wdog_dd->ping_start[cpu] = wdog_dd->ping_end[cpu] = 0;
if (wdog_dd->do_ipi_ping)
qcom_wdt_ping_other_cpus(wdog_dd); // 向其他 CPU 发送 IPI(处理器间中断),以确保它们处于活动状态。
do {
ret = wait_event_interruptible(wdog_dd->pet_complete,
wdog_dd->user_pet_complete); //等待用户操作完成
} while (ret != 0);
wdog_dd->timer_expired = false;
wdog_dd->user_pet_complete = !wdog_dd->user_pet_enabled;
if (wdog_dd->enabled) {
delay_time = msecs_to_jiffies(wdog_dd->pet_time);
wdog_dd->ops->reset_wdt(wdog_dd); // 重置看门狗定时器
wdog_dd->last_pet = sched_clock(); // 记录最后一次重置看门狗的时间
}
/* Check again before scheduling
* Could have been changed on other cpu
*/
if (!kthread_should_stop()) { // 检查是否需要停止线程并设置定时器
spin_lock(&wdog_dd->freeze_lock);
if (!wdog_dd->freeze_in_progress)
mod_timer(&wdog_dd->pet_timer,
jiffies + delay_time); // 修改定时器的到期时间
spin_unlock(&wdog_dd->freeze_lock);
}
record_irq_count(); //记录次数
}
return 0;
}