Linux驱动开发九.内核定时器使用——1.定时器APP调用


在上面一张我们在一个基础IO外设上加上了定时器功能,但是在设备驱动挂载完成后直接就运行定时器了,这样肯定是不行的。一定是需要一个APP程序和底层驱动进行交互。APP起码具备的功能有启动、停止定时器,修改定时器工作周期的功能。

在前面所有的APP中我们主要用了file_operations结构体中的open、read、write和realease。其实write就可以满足我们的需求,但是今天我们通过一个新的操作来实现数据的交互——ioctrl

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

ioctl和write用法有些区别,用man来打印一下看看

Linux驱动开发九.内核定时器使用——1.定时器APP调用

 

 上面的是write的介绍,下面是ioctl的介绍

Linux驱动开发九.内核定时器使用——1.定时器APP调用

 

 具体的区别我暂时还没搞太明白,但是很明显两个函数的参数是不一样的,write是通过指针指向要修改的地方,有个参数是指针类型。而ioctl是两个int类型的参数。

compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。因为我们的I.MX6UL运行的是32位的程序,所以我们要使用的就是unlock_ioctl。

/**
 * @brief 文件操作集合
 * 
 */
static const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open =  new_dev_open,
    .unlocked_ioctl = new_dev_ioctl,
};

 

unlock_ioctl函数

先看下函数的参数

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

我们可以查到ioctl的引用一直到include/uapi/asm-generic/ioctl.h这个文件中有下面的介绍

/* ioctl command encoding: 32 bits total, command in lower 16 bits,
 * size of the parameter structure in the lower 14 bits of the
 * upper 16 bits.
 * Encoding the size of the parameter structure in the ioctl request
 * is useful for catching programs compiled with old versions
 * and to avoid overwriting user space outside the user buffer area.
 * The highest 2 bits are reserved for indicating the ``access mode''.
 * NOTE: This limits the max parameter size to 16kB -1 !
 */

/*
 * The following is for compatibility across the various Linux
 * platforms.  The generic ioctl numbering scheme doesn't really enforce
 * a type field.  De facto, however, the top 8 bits of the lower 16
 * bits are indeed used as a type field, so we might just as well make
 * this explicit here.  Please be sure to use the decoding macros
 * below from now on.
 */

第一个参数不用说了,就是文件句柄,第二个参数是命令,这个命令是有固定格式的,我们后面再讲。最后的参数可以用来传一个长整型的参数。既然第二个参数是命令,我们就可以使用switch语句来进行处理。内核中整个函数结果可以简化成这样的

static long new_dev_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
    switch (cmd)
    {
    case cmd1:
        //do case1
        break;
    
    case cmd2:
        //do case2
        break;

    case cmd3:
        //do case3
        break;
    }
}

我们只需要根据不同的cmd对应的值去做相应的工作就行了。

cmd结构

虽然命令的数据类型是个int类型,但是这个数据时有形式要求的。这个在Documentation/ioctl/路径下的ioctl-number.txt里有详细的说明。我们可以在里面看一下内核里使用这个ioctl写的驱动样式.

 随便在内核中搜索一下ioctl的驱动文件,可以发现下面的案例

 1 #define CMD_COREB_START        _IO('b', 0)
 2 #define CMD_COREB_STOP        _IO('b', 1)
 3 #define CMD_COREB_RESET        _IO('b', 2)
 4 
 5 static long
 6 coreb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 7 {
 8     int ret = 0;
 9 
10     switch (cmd) {
11     case CMD_COREB_START:
12         bfin_write_SYSCR(bfin_read_SYSCR() & ~0x0020);
13         break;
14     case CMD_COREB_STOP:
15         bfin_write_SYSCR(bfin_read_SYSCR() | 0x0020);
16         bfin_write_SICB_SYSCR(bfin_read_SICB_SYSCR() | 0x0080);
17         break;
18     case CMD_COREB_RESET:
19         bfin_write_SICB_SYSCR(bfin_read_SICB_SYSCR() | 0x0080);
20         break;
21     default:
22         ret = -EINVAL;
23         break;
24     }
25 
26     CSYNC();
27 
28     return ret;
29 }

在上面的驱动代码中,就是通过switch来实现cmd的处理的,注意cmd是开始定义的宏,这个宏是调用了一个_IO的函数。所以也就是说这个cmd的格式是有要求的。

cmd命令格式

Linux内核在使用ioctl函数来进行文件读写操作时,是对cmd参数有指定要求的。在内核文档ioctl/ioctl-decoding.txt里面有对其进行的详细说明

Linux驱动开发九.内核定时器使用——1.定时器APP调用

整个cmd包含了4个部分,包括了幻数、 序数、传输方向和数据大小,有些介绍在这里我们不再做详细的说明。总之我们要构造一个cmd是很麻烦的事情,还好,Linux在include/uapi/asm-generic/ioctl.h里为我们提供了一个API生成这个cmd,我们只需要传输相应的参数就可以了

 1 #ifndef _UAPI_ASM_GENERIC_IOCTL_H
 2 #define _UAPI_ASM_GENERIC_IOCTL_H
 3 
 4 /* ioctl command encoding: 32 bits total, command in lower 16 bits,
 5  * size of the parameter structure in the lower 14 bits of the
 6  * upper 16 bits.
 7  * Encoding the size of the parameter structure in the ioctl request
 8  * is useful for catching programs compiled with old versions
 9  * and to avoid overwriting user space outside the user buffer area.
10  * The highest 2 bits are reserved for indicating the ``access mode''.
11  * NOTE: This limits the max parameter size to 16kB -1 !
12  */
13 
14 /*
15  * The following is for compatibility across the various Linux
16  * platforms.  The generic ioctl numbering scheme doesn't really enforce
17  * a type field.  De facto, however, the top 8 bits of the lower 16
18  * bits are indeed used as a type field, so we might just as well make
19  * this explicit here.  Please be sure to use the decoding macros
20  * below from now on.
21  */
22 #define _IOC_NRBITS    8
23 #define _IOC_TYPEBITS    8
24 
25 /*
26  * Let any architecture override either of the following before
27  * including this file.
28  */
29 
30 #ifndef _IOC_SIZEBITS
31 # define _IOC_SIZEBITS    14
32 #endif
33 
34 #ifndef _IOC_DIRBITS
35 # define _IOC_DIRBITS    2
36 #endif
37 
38 #define _IOC_NRMASK    ((1 << _IOC_NRBITS)-1)
39 #define _IOC_TYPEMASK    ((1 << _IOC_TYPEBITS)-1)
40 #define _IOC_SIZEMASK    ((1 << _IOC_SIZEBITS)-1)
41 #define _IOC_DIRMASK    ((1 << _IOC_DIRBITS)-1)
42 
43 #define _IOC_NRSHIFT    0
44 #define _IOC_TYPESHIFT    (_IOC_NRSHIFT+_IOC_NRBITS)
45 #define _IOC_SIZESHIFT    (_IOC_TYPESHIFT+_IOC_TYPEBITS)
46 #define _IOC_DIRSHIFT    (_IOC_SIZESHIFT+_IOC_SIZEBITS)
47 
48 /*
49  * Direction bits, which any architecture can choose to override
50  * before including this file.
51  */
52 
53 #ifndef _IOC_NONE
54 # define _IOC_NONE    0U
55 #endif
56 
57 #ifndef _IOC_WRITE
58 # define _IOC_WRITE    1U
59 #endif
60 
61 #ifndef _IOC_READ
62 # define _IOC_READ    2U
63 #endif
64 
65 #define _IOC(dir,type,nr,size) /
66     (((dir)  << _IOC_DIRSHIFT) | /
67      ((type) << _IOC_TYPESHIFT) | /
68      ((nr)   << _IOC_NRSHIFT) | /
69      ((size) << _IOC_SIZESHIFT))
70 
71 #ifndef __KERNEL__
72 #define _IOC_TYPECHECK(t) (sizeof(t))
73 #endif
74 
75 /* used to create numbers */
76 #define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
77 #define _IOR(type,nr,size)    _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
78 #define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
79 #define _IOWR(type,nr,size)    _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
80 #define _IOR_BAD(type,nr,size)    _IOC(_IOC_READ,(type),(nr),sizeof(size))
81 #define _IOW_BAD(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
82 #define _IOWR_BAD(type,nr,size)    _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
83 
84 /* used to decode ioctl numbers.. */
85 #define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
86 #define _IOC_TYPE(nr)        (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
87 #define _IOC_NR(nr)        (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
88 #define _IOC_SIZE(nr)        (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
89 
90 /* ...and for the drivers/sound files... */
91 
92 #define IOC_IN        (_IOC_WRITE << _IOC_DIRSHIFT)
93 #define IOC_OUT        (_IOC_READ << _IOC_DIRSHIFT)
94 #define IOC_INOUT    ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
95 #define IOCSIZE_MASK    (_IOC_SIZEMASK << _IOC_SIZESHIFT)
96 #define IOCSIZE_SHIFT    (_IOC_SIZESHIFT)
97 
98 #endif /* _UAPI_ASM_GENERIC_IOCTL_H */

我们主要使用的就是滴85~88行几组宏,说白了就是将0或1左右移动最后获取到指定的cmd。

#define _IO(type,nr)            //没有参数的命令
#define _IOR(type,nr,size)      //从驱动中读取数据
#define _IOW(type,nr,size)      //向驱动中写入数据
#define _IOWR(type,nr,size)     //双向数据传输

其中参数type就是幻数、nr是序号,size是大小,幻数也是个int点数据,可以在内核文档Documentation/ioctl/ioctl-number.txt里已经给出了参考值,可以根据需求从里面查询。

所以我们可以把我们需要对命令按照需求声明出来。我们的APP和驱动交互主要用来实现3个功能,启动、关闭定时器及修改定时器工作频率,打开和关闭是不用传递额外的参数的,而设置周期要写入个周期,就要用_IOW了。幻数使用的0xEF

#define CMD_CLOSE       _IO(0xEF,1)             //cmd值为1,关闭定时器
#define CMD_OPEN        _IO(0xEF,2)             //cmd值为2,启动定时器
#define CMD_PERIOD      _IOW(0xEF,3,int)        //cmd值为3,修改定时器工作频率

前两个不需要传参数,就用来_IO,不用指定count,设置频率涉及到应用程序向内核写数据,就用个_IOW,参数int意思是我们要传递个int类型的数据,所以长度就是int对应的长度值。可以看出来,使用Linux提供的方法来构建cmd就简单多了,我们只需要使用定义好的宏就可以了。

驱动修改

下面我们就可以根据ioctl的样式修改前面的驱动。主要就是unlocked_ioctl绑定的函数构建

static long new_dev_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
    int ret = 0;
    int value = 0;
    struct new_dev *dev = file->private_data;
    switch (cmd)
    {
    case CMD_CLOSE:
        del_timer_sync(&dev->timer);
        gpio_set_value(dev->gpio,1);
        break;
    
    case CMD_OPEN:
        value = atomic_read(&dev->timer_per);
        mod_timer(&dev->timer,jiffies+msecs_to_jiffies(value));
        break;

    case CMD_PERIOD:
        ret = copy_from_user(&value,(int *)arg,sizeof(int));  //arg是应用传递给驱动的周期值数据首地址,长度为4个字节
            if(ret<0){
            return -EFAULT;
        }
        atomic_set(&dev->timer_per,value);
        mod_timer(&dev->timer,jiffies+msecs_to_jiffies(value));
        break;
    }
}

驱动没什么可讲的,主要就是一个switch结构,根据APP通过ioctl操作传进来的cmd进行操作。要注意点就是第修改周期时使用了copy_from_user函数,注意传递函数时和APP里指针取值的对应。还有就是如果我们的定时器如果在运行中,是要不停读取timer_per这个变量值来设置定时器expires的,如果我们在没有停止定时器时去设置周期值,很有可能发生竞争,所以在一开始设计程序结构的时候,这个变量采用了原子变量,避免了竞争的发生。所以我前面说过,并发和竞争一定要在写代码前先想好,要不是改起来就很麻烦了!

下面吧整个驱动的代码放出来

Linux驱动开发九.内核定时器使用——1.定时器APP调用

/**
 * @file timer.c
 * @author your name (you@domain.com)
 * @brief 定时器测试驱动程序
 * @version 0.1
 * @date 2022-07-16
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#include <linux/ioctl.h>
#define DEVICE_CNT      1
#define DEVICE_NAME    "time"


#define CMD_CLOSE       _IO(0xEF,1)
#define CMD_OPEN        _IO(0xEF,2)
#define CMD_PERIOD      _IOW(0xEF,3,int)  


struct new_dev
{
    dev_t dev_id;
    int major;
    int minor;
    struct class *class;
    struct device *device;
    struct cdev cdev;
    struct device_node *dev_nd;
    int gpio;
    struct timer_list timer;    //定时器
    atomic_t timer_per;         //定时器周期
};

struct new_dev new_dev;

static int new_dev_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &new_dev;     /* 设置私有数据 */
    return 0;
}

/**
 * @brief 文件io操作
 * 
 * @param file 
 * @param cmd 
 * @param arg 
 * @return long 
 */
static long new_dev_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
    int ret = 0;
    int value = 0;
    struct new_dev *dev = file->private_data;
    switch (cmd)
    {
    case CMD_CLOSE:
        del_timer_sync(&dev->timer);
        gpio_set_value(dev->gpio,1);
        break;
    
    case CMD_OPEN:
        value = atomic_read(&dev->timer_per);

        mod_timer(&dev->timer,jiffies+msecs_to_jiffies(value));

        break;

    case CMD_PERIOD:
        
        ret = copy_from_user(&value,(int *)arg,sizeof(int));  //arg是应用传递给驱动的周期值数据首地址,长度为4个字节
        
        if(ret<0){
            return -EFAULT;
        }
        atomic_set(&dev->timer_per,value);
        mod_timer(&dev->timer,jiffies+msecs_to_jiffies(value));
        break;
    }
}

/**
 * @brief 文件操作集合
 * 
 */
static const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open =  new_dev_open,
    // .release = new_dev_release,
    .unlocked_ioctl = new_dev_ioctl,
};





//gpio设备初始化
int beep_init(struct new_dev *dev)
{
    int ret = 0 ;

    //从设备树搜索设备节点
    dev->dev_nd = of_find_node_by_path("/beep");
    if(dev->dev_nd == NULL){
        printk("no device found/r/n");
        ret = -EINVAL;
        goto fail_nd;
    }

    //获取beep对应GPIO
    dev->gpio = of_get_named_gpio(dev->dev_nd,"beep-gpios",0);
    printk("beep_gpio=%d/r/n",dev->gpio);
    if(dev->gpio < 0){
        printk("no GPIO found!/r/n");
        ret = -EINVAL;     //errno-base.h中定义的异常数值到34,这里从100开始使用防止冲突
        goto fail_gpio;
    }

    //请求GPIO
    ret = gpio_request(dev->gpio,"beep-gpio");
    if(ret){
        printk("gpio request err/r/n");
        ret = -EBUSY;
        goto fail_request;}
    

    ret = gpio_direction_output(dev->gpio,1);
    if(ret < 0){
        ret = -EINVAL;
        goto fail_gpioset;
    }

    return 0;
    fail_gpioset:
        gpio_free(dev->gpio);
    fail_request:
    fail_gpio:
    fail_nd:
        return ret;
}


timer_func(unsigned long arg){
    static int stat = 1;
    int value = 0;
    struct new_dev *dev = (struct new_dev*)arg;
    stat = !stat;
    gpio_set_value(dev->gpio,stat);
    value = atomic_read(&dev->timer_per);
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(value));
}


static int __init timer_init(void){

    int ret = 0; 
    unsigned int value = 500;

    //申请设备号
    new_dev.major = 0;
    if(new_dev.major){
        //手动指定设备号,使用指定的设备号
        new_dev.dev_id = MKDEV(new_dev.major,0);
        ret = register_chrdev_region(new_dev.dev_id,DEVICE_CNT,DEVICE_NAME);
    }
    else{
        //设备号未指定,申请设备号
        ret = alloc_chrdev_region(&new_dev.dev_id,0,DEVICE_CNT,DEVICE_NAME);
        new_dev.major = MAJOR(new_dev.dev_id);
        new_dev.minor = MINOR(new_dev.dev_id);
    }
    printk("dev id geted!/r/n");

    if(ret<0){
        //设备号申请异常,跳转至异常处理
        goto faile_devid;
    }

    //字符设备cdev初始化
    new_dev.cdev.owner = THIS_MODULE;

    cdev_init(&new_dev.cdev,&key_fops);                 //文件操作集合映射

    ret = cdev_add(&new_dev.cdev,new_dev.dev_id,DEVICE_CNT);
    if(ret<0){
        //cdev初始化异常,跳转至异常处理
        goto fail_cdev;
    }

    printk("chr dev inited!/r/n");


    //自动创建设备节点
    new_dev.class = class_create(THIS_MODULE,DEVICE_NAME);
    if(IS_ERR(new_dev.class)){
        //class创建异常处理
        printk("class err!/r/n");
        ret = PTR_ERR(new_dev.class);
        goto fail_class;
    }
    printk("dev class created/r/n");
    new_dev.device = device_create(new_dev.class,NULL,new_dev.dev_id,NULL,DEVICE_NAME);
    if(IS_ERR(new_dev.device)){
        //设备创建异常处理
        printk("device err!/r/n");
        ret = PTR_ERR(new_dev.device);
        goto fail_device;
    }
    printk("device created!/r/n");


    //gpio外设初始化
    ret = beep_init(&new_dev);
    if(ret<0){
        printk("gpio init err/r/n");
        goto fail_gpioinit;
    }


    //定时器初始化
    init_timer(&new_dev.timer);
    atomic_set(&new_dev.timer_per,value);
    new_dev.timer.expires = jiffies + msecs_to_jiffies(value);
    new_dev.timer.function = timer_func;
    new_dev.timer.data = (unsigned long)&new_dev;
    add_timer(&new_dev.timer);



    return ret;
fail_gpioinit:

fail_device:
    //device创建失败,意味着class创建成功,应该将class销毁
    printk("device create err,class destroyed/r/n");
    class_destroy(new_dev.class);
fail_class:
    //类创建失败,意味着设备应该已经创建成功,此刻应将其释放掉
    printk("class create err,cdev del/r/n");
    cdev_del(&new_dev.cdev);
fail_cdev:
    //cdev初始化异常,意味着设备号已经申请完成,应将其释放
    printk("cdev init err,chrdev register/r/n");
    unregister_chrdev_region(new_dev.dev_id,DEVICE_CNT);
faile_devid:
    //设备号申请异常,由于是第一步操作,不需要进行其他处理
    printk("dev id err/r/n");
    return ret;
}


static void __exit timer_exit(void)
{

    gpio_set_value(new_dev.gpio,1);
    del_timer(&new_dev.timer);

    cdev_del(&new_dev.cdev);
    unregister_chrdev_region(new_dev.dev_id,DEVICE_CNT);

    device_destroy(new_dev.class,new_dev.dev_id);
    class_destroy(new_dev.class);

    gpio_free(new_dev.gpio);

}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZeqiZ");

View Code

 

应用APP

先把应用APP的代码放出来

/**
 * @file timerAPP.c
 * @author your name (you@domain.com)
 * @brief 定时器APP测试程序
 * @version 0.1
 * @date 2022-07-16
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#define CMD_CLOSE       _IO(0xEF,1)
#define CMD_OPEN        _IO(0xEF,2)
#define CMD_PERIOD      _IOW(0xEF,3,int)  

/**
 * @brief 
 * 
 * @param argc                      //参数个数 
 * @param argv                      //参数
 * @return int 
 */
int main(int argc,char *argv[])
{
    char *filename;                 //文件名
    filename = argv[1];             //文件名为命令行后第二个参数(索引值为1)
    int value = 0;
    int ret = 0;                    //初始化操作返回值
    int f = 0;                      //初始化文件句柄

    unsigned int cmd;
    unsigned int arg;
    unsigned char str[100];

    f = open(filename, O_RDWR);     //打开文件
    
    if(f < 0){
    printf("file open error/r/n");
    return -1;
    }

    while(1){
        printf("Input CMD:");
        ret = scanf("%d",&cmd);     //cmd对应

        if(ret !=1){
            gets(str);//防止卡死
        }

        if(cmd == 1){               //cmd值为1,关闭定时器
            ioctl(f,CMD_CLOSE,&arg);
        }
        else if(cmd == 2){          //cmd值为2,启动定时器
            ioctl(f,CMD_OPEN,&arg);
        } 
        else if(cmd == 3){          //cmd值为3,设置定时器周期
            printf("Input Timer Period=");
            ret = scanf("%d",&arg);
            if(ret !=1){
                gets(str);
            }
            ioctl(f,CMD_PERIOD,&arg);
        }
        }
    
    close(f);                       //关闭文件
    return 0;
}

程序运行以后,先打开驱动文件,然后进入while循环等待键盘输入cmd的值。当输入1时,定时器关闭,输入2时定时器重新启动,输入3时从键盘获取新的周期值

Linux驱动开发九.内核定时器使用——1.定时器APP调用

 

原创文章,作者:jamestackk,如若转载,请注明出处:https://blog.ytso.com/274711.html

(0)
上一篇 2022年7月16日
下一篇 2022年7月16日

相关推荐

发表回复

登录后才能评论