linux-wdt 原理和任意超時時間設定的解決方法

迷霧綠洲發表於2019-02-13

Watchdog Timer的縮寫字母,也就是看門狗,是一個定時器電路。這個電路的功能是維護系統的正常執行,如果遇到系統卡死的情況可以自動的從硬體上覆位系統。簡單來說是用軟體發現問題,用硬體操作解決問題。
wdt 描述
可以看到整個模組是內部一個timer計數器,外部輸入一個timer 的復位訊號,輸出一箇中斷訊號和一個復位訊號。
流程是:
1.設定復位超時時間
2.內部計數器進行減數計時,減到0 輸出中斷,資料又更新到復位超時時間,然後再減數執行,再次減到0 輸出硬體的復位訊號,系統復位。也有不產生中斷直接第一次減數到0就輸出復位訊號,直接系統復位的。
3.軟體在復位時間內復位timer ,保證timer 內部的數不到0.
內部計數暫存器
通過調整輸入時鐘可以改變reset 的時間,達到自動控制系統當機重啟的功能。
可以看到這個暫存器的設定檔位只有7:4,也就只能設定16個不同的時間週期,並且這個週期是以2的n次方的方式,並不是通常理解的線性時間設定,16個檔位肯定不能滿足需求,目標必須是任意時間都可以設定,硬體如此就只能通過軟體解決了。
解決辦法:
1.將硬體wdt 重啟時間設定成為0.5秒,這樣一般也就滿足了日常應用的最小時間設定。
在open 函式就啟動wdt,
···
static int fh_wdt_open(struct inode *inode, struct file *filp)
{
if (test_and_set_bit(0, &fh_wdt.in_use))
return -EBUSY;

/* Make sure we don't get unloaded. */
__module_get(THIS_MODULE);

spin_lock(&fh_wdt.lock);

if(fh_wdt.plat_data && fh_wdt.plat_data->resume)
	fh_wdt.plat_data->resume();

fh_wdt_set_top(WDT_HW_TIMEOUT);///3000);
if (!fh_wdt_is_enabled())
{
    /*
     * The watchdog is not currently enabled. Set the timeout to
     * the maximum and then start it.
     */
    u32 value;
    value = WDOG_CONTROL_REG_WDT_EN_MASK;

	writel(value, fh_wdt.regs + WDOG_CONTROL_REG_OFFSET);//設定硬體最小的超時時間
	fh_wdt_keepalive();
}

fh_wdt_set_next_heartbeat();

spin_unlock(&fh_wdt.lock);

return nonseekable_open(inode, filp);

}
···
2.驅動中起一個timer 定時器,定時時間小於硬體wdt重啟時間,進行軟體喂狗。放置硬體定時器重啟。

	setup_timer(&fh_wdt.timer, fh_wdt_ping, 0);
	mod_timer(&fh_wdt.timer, jiffies + WDT_TIMEOUT);
static void fh_wdt_ping(unsigned long data)
{
	if (time_before(jiffies, fh_wdt.next_heartbeat) ||
	    (!nowayout && !fh_wdt.in_use)) {
		fh_wdt_keepalive();
		mod_timer(&fh_wdt.timer, jiffies + WDT_TIMEOUT);
	} else
		pr_crit("keepalive missed, machine will reset\n");
}

3.驅動解析應用設定的重啟時間,接收應用喂狗的資訊。

應用呼叫ioctl 配置一個全域性變數用來記錄軟體任意時間的超時時間heartbeat。

	case WDIOC_SETTIMEOUT:
		if (get_user(val, (int __user *)arg))
			return -EFAULT;

		pr_debug("[wdt] settime value %lu", val);
		heartbeat =  val;
		fh_wdt_keepalive();
		fh_wdt_set_next_heartbeat();

		return put_user(val , (int __user *)arg);

驅動把超時時間記錄在fh_wdt.next_heartbeat中

static inline void fh_wdt_set_next_heartbeat(void)
{
	fh_wdt.next_heartbeat = jiffies + heartbeat * HZ;
}

也可以通過write 來寫

ssize_t fh_wdt_write(struct file *filp, const char __user *buf, size_t len,
		     loff_t *offset)
{
	if (!len)
		return 0;

	if (!nowayout) {
		size_t i;

		fh_wdt.expect_close = 0;

		for (i = 0; i < len; ++i) {
			char c;

			if (get_user(c, buf + i))
				return -EFAULT;

			if (c == 'V') {
				fh_wdt.expect_close = 1;
				break;
			}
		}
	}

	fh_wdt_set_next_heartbeat();
	mod_timer(&fh_wdt.timer, jiffies + WDT_TIMEOUT);//啟動下一次的超時時間

	return len;
}

4.如若應用在自己設定的超時時間之內喂狗,則一直維護這個timer。如果應用在自己設定的超時時間內沒有喂狗,這個timer 呼叫的硬體喂狗停止,放任硬體wdt 重啟系統。
每0.5s timer迴圈進行呼叫fh_wdt_ping 進行時間比對,當前時間jffies 是否超過了應用預設時間fh_wdt_next_hearbeat,沒超過硬體喂狗,超過不喂狗。

static void fh_wdt_ping(unsigned long data)
{
	if (time_before(jiffies, fh_wdt.next_heartbeat) ||
	    (!nowayout && !fh_wdt.in_use)) {
		fh_wdt_keepalive();
		mod_timer(&fh_wdt.timer, jiffies + WDT_TIMEOUT);繼續喂狗,設定0.5s重啟
	} else
		pr_crit("keepalive missed, machine will reset\n");停止喂狗
}

總結

用軟體方法解決硬體的侷限

相關文章