IMX6ULL驱动学习 -- 内核定时器

内核定时器

  • 时钟源
  • 基础定时器
    • 绕回处理函数
    • 时间转换函数
  • 用户定时器
    • 用户定时器使用
    • 内核短延时函数
  • API
  • 代码
    • 定时器模块代码
    • 定时器测试应用代码
    • Makefile
    • make.sh

时钟源

内核定时器时钟源有硬件定时器提供,定时器频率可以设置,设置好之后,周期性产生定时器中断,系统利用定时器中断计时。中断频率即为系统频率,也叫作节拍率。单位Hz。

  • 节拍率
    常见的节拍率设置有:1000Hz,500Hz,300Hz,250Hz,200Hz,100Hz。频率越高,内核计时任务占用资源越高,但是计时精度越高。一般设置为100Hz。在内核中用宏定义HZ表示。
  • 设置
    配置:
$ make menuconfig
->Kernel Features
	->Timer frequency ( [=y] )

基础定时器

定时器节拍率设置之后,内核周期性进入中断处理,累计节拍数。内核中使用全局变量记录从系统启动以来的系统节拍数。

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

其中jiffies_64用在64位系统,jiffies用于32位系统。在内核启动时初始化为0,每此进入硬件定时中断累加1,因此64位系统在节拍率为1000时,5.8亿年计数溢出,可忽略不计。但是32位系统只需要49.7天机会溢出,因此需要考虑绕回处理。

绕回处理函数

函数 描述
time_after(unkown, known) unkown 通常为 jiffies, known 通常是需要对比的值。
time_before(unkown, known) unkown 通常为 jiffies, known 通常是需要对比的值。
time_after_eq(unkown, known) unkown 通常为 jiffies, known 通常是需要对比的值。
time_before_eq(unkown, known) unkown 通常为 jiffies, known 通常是需要对比的值。

如果unknown超过known,time_after返回真,否则返回假。time_before相反。

  • demo
    实现定时2s。
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */
/*************************************
具体的代码
************************************/
/* 判断有没有超时 */
if(time_before(jiffies, timeout)) 
{
	/* 超时未发生 */
} 
else 
{
	/* 超时发生 */
}

时间转换函数

函数 描述
int jiffies_to_msecs(const unsigned long j) 将 jiffies 类型的参数 j 分别转换为对应的毫秒
int jiffies_to_usecs(const unsigned long j) 将 jiffies 类型的参数 j 分别转换为对应的微秒
u64 jiffies_to_nsecs(const unsigned long j) 将 jiffies 类型的参数 j 分别转换为对应的纳秒
long msecs_to_jiffies(const unsigned int m) 将毫秒转换成jiffies类型
long usecs_to_jiffies(const unsigned int u) 将微秒转换成jiffies类型
unsigned long nsecs_to_jiffies(u64 n) 将纳秒转换成jiffies类型

用户定时器

Linux定时器使用很简单,只需要提供超时时间和定时器处理函数即可。但是内核定时器并不是周期性运行,超时后会自动关闭,因此如果需要周期性定时,需要在定时器处理函数中重新启动定时器。

  • 定时器结构体
struct timer_list {
	struct list_head entry;
	unsigned long expires; /* 定时器超时时间,单位是节拍数 */
	struct tvec_base *base;
	void (*function)(unsigned long); /* 定时处理函数 */
	unsigned long data; /* 要传递给 function 函数的参数 */
	int slack;
};

用户定时器使用

  • 定义定时器
 struct timer_list timer;        //定时器
  • 初始化定时器
void init_timer(struct timer_list *timer)

....demo....
init_timer(&timerdev.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)

内核短延时函数

函数 描述
void ndelay(unsigned long nsecs) 纳秒延时
void udelay(unsigned long usecs) 微秒延时
void mdelay(unsigned long mseces) 毫秒延时

API

代码

定时器模块代码

  • timer.c
#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,0x1))     //关闭定时器
#define OPEN_CMD            (_IO(0xEF,0x2))     //打开定时器
#define SETPERIOD_CMD       (_IO(0xEF,0x3))     //设置定时器周期命令

#define LEDOFF      0   //关灯
#define LEDON       1   //开灯


//gpioled设备结构体
struct  timer_dev
{
    /* data */
    dev_t devid;                //设备号
    struct cdev cdev;           //cdev
    struct class *class;        //类
    struct device *device;      //设备
    int major;                  //主设备号
    int minor;                  //次设备号
    struct  device_node *nd;    //设备节点
    int led_gpio;               //使用的GPIO编号
    int timeperiod;             //定时器周期,单位ms
    struct timer_list timer;    //定义定时器
    spinlock_t lock;            //定义自旋锁
};

struct timer_dev timerdev;//timer设备
/*
初始化led IO,open函数打开驱动时初始化GPIO
*/
static int led_init(void)
{
    
    timerdev.nd = of_find_node_by_path("/light");//获取设备节点
    if(timerdev.nd == NULL)
    {
        return -EINVAL;
    }
    timerdev.led_gpio = of_get_named_gpio(timerdev.nd,"light-gpio",0);//获取GPIO编号
    if(timerdev.led_gpio < 0)
    {
        printk("can't find light-gpio!\r\n");
        
    }
    return 0;
}

/*
@description      :   打开设备
@param - inode    :   传递给驱动的inode   
@param - filp     :   设备文件 
@return           :   0 成功; 其他 失败
*/
static int timer_open(struct inode *inode, struct file *file)
{
    int ret = 0;
    file->private_data = &timerdev;        //设置私有数据
    
    timerdev.timeperiod = 1000;//默认定时器为1s
    ret = led_init();
    if(ret < 0)
    {
        return ret;
    }
    return 0;
}
/*
ioctl函数

*/
static long timer_unlocked_ioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{
    struct timer_dev *dev = (struct timer_dev *)filp->private_data;
    int timerperiod;
    unsigned long flags;

    switch (cmd)
    {
    case CLOSE_CMD:
        del_timer_sync(&dev->timer);
        break;
    case OPEN_CMD:
        spin_lock_irqsave(&dev->lock,flags);
        timerperiod = dev->timeperiod;
        spin_unlock_irqrestore(&dev->lock,flags);
        mod_timer(&dev->timer,jiffies + msecs_to_jiffies(timerperiod));
        break;
    case SETPERIOD_CMD:
        spin_lock_irqsave(&dev->lock,flags);
        dev->timeperiod = arg;

        spin_unlock_irqrestore(&dev->lock,flags);
        mod_timer(&dev->timer,jiffies + msecs_to_jiffies(arg));
        break;
    default:
        break;
    }
    return 0;
}

/*   设备操作函数集合   */
static struct file_operations timer_fops = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .unlocked_ioctl = timer_unlocked_ioctl,
};

void timer_function(unsigned long arg)
{
    struct timer_dev *dev = (struct timer_dev*)arg;
    static int sta = 1;
    int timerperiod;
    unsigned long flags;
    sta = !sta;//每次状态取反,翻转LED

    gpio_set_value(dev->led_gpio,sta);

    //重启定时器
    spin_lock_irqsave(&dev->lock,flags);
    timerperiod = dev->timeperiod;
    spin_unlock_irqrestore(&dev->lock,flags);
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->timeperiod));

}

/*
@description      :   驱动入口函数  
@param -          :   无 
@return           :   无
*/
static int __init timer_init(void)
{
    //初始化自旋锁
    spin_lock_init(&timerdev.lock);


    
    //注册字符设备驱动

    //1.创建设备号
    if(timerdev.major)
    {
        timerdev.devid = MKDEV(timerdev.major,0);
        register_chrdev_region(timerdev.devid,TIMER_CNT,TIMER_NAME);
    }
    else
    {
        alloc_chrdev_region(&timerdev.devid,0,TIMER_CNT,TIMER_NAME);//申请设备号
        timerdev.major = MAJOR(timerdev.devid);//获取主设备号
        timerdev.minor = MINOR(timerdev.devid);//获取次设备号
    }

    printk("timerdev major=%d,minor=%d\r\n",timerdev.major,timerdev.minor);

    //2.初始化cdev
    timerdev.cdev.owner = THIS_MODULE;
    cdev_init(&timerdev.cdev,&timer_fops);

    //3.添加cdev
    cdev_add(&timerdev.cdev,timerdev.devid,TIMER_CNT);

    //4.创建类
    timerdev.class = class_create(THIS_MODULE,TIMER_NAME);
    if(IS_ERR(timerdev.class))
    {
        return PTR_ERR(timerdev.class);
    }
    //5.创建设备
    timerdev.device = device_create(timerdev.class,NULL,timerdev.devid,NULL,TIMER_NAME);
    if(IS_ERR(timerdev.device))
    {
        return PTR_ERR(timerdev.device);
    }

    //6.初始化timer,设置定时器回调函数,未设置周期,因此不会激活定时器
    init_timer(&timerdev.timer);
    timerdev.timer.function = timer_function;
    timerdev.timer.data = (unsigned long)&timerdev;
    return 0;

}

static void __exit timer_exit(void)
{


    gpio_set_value(timerdev.led_gpio,1);
    del_timer_sync(&timerdev.timer);

    //注销字符驱动
    cdev_del(&timerdev.cdev);//删除cdev
    unregister_chrdev_region(timerdev.devid,TIMER_CNT);
    device_destroy(timerdev.class,timerdev.devid);
    class_destroy(timerdev.class);
    
}

module_init(timer_init);
module_exit(timer_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

定时器测试应用代码

  • timerApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"


#define CLOSE_CMD           (_IO(0xEF,0x1))     //关闭定时器
#define OPEN_CMD            (_IO(0xEF,0x2))     //打开定时器
#define SETPERIOD_CMD       (_IO(0xEF,0x3))     //设置定时器周期命令

int main(int argc, char *argv[])
{
    int fd,ret;
    char *filename;
    unsigned int cmd;
    unsigned int arg;
    unsigned char str[100];

    if(argc != 2)
    {
        printf("error usage!\r\n");
        return -1;
    }

    filename = argv[1];

    //打开LED驱动
    fd = open(filename,O_RDWR);

    if(fd<0)
    {
        printf("file %s open failed! \r\n",argv[1]);
        return -1;
    }

    while (1)
    {
        printf("Input CMD:");
        ret = scanf("%d",&cmd);
        if(ret != 1)//参数输入错误
        {
            gets(str);//防止卡死
        }

        if(cmd == 1)
            cmd = CLOSE_CMD;
        else if (cmd == 2)
        {
            cmd = OPEN_CMD;
        }
        else if (cmd == 3)
        {
            cmd = SETPERIOD_CMD;//设置周期
            printf("Input Timer Period:");
            ret = scanf("%d",&arg);
            if(ret != 1)//参数输入错误
            {
                gets(str);//防止卡死
            }
        }
        ioctl(fd,cmd,arg);//控制定时器打开关闭
    }
    close(fd);
}

Makefile

  • Makefile
KERNELDIR := /home/book/arm/imx6ull/ebf_6ull_linux
CURRENT_PATH := $(shell pwd)

obj-m := timer.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

make.sh

  • make.sh
#!/bin/bash
sudo make -j12  ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
arm-linux-gnueabihf-gcc timerApp.c -o timerApp
sudo rm -r .*.cmd *.mod.* *.o *.symvers *.order
sudo cp *.ko /home/book/arm/imx6ull/eth_file/modules
sudo cp *App /home/book/arm/imx6ull/eth_file/modules

你可能感兴趣的:(linux)