前提 开发环境
Chip : TsingMicro dt56
OS : ubuntu20.04
Compiler : gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf
Test Mechine : HAPS (FPGA)
概要 WTD驱动程序开发过程分为四个部分
- WTD概念,原理以及配置方式的学习
- 硬件操作的代码编写
- 驱动框架的理解
- 测试
一 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域配置,初始化超时值,在系统复位之前或者之后被写入
一套简单的配置
- 配置WDT_CR 失能WDT
- 配置WDT_TORR 设置WDT计数初始值
- 配置WDT_CR 使能WDT
一套复杂的配置
使用用户定义的计数器配置WDT:
- 通过写入看门狗控制寄存器WDT_CR禁用WDT。
- 配置WDT_TORR / WDT_TORR_USR / WDT_TORR_USR_INIT。
- 配置WDT_PAUSE在默认值和用户定义的超时值之间切换
价值。 - 通过写入WDT_CR来启用WDT。
- 通过写入WDT_PAUSE暂停WDT。
- 通过写入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;
}
今天的文章嵌入式linux驱动开发教程_嵌入式开发是什么分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/88942.html