Android系统高通平台Kernel Watchdog

xiaoxiao2021-02-28  119

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 Treewatchdog的定义

       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_expiredture,唤醒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.lu\n", (unsigned long) t,               nanosec_rem / 1000); // 打印狗叫的时间        nanosec_rem = do_div(wdog_dd->last_pet, 1000000000);        printk(KERN_INFO "Watchdog last pet at %lu.lu\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工作示意图

转载请注明原文地址: https://www.6miu.com/read-30540.html

最新回复(0)