天天看點

高通看門狗驅動-MSM Watchdog

源碼中的文檔:

高通看門狗驅動-MSM Watchdog

不論是軟狗還是硬狗,大體思路都差不多:

準備一個定時器,定時10s,逾時之後啟動地球毀滅程式~~~。為了防止地球毀滅,需要時不時的将計時器的計數值清零,稱為喂狗。一般看門狗定時器是無論系統跑飛還是卡死都能正常運作的,一般逾時後直接拉CPU的reset引腳,讓系統複位。

高通看門狗驅動-MSM Watchdog

先看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的核心線程:

高通看門狗驅動-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, &param);

	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

高通看門狗驅動-MSM Watchdog

3.是以沒法喂狗會發生在以下情況:

喂狗線程所在cpu長時間關搶占,導緻即使pet_timer喚醒喂狗線程但得不到執行。

喂狗線程所在的cpu頻繁被中斷打斷,導緻沒法按時喂狗。

其他cpu長時間處于關中斷導緻無法響應ipi中斷。

所有cpu長時間處于關中斷導緻直接觸發狗咬(pet_timer無法執行,狗叫中斷無法響應)。

值得一提的是,關中斷其實就是關搶占,顯式調用關中斷之後關搶占隻是為了增加搶占點罷了。

喂狗線程所在cpu的調用棧(在程序上下文ping其他cpu并喂狗):

高通看門狗驅動-MSM Watchdog

被喂狗程序ping的cpu的調用棧(在中斷上下文響應ping):

高通看門狗驅動-MSM Watchdog

繼續閱讀