Android系统高通平台Kernel Watchdog


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; //指向watchdogdevice
       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_ppitrue时才会用到
       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_kthreadwdog_ddwatchdog_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; //手机panicwatchdog的回调函数
       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的工作流程

  1. 每次喂狗定时器超时后,执行定时器函数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);
  }
  1. 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, &param); //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_completefalse,永不等待
                     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;
  }
  1. 喂狗
  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;
  }
  1. 没有按时喂狗,触发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;
  }
  1. 狗叫时,系统已经异常,因此无法走正常关机流程,因此需要写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一些寄存器信息
  }
  1. 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://blog.csdn.net/xichangbao/article/details/76727425
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭