前提 开发环境

Chip : TS
OS : ubuntu20.04
Compiler : gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf
Test Mechine : HAPS (FPGA)

概要 WTD驱动程序开发过程分为四个部分

  1. WTD概念,原理以及配置方式的学习
  2. 硬件操作的代码编写
  3. 驱动框架的理解
  4. 测试

一 WTD的概念

WTD全称WatchDog,用于对计算机系统的自动复位。计算机在工作时容易受到各种干扰,导致计算机程序进入死循环,跑飞,或者死机崩溃。看门狗这种硬件用于解救这种情况下的计算机。

看门狗的工作逻辑如下:
初始化时配置初值,频率,以及减到0时是否要产生中断等
开始计数后可以选择在计数减为0之前重新设置初值,也就是所谓的Tick Dog,俗称喂狗,系统正常工作
如果系统崩溃则无法置初值,WTD在计数减为0时会触发系统复位,从而保证系统工作
在这里插入图片描述

二 WTD 首要寄存器

WDT_CR寄存器:

Offset address: 0x00, Control Register.
对RPL域配置,设置pclk cycles 这个值决定了时钟频率,也就是计数快慢
对RMOD配置,设置响应模式,决定计数到0值时产生系统复位还是产生中断
对WDT_EN域配置,控制WDT的使能或失能
在这里插入图片描述

WDT_TORR寄存器

Offset address = 0x04, Timeout Range Register.
在这里插入图片描述
对TOP_INIT域配置,初始化超时值,在系统复位之前或者之后被写入

一套简单的配置

  1. 配置WDT_CR 失能WDT
  2. 配置WDT_TORR 设置WDT计数初始值
  3. 配置WDT_CR 使能WDT

一套复杂的配置

使用用户定义的计数器配置WDT:

  1. 通过写入看门狗控制寄存器WDT_CR禁用WDT。
  2. 配置WDT_TORR / WDT_TORR_USR / WDT_TORR_USR_INIT。
  3. 配置WDT_PAUSE在默认值和用户定义的超时值之间切换
    价值。
  4. 通过写入WDT_CR来启用WDT。
  5. 通过写入WDT_PAUSE暂停WDT。
  6. 通过写入WDT_PAUSE释放暂停,WDT继续工作。

三 硬件操作部分的代码

需实现的驱动接口列表

包含启动,停止,对WTD设值,喂狗,获取计数剩余值,重启。

static const struct watchdog_ops dw_wdt_ops = {
	.owner		= THIS_MODULE,
	.start		= dw_wdt_start,
	.stop		= dw_wdt_stop,
	.ping		= dw_wdt_ping,
	.set_timeout	= dw_wdt_set_timeout,
	.get_timeleft	= dw_wdt_get_timeleft,
	.restart	= dw_wdt_restart,
};

硬件相关的宏定义 以及 WDT参数的定义

这部分代码包含了以下信息:
寄存器以及其偏移值的映射
时钟的PLCK一个脉冲的长度
响应模式
最大可设超时时间
默认超时时间
是否外界可以关闭WTD 作为模块参数可由用户加载模块式配置
wdt结构体 其继承了watchdog_device

//寄存器以及其偏移值的映射
#define WDOG_CONTROL_REG_OFFSET				0x00
#define WDOG_CONTROL_REG_WDT_EN_MASK		0x01
#define WDOG_CONTROL_REG_RESP_MODE_MASK		0x02
#define WDOG_CONTROL_REG_RESP_PULSE_LENGTH_MASK		0x07
#define WDOG_CONTROL_REG_RESP_PULSE_LENGTH_POS		(2)
#define WDOG_CONTROL_REG_RESET_MODE_MASK		0x1
#define WDOG_CONTROL_REG_RESET_MODE_POS			(1)


#define WDOG_TIMEOUT_RANGE_REG_OFFSET		0x04
#define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT	4

#define WDOG_CURRENT_COUNT_REG_OFFSET		0x08
#define WDOG_COUNTER_RESTART_REG_OFFSET		0x0c
#define WDOG_COUNTER_RESTART_KICK_VALUE		0x76

#define WDOG_CONTROL_REG_CLEAR_INT			0x14

//时钟的PLCK一个脉冲的长度
typedef enum {
	WDT_ResetPulseLength_2_PCLK_CYCLES = 0,
	WDT_ResetPulseLength_4_PCLK_CYCLES,
	WDT_ResetPulseLength_8_PCLK_CYCLES,
	WDT_ResetPulseLength_16_PCLK_CYCLES,
	WDT_ResetPulseLength_32_PCLK_CYCLES,
	WDT_ResetPulseLength_64_PCLK_CYCLES,
	WDT_ResetPulseLength_128_PCLK_CYCLES,
	WDT_ResetPulseLength_256_PCLK_CYCLES,
} eWDT_ResetPulseLength_t;

//响应模式
typedef enum {
	WDT_SYSTEM_RESET = 0,
	WDT_INTERRUPT,
} eWDT_ResponseMode_t;

//最大可设超时时间
/* The maximum TOP (timeout period) value that can be set in the watchdog. */
#define DW_WDT_MAX_TOP		15
//默认超时时间
#define DW_WDT_DEFAULT_SECONDS	5
#define DW_WDT_DEFAULT_RESET_PULSE_LENGTH	WDT_ResetPulseLength_64_PCLK_CYCLES
#define DW_WDT_DEFAULT_RESET_MODE	WDT_SYSTEM_RESET

//是否外界可以关闭WTD  作为模块参数可由用户加载模块式配置
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
		 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

//wdt结构体 继承了watchdog_device
struct dw_wdt {
	void __iomem		*regs;
	struct clk		*clk;
	unsigned long		rate;
	struct watchdog_device	wdd;
	struct reset_control	*rst;
};

#define to_dw_wdt(wdd)	container_of(wdd, struct dw_wdt, wdd)

实现硬件操作的接口

按照调用关系排列
最基础的函数是dw_wdt_set_timeout

dw_wdt_set_timeout

该函数采取迭代的方法,先将用户所传入的top_s(秒)值转化为

static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
{
	struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
	int i, top_val = DW_WDT_MAX_TOP;

	/*
	 * Iterate over the timeout values until we find the closest match. We
	 * always look for >=.
	 */
	for (i = 0; i <= DW_WDT_MAX_TOP; ++i)
		if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) {
			top_val = i;
			break;
		}

	/*
	 * Set the new value in the watchdog.  Some versions of dw_wdt
	 * have have TOPINIT in the TIMEOUT_RANGE register (as per
	 * CP_WDT_DUAL_TOP in WDT_COMP_PARAMS_1).  On those we
	 * effectively get a pat of the watchdog right here.
	 */
	writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT,
		   dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);

	wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val);

	return 0;
}

dw_wdt_ping

ping函数主要用于喂狗,往上面所说的寄存器里写值即可

static int dw_wdt_ping(struct watchdog_device *wdd)
{
	struct dw_wdt *dw_wdt = to_dw_wdt(wdd);

	writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt->regs +
		   WDOG_COUNTER_RESTART_REG_OFFSET);

	return 0;
}

dw_wdt_arm_system_reset

系统重启函数:配置了失能中断模式,计数到0时系统总是会重启
配置WDT_CR寄存器的EN域,使能watchdog

static void dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt)
{

	u32 val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);

	/* Disable interrupt mode; always perform system reset. */
	val &= ~(WDOG_CONTROL_REG_RESET_MODE_MASK << WDOG_CONTROL_REG_RESET_MODE_POS);
	val |= (DW_WDT_DEFAULT_RESET_MODE << WDOG_CONTROL_REG_RESET_MODE_POS);

	/* Enable watchdog. */
	val |= WDOG_CONTROL_REG_WDT_EN_MASK;
	writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
}

dw_wdt_start

wdt的开启函数,调用上面俩函数,设置超时值后将,配置响应模式,系统重启

static int dw_wdt_start(struct watchdog_device *wdd)
{
	struct dw_wdt *dw_wdt = to_dw_wdt(wdd);

	dw_wdt_set_timeout(wdd, wdd->timeout);
	dw_wdt_arm_system_reset(dw_wdt);

	return 0;
}

dw_wdt_stop

配置wdt的停止函数

static int dw_wdt_stop(struct watchdog_device *wdd)
{
	struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
	if (!dw_wdt->rst) {
		set_bit(WDOG_HW_RUNNING, &wdd->status);
		return 0;
	}
	reset_control_assert(dw_wdt->rst);
	reset_control_deassert(dw_wdt->rst);
	return 0;
}

四 驱动框架的理解

内核单独给WatchDog准备了一套框架,与Platform平台设备类似,WatchDog框架包含以下三部分:
驱动层watchdog_drv — 核心层watchdog_core — 设备层watchdog_ dev

前面章节遇到的框架中一般都是要自己手动创建节点或者通过程序运行时自动创建节点
对于watchdog比较特别,节点创建并登记的函数(watchdog_cdev_register)已经定义在watchdog_dev.c中,并在watchdog_dev_register函数中调用,
而watchdog_dev_register又是被__watchdog_register_device调用的,
而__watchdog_register_devicer又是被watchdog_register_device调用。
所以这个函数watchdog_register_device才是留给我们调用注册节点的接口,需要在probe函数中调用

节点注册的调用关系图

probe函数-->
	watchdog_register_device-->
		__watchdog_register_device-->
			watchdog_dev_register-->
				watchdog_cdev_register-->
					watchdog_miscdev.parent = wdd->parent;
					err = misc_register(&watchdog_miscdev);
					-----------------------
					cdev_init(&wd_data->cdev, &watchdog_fops);
					err = cdev_device_add(&wd_data->cdev, &wd_data->dev);

可见这里层级调用中watchdog_cdev_register注册了一个混杂设备watchdog_miscdev

static struct miscdevice watchdog_miscdev = {
	.minor		= WATCHDOG_MINOR,
	.name		= "watchdog",
	.fops		= &watchdog_fops,
};

其fops如下:
可以看到这里就是应用层调用文件操作函数时最后会调用到的相应的函数接口

static const struct file_operations watchdog_fops = {
	.owner		= THIS_MODULE,
	.write		= watchdog_write,
	.unlocked_ioctl	= watchdog_ioctl,
	.open		= watchdog_open,
	.release	= watchdog_release,
};

实现fops里的接口

write实现

拿write接口的实现为例:
最主要的功能就是调用了硬件操作部分已经实现的 watchdog_ping函数,达到喂狗的目的。

static ssize_t watchdog_write(struct file *file, const char __user *data,
						size_t len, loff_t *ppos)
{
	struct watchdog_core_data *wd_data = file->private_data;
	struct watchdog_device *wdd;
	int err;
	size_t i;
	char c;

	if (len == 0)
		return 0;

	/*
	 * Note: just in case someone wrote the magic character
	 * five months ago...
	 */
	clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);

	/* scan to see whether or not we got the magic character */
	for (i = 0; i != len; i++) {
		if (get_user(c, data + i))
			return -EFAULT;
		if (c == 'V')
			set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);
	}

	/* someone wrote to us, so we send the watchdog a keepalive ping */

	err = -ENODEV;
	mutex_lock(&wd_data->lock);
	wdd = wd_data->wdd;
	if (wdd)
		err = watchdog_ping(wdd);
	mutex_unlock(&wd_data->lock);

	if (err < 0)
		return err;

	return len;
}
ioctl的实现

可以将ioctl看作一个控制器,(约定好底层驱动对应哪个命令)用户调用这个函数,并指定使用哪个命令,就可以调用到哪个函数

/*
 *    watchdog_ioctl: handle the different ioctl's for the watchdog device.
 *    @file: file handle to the device
 *    @cmd: watchdog command
 *    @arg: argument pointer
 *
 *    The watchdog API defines a common set of functions for all watchdogs
 *    according to their available features.
 */
 
static long watchdog_ioctl(struct file *file, unsigned int cmd,
                            unsigned long arg)
{
    struct watchdog_core_data *wd_data = file->private_data;
    void __user *argp = (void __user *)arg;
    struct watchdog_device *wdd;
    int __user *p = argp;
    unsigned int val;
    int err;
 
    mutex_lock(&wd_data->lock);
 
    wdd = wd_data->wdd;
    if (!wdd) {
        err = -ENODEV;
        goto out_ioctl;
    }
 
    err = watchdog_ioctl_op(wdd, cmd, arg);
    if (err != -ENOIOCTLCMD)
        goto out_ioctl;
 
    switch (cmd) {
    case WDIOC_GETSUPPORT:
        err = copy_to_user(argp, wdd->info,
            sizeof(struct watchdog_info)) ? -EFAULT : 0;
        break;
    case WDIOC_GETSTATUS:
        val = watchdog_get_status(wdd);
        err = put_user(val, p);
        break;
    case WDIOC_GETBOOTSTATUS:
        err = put_user(wdd->bootstatus, p);
        break;
    case WDIOC_SETOPTIONS:
        if (get_user(val, p)) {
            err = -EFAULT;
            break;
        }
        if (val & WDIOS_DISABLECARD) {
            err = watchdog_stop(wdd);
            if (err < 0)
                break;
        }
        if (val & WDIOS_ENABLECARD)
            err = watchdog_start(wdd);
        break;
    case WDIOC_KEEPALIVE:
        if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) {
            err = -EOPNOTSUPP;
            break;
        }
        err = watchdog_ping(wdd);
        break;
    case WDIOC_SETTIMEOUT:
        if (get_user(val, p)) {
            err = -EFAULT;
            break;
        }
        err = watchdog_set_timeout(wdd, val);
        if (err < 0)
            break;
        /* If the watchdog is active then we send a keepalive ping
         * to make sure that the watchdog keep's running (and if
         * possible that it takes the new timeout) */
        err = watchdog_ping(wdd);
        if (err < 0)
            break;
        /* Fall */
    case WDIOC_GETTIMEOUT:
        /* timeout == 0 means that we don't know the timeout */
        if (wdd->timeout == 0) {
            err = -EOPNOTSUPP;
            break;
        }
        err = put_user(wdd->timeout, p);
        break;
    case WDIOC_GETTIMELEFT:
        err = watchdog_get_timeleft(wdd, &val);
        if (err < 0)
            break;
        err = put_user(val, p);
        break;
    case WDIOC_SETPRETIMEOUT:
        if (get_user(val, p)) {
            err = -EFAULT;
            break;
        }
        err = watchdog_set_pretimeout(wdd, val);
        break;
    case WDIOC_GETPRETIMEOUT:
        err = put_user(wdd->pretimeout, p);
        break;
    default:
        err = -ENOTTY;
        break;
    }
 
out_ioctl:
    mutex_unlock(&wd_data->lock);
    return err;
}

匹配

如前面章节剖析的一样,probe函数如何才能被调用?
答:设备树上注册的.compatible和driver里的.of_match_table能够被匹配上

static const struct of_device_id dw_wdt_of_match[] = {
    { .compatible = "snps,dw-wdt", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_wdt_of_match);
 
static struct platform_driver dw_wdt_driver = {
    .probe        = dw_wdt_drv_probe,
    .remove        = dw_wdt_drv_remove,
    .driver        = {
        .name    = "dw_wdt",
        .of_match_table = of_match_ptr(dw_wdt_of_match),
        .pm    = &dw_wdt_pm_ops,
    },
};

五 编写测试App

应用层代码需要一一检验是否驱动功能已经全部实现:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <sys/signal.h>
 
//watchdog 
#define WATCHDOG_IOCTL_BASE     'W'
 
struct watchdog_info {
    unsigned int options;          /* Options the card/driver supports */
    unsigned int firmware_version; /* Firmware version of the card */
    char identity[32];     /* Identity of the board */
};
 
#define WDIOC_GETSUPPORT        _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS         _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS     _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP           _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS        _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE         _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT        _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT        _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT     _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT     _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT       _IOR(WATCHDOG_IOCTL_BASE, 10, int)
 
#define WDIOF_OVERHEAT          0x0001  /* Reset due to CPU overheat */
#define WDIOF_FANFAULT          0x0002  /* Fan failed */
#define WDIOF_EXTERN1           0x0004  /* External relay 1 */
#define WDIOF_EXTERN2           0x0008  /* External relay 2 */
#define WDIOF_POWERUNDER        0x0010  /* Power bad/power fault */
#define WDIOF_CARDRESET         0x0020  /* Card previously reset the CPU */
#define WDIOF_POWEROVER         0x0040  /* Power over voltage */
#define WDIOF_SETTIMEOUT        0x0080  /* Set timeout (in seconds) */
#define WDIOF_MAGICCLOSE        0x0100  /* Supports magic close char */
#define WDIOF_PRETIMEOUT        0x0200  /* Pretimeout (in seconds), get/set */
#define WDIOF_KEEPALIVEPING     0x8000  /* Keep alive ping reply */
 
#define WDIOS_DISABLECARD       0x0001  /* Turn off the watchdog timer */
#define WDIOS_ENABLECARD        0x0002  /* Turn on the watchdog timer */
#define WDIOS_TEMPPANIC         0x0004  /* Kernel panic on temperature trip */
 
int wdt_fd;
int time_out = 5;
#define DEFAULT_PING_RATE    1
void stop_signal()
{
    int val = 0 , ret = 0 ;
 
    val = WDIOS_DISABLECARD ;
    ret = ioctl(wdt_fd, WDIOC_SETOPTIONS, &val) ;
    if (ret < 0)
        printf("ioctl WDIOC_GETSUPPORT failed with %d.\n", ret);
 
    printf("===watchdow will be closed===\n") ;
    close(wdt_fd) ;
    exit(0);
    
}
 
int main(int argc, char *argv[])
{
    int ret;
    static int count = 0;
    struct watchdog_info wdt_info;
    unsigned int ping_rate = DEFAULT_PING_RATE;
 
    signal(SIGINT, stop_signal) ;
 
    wdt_fd = open("/dev/watchdog0", O_RDWR);
    if(wdt_fd < 0)
    {
        printf("open /dev/watchdog0 failed.\n");
    }
 
    /* get watchdog infomation struct */
    ret = ioctl(wdt_fd, WDIOC_GETSUPPORT, &wdt_info);
    if (ret < 0)
        printf("ioctl WDIOC_GETSUPPORT failed.\n");
    else
    {
        printf("options = 0x%x,id = %s\n", wdt_info.options, wdt_info.identity);
    }
 
    ioctl(wdt_fd, WDIOC_SETTIMEOUT, &time_out);
    if (ret < 0)
        printf("ioctl WDIOC_SETTIMEOUT failed.\n");
    
    while(1)
    {
        
        if(count > 10)
        {
            printf("unfood watchdog, count = %d \n",count++);
        }
        else
        {
            ioctl(wdt_fd,WDIOC_KEEPALIVE,NULL);
            printf("food watchdog, count = %d \n",count++);
        }
        sleep(DEFAULT_PING_RATE);
    }    
 
    close(wdt_fd);
    return 0;
}
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐