1 Linux 时间管理和内核定时器简介
1.1 内核时间管理简介
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
1.2 内核定时器简介
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
void init_timer(struct timer_list *timer)
void add_timer(struct timer_list *timer)
int del_timer(struct timer_list * timer)
int del_timer_sync(struct timer_list *timer)
int mod_timer(struct timer_list *timer, unsigned long expires)
struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
/*
* 定时器处理代码
*/
/* 如果需要定时器周期性运行的话就使用 mod_timer
* 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
}
/* 初始化函数 */
void init(void)
{
init_timer(&timer);/* 初始化定时器*/
timer.function = function;/* 设置定时处理函数*/
timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
add_timer(&timer);/* 启动定时器*/
}
/* 退出函数 */
void exit(void)
{
del_timer(&timer); /* 删除定时器 */
/* 或者使用 */
del_timer_sync(&timer);
}
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)
2 硬件原理图分析
同《pinctrl 和 gpio 子系统实验-基于正点原子IMX6ULL开发板》4.1
3.2 定时器驱动程序编写
新建工程,在 timer.c 里面输入如下内容:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define TIMER_CNT 1
#define TIMER_NAME "timer"
#define CLOSE_CMD (_IO(0xEF, 1)) //关闭命令
#define OPEN_CMD (_IO(0xEF, 2)) //打开命令
#define SETPERIOD_CMD (_IOW(0xEF, 3, int)) //设置周期
/*TIMER 设备结构体*/
struct timer_dev
{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
struct timer_list timer; /* 定义一个定时器 */
int timeperiod; /* 定时周期,单位为 ms */
int led_gpio;
};
struct timer_dev timerdev; /*TIMER */
static int timer_open(struct inode *inode, struct file *filp)
{
filp->private_data = &timerdev;
return 0;
}
static int timer_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long timer_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
int value = 0;
struct timer_dev *dev = filp->private_data;
switch (cmd)
{
case CLOSE_CMD: /* 关闭定时器 */
del_timer_sync(&dev->timer);
break;
case OPEN_CMD: /* 打开定时器 */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
break;
case SETPERIOD_CMD: /* 设置定时器周期 */
ret = copy_from_user(&value, (int *)arg, sizeof(int));
if (ret < 0)
{
return -EFAULT;
}
dev->timeperiod = value;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
break;
default:
break;
}
return ret;
}
/*操作集*/
static const struct file_operations timerdev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = timer_ioctl,
.open = timer_open,
.release = timer_release,
};
/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev *)arg;
static int sta = 1;
sta = !sta;
gpio_set_value(dev->led_gpio, sta);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
}
/*初始化LED灯*/
int led_init(struct timer_dev *dev)
{
int ret = 0;
dev->nd = of_find_node_by_path("/gpioled");
if (dev->nd == NULL)
{
ret = -EINVAL;
goto fail_fd;
}
dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
if (dev->led_gpio < 0)
{
ret = -EINVAL;
goto fail_gpio;
}
/* 初始化 led 所使用的 IO */
ret = gpio_request(dev->led_gpio, "led");
if (ret)
{
ret = -EBUSY;
printk("IO %d can't request!\r\n", dev->led_gpio);
goto fail_request;
}
ret = gpio_direction_output(dev->led_gpio, 1); /*设置输出,默认关灯*/
if (ret < 0)
{
ret = -EINVAL;
goto fail_gpioset;
}
return 0;
fail_gpioset:
gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:
return ret;
}
/*驱动入口函数*/
static int __init timer_init(void)
{
int ret = 0;
/*1,注册字符设备驱动*/
timerdev.major = 0;
if (timerdev.major)
{ /*给定主设备号*/
timerdev.devid = MKDEV(timerdev.major, 0);
ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
}
else
{
ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);
timerdev.major = MAJOR(timerdev.devid);
timerdev.minor = MINOR(timerdev.devid);
}
if (ret < 0)
{
goto fail_devid;
}
printk("timerdev major =%d, minor =%d \r\n", timerdev.major, timerdev.minor);
/*2,初始化cdev*/
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev, &timerdev_fops);
/*3,添加cdev*/
ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
if (ret)
{
goto fail_cdevadd;
}
/*4,创建类*/
timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
if (IS_ERR(timerdev.class))
{
ret = PTR_ERR(timerdev.class);
goto fail_class;
}
/*5,创建设备*/
timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
if (IS_ERR(timerdev.device))
{
ret = PTR_ERR(timerdev.device);
goto fail_device;
}
/*6,初始化LED灯*/
ret = led_init(&timerdev);
if (ret < 0)
{
goto fail_ledinit;
}
/*7,初始化定时器*/
init_timer(&timerdev.timer);
timerdev.timeperiod = 500;
timerdev.timer.function = timer_func;
timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timeperiod);
timerdev.timer.data = (unsigned long)&timerdev;
add_timer(&timerdev.timer); /*添加到系统*/
return 0;
fail_ledinit:
fail_device:
class_destroy(timerdev.class);
fail_class:
cdev_del(&timerdev.cdev);
fail_cdevadd:
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
fail_devid:
return ret;
}
/*驱动出口函数*/
static void __exit timer_exit(void)
{
/*关灯*/
gpio_set_value(timerdev.led_gpio, 1);
/*删除定时器*/
del_timer(&timerdev.timer);
/*注销字符设备驱动*/
cdev_del(&timerdev.cdev);
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
device_destroy(timerdev.class, timerdev.devid);
class_destroy(timerdev.class);
/*释放IO*/
gpio_free(timerdev.led_gpio);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");
测试 APP 我们要实现的内容如下:
#include
#include
#include
#include
#include
#include
#include
#include
/*
*argc:应用程序参数个数
* argv[]:具体的参数内容,字符串形式
* ./timerAPP <0:1> 0 关灯,1 开灯
* ./timerAPP /dev/timer
*/
#define CLOSE_CMD _IO(0xEF, 1) //关闭命令
#define OPEN_CMD _IO(0xEF, 2) //打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) //设置周期
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char cnt;
unsigned int cmd;
unsigned int arg;
unsigned char str[100];
if (argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("file %s open failed!\r\n", filename);
return -1;
}
/*循环读取*/
while (1)
{
printf("Input CMD:");
ret = scanf("%d", &cmd);
if (ret != 1)
{
gets(str); /*防止卡死*/
}
if (cmd == 1) /*关闭*/
{
ioctl(fd,CLOSE_CMD,&arg);
}
else if (cmd == 2)/*打开*/
{
ioctl(fd,OPEN_CMD,&arg);
}
else if (cmd == 3)/*设置周期*/
{
printf("Input Timer period:");
ret = scanf("%d",&arg);
if(ret !=1){
gets(str); /*防止卡死*/
}
ioctl(fd,SETPERIOD_CMD,&arg);
}
}
close(fd);
return 0;
}
详细内容参考正点原子《I.MX6U 嵌入式 Linux 驱动开发指南 V1.6》第50章。
补充:ioctl 用户与驱动之间的协议
ioctl 方法第二个参数 cmd 为用户与驱动的 “协议”,理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:
为了方便我们会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令,函数原型如下所示:
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
_IO: 定义不带参数的 ioctl 命令
_IOW: 定义带写参数的 ioctl 命令(copy_from_user)
_IOR: 定义带读参数的ioctl命令(copy_to_user)
_IOWR: 定义带读写参数的 ioctl 命令
参考内容:
linux驱动开发学习笔记十九:认识一下ioctl函数_耐心的小黑的博客-CSDN博客