第八章Linux的应用层和驱动层的ioctl函数讲解

对于日常开发中的设备,它一般除了常规的读写操作来收发数据和保存数据外,通常还需要有更多其他的操作来实现更为复杂的功能,例如对于一个串口通信设备,它就需要具有波特率的设置和获取、数据帧格式的获取和设置等功能。

正因为硬件设备往往需要各自繁杂的操作,因此linux内核一般情况会将读写之外的I/o操作会分派给ioctl()函数来实现其他复杂的操作。

8.1应用层ioctl函数讲解

在linux应用层中,系统提供了一个对应的系统IO函数如下所示:
/*函数功能:ioctl控制函数
*函数参数:
*@int fd:用户程序打开设备时返回的文件描述符
*@unsigned long request:用户程序对设备的控制命令
*函数返回值:
*@成功:返回0
*@失败:返回小于0的值
*/
 #include <sys/ioctl.h>
 int ioctl(int fd, unsigned long request, ...);
常用函数原型:
int ioctl(int fd, unsigned int cmd, unsigned long args);
int ioctl(int fd, unsigned int cmd);

对于ioctl()函数第二个参数cmd而言,它是一个应用层程序向内核驱动设备发送的一个控制命令,这个命令一般不是单纯的传入一个随机的整型数的,在linux中给出了如下对ioctl()函数命令的定义建议:

图8.1 ioctl函数控制命令组成

(1) 命令码中的设备类型使用”幻数”进行填充,其中幻数的范围是在0x00~0xFF之间,由于内核中的ioctl-number.txt已经给出了一些推荐的幻数,如果需要新定义设备驱动的“幻数”时,此时需要注意新定义的是否与已有的幻数进行冲突。

(2) 命令码的序列号为8位。

(3) 命令吗的方向字段则为2位,其中可能是如下的值:

_IOC_NONE  无数据传输
_IOC_READ  读数据
_IOC_WRITE 写数据
_IOC_READ|_IOC_WRITE 读写数据同时使能

(4) 命令码的数据长度这个与用户数据大小相关,它的长度通常为13位或者14位

linux为了考虑到编程的便利和规范性,对于命令的生成,linux给出了四个函数用来辅助生成,其中包括_IO()、_IOR()、_IOW()和_IOWR()。如下为这四个函数的定义:

#define _IOC(dir,type,nr,size) \
    ((unsigned int)                \
     (((dir)  << _IOC_DIRSHIFT) |        \
      ((type) << _IOC_TYPESHIFT) |        \
      ((nr)   << _IOC_NRSHIFT) |        \
      ((size) << _IOC_SIZESHIFT)))
/* used to create numbers */
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)    _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size)    _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

由上面的定义可以看出,这些辅助宏函数的主要作用在于传入type(设备类型字段)、nr(序列号字段)和size(数据长度字段)以及宏名隐含的方向字段位移组合来进行生成相应的命令码。

一般在我们编写代码时会将相应的命令码生成提前用宏来定义,例如如下定义了对led灯操作的命令定义:

#define GEC6818_LED_ON        _IOW('L',  1, unsigned long)
#define GEC6818_LED_OFF    _IOW('L',  2, unsigned long)

8.2驱动层ioctl函数讲解

对于驱动层的ioctl的函数原型来说是与应用层的ioctl函数原型是有点差别的,具体如下为驱动层的ioctl函数的原型:

*函数参数:/*函数功能:驱动层ioctl控制函数 *@struct file *filp:内核打开设备文件的指针
*@unsigned int cmd: 用户程序发送过来的对设备的控制命令
*@unsigned long args:发送给应用程序的参数
*函数返回值:
*@成功:返回0
*@失败:返回小于0的值
*/
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long args);

从这个驱动层的函数原型来看,它的cmd和args参数都是与应用层的ioctl函数对应起来了,可以接收来自应用层的控制命令和参数,实现对驱动设备的控制。

在了解了驱动层的ioctl函数定义后,要让它能够可以在内核驱动中起到作用,还需要进行一步操作就是,需要将定义好ioctl驱动函数的地址赋值给struct file_operations结构体中的unlocked_ioctl函数指针,然后再注册进内核,则该ioctl函数才能够正常使用,如下为将控制led灯的ioctl驱动函数赋值给file_operations的例子:

static long gec6818_led_ioctl (struct file *filp, unsigned int cmd, unsigned long args)
{
    //获取当前要控制的LED灯
    unsigned long led_pos = args - 7;
    switch(cmd)
    {
        case GEC6818_LED_ON:
                gpio_set_value(led_gpio_info_tbl[led_pos].num,0);
        break;
        case GEC6818_LED_OFF:
                gpio_set_value(led_gpio_info_tbl[led_pos].num,1);
        break;        
        default:
            return -ENOIOCTLCMD;
    }
    return 0;
}
static const struct file_operations gec6818_led_fops = {
     .owner             = THIS_MODULE,
    .unlocked_ioctl = gec6818_led_ioctl,
    .open             = gec6818_led_open,
    .release         = gec6818_led_release,
    .read             = gec6818_led_read,
};

8.3 ioctl函数应用示例讲解

在讲解完应用层和驱动层的ioctl函数的相关知识后,接下来将给出利用ioctl函数来实现对led灯控制的事例代码:

在驱动led_drv.c中定义了ioctl函数”static long gec6818_led_ioctl (struct file *filp, unsigned int cmd, unsigned long args)”,主要是用来控制指定的LED的亮灭情况,其中具体流程如下:

(1) 首先通过参数args传入来判断是需要控制哪一个LED灯的状态。

(2) 然后再通过传入参数cmd来对该灯进行控制亮灭,其中定义了如下两个宏来进行控制:

#define GEC6818_LED_ON        _IOW('L',  1, unsigned long)
#define GEC6818_LED_OFF        _IOW('L',  2, unsigned long)

(1) 对于ioctl中出现了如下的gpio_set_value函数是对应于后面章节GPIO框架函数的内容,将在后续章节进行详细讲解,下面仅提供函数的定义:

/*函数功能: gpio_set_value为gpio框架的驱动函数,用于设置GPIO的状态值
*函数参数:
*@unsigned int gpio:为对应的GPIO值
*@ int value:为需要设置GPIO的状态值
*函数返回值:无
*/
static inline void gpio_set_value(unsigned int gpio, int value)
{
    __gpio_set_value(gpio, value);
}

led_drv.c驱动代码

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <cfg_type.h>
#include <linux/miscdevice.h>
#include <linux/ioctl.h>
#define GEC6818_LED_ON        _IOW('L',  1, unsigned long)
#define GEC6818_LED_OFF        _IOW('L',  2, unsigned long)

struct led_gpio_info{
    unsigned int num;
    char name[20];    
};
static const struct led_gpio_info led_gpio_info_tbl[4]={
    {
        .num  = PAD_GPIO_E+13,
        .name = "gpioe13",
    },
    {
        .num  = PAD_GPIO_C+7,
        .name = "gpioc7",
    },    
    {
        .num  = PAD_GPIO_C+8,
        .name = "gpioc8",
    },        
    {
        .num  = PAD_GPIO_C+17,
        .name = "gpioc17",
    },        
};        

static int  gec6818_led_open (struct inode * inode, struct file *file)
{
    int i=0;
    //配置GPIOE13、GPIOC17、GPIOC7、GPIOC8为输出模式
    for(i=0; i<4; i++)
        gpio_direction_output(led_gpio_info_tbl[i].num,1);
    printk("gec6818_led_open \n");
    return 0;
}

static int  gec6818_led_release (struct inode * inode, struct file *file)
{
    printk("gec6818_led_release \n");
    return 0;
}


static long gec6818_led_ioctl (struct file *filp, unsigned int cmd, unsigned long args)
{
    //获取当前要控制的LED灯
    unsigned long led_pos = args - 7;
    switch(cmd)
    {
        case GEC6818_LED_ON:
                gpio_set_value(led_gpio_info_tbl[led_pos].num,0);
        break;
        case GEC6818_LED_OFF:
                gpio_set_value(led_gpio_info_tbl[led_pos].num,1);
        break;        
        default:
            return -ENOIOCTLCMD;
    }
    return 0;
}

static ssize_t gec6818_led_read (struct file *file, char __user *buf, size_t len, loff_t * offs)
{
    return 0;
}

static const struct file_operations gec6818_led_fops = {
     .owner             = THIS_MODULE,
    .unlocked_ioctl = gec6818_led_ioctl,
    .open             = gec6818_led_open,
    .release         = gec6818_led_release,
    .read             = gec6818_led_read,
};

static struct miscdevice gec6818_led_miscdev = {
    .minor        = MISC_DYNAMIC_MINOR,    //MISC_DYNAMIC_MINOR,动态分配次设备号
    .name        = "gec6818_leds",        //设备名称,/dev/gec6818_leds    
    .fops        = &gec6818_led_fops,    //文件操作集
};

//入口函数
static int __init gec6818_led_init(void)
{
    int rt=0;
    int i=0;
    //混杂设备的注册
    rt = misc_register(&gec6818_led_miscdev);
    if (rt) 
    {
        printk("misc_register fail\n");
        return rt;
    }

    //这招是损招,先释放gpio占用的资源
    for(i=0; i<4; i++)
        gpio_free(led_gpio_info_tbl[i].num);    
    //申请GPIOE13、GPIOC7、GPIOC8、GPIOC17
    for(i=0; i<4; i++)
    {
        rt=gpio_request(led_gpio_info_tbl[i].num,
                        led_gpio_info_tbl[i].name);            
        if(rt < 0)
        {
            printk("gpio_request:%s fail\n",led_gpio_info_tbl[i].name);
            goto gpio_request_fail;
        }
    }
    printk("gec6818 led init\n");
    return 0;
    
gpio_request_fail:
    for(i=0; i<4; i++)
        gpio_free(led_gpio_info_tbl[i].num);
    
    misc_deregister(&gec6818_led_miscdev);
    return rt;
}


//出口函数
static void __exit gec6818_led_exit(void)
{
    int i;
    for(i=0; i<4; i++)
        gpio_free(led_gpio_info_tbl[i].num);
    misc_deregister(&gec6818_led_miscdev);
    printk("gec6818 led exit\n");
}

//驱动程序的入口:insmod led_drv.ko调用module_init,module_init又会去调用gec6818_led_init。
module_init(gec6818_led_init);
//驱动程序的出口:rmsmod led_drv调用module_exit,module_exit又会去调用gec6818_led_exit。
module_exit(gec6818_led_exit)
//模块描述
MODULE_AUTHOR("stephenwen88@163.com");            //作者信息
MODULE_DESCRIPTION("gec6818 led driver");        //模块功能说明
MODULE_LICENSE("GPL");                            //许可证:驱动遵循GPL协议

led_test.c应用层代码,从代码中可以看出应用层传入的cmd的宏定义值与驱动层的ioctl中的宏定义值相同,然后再传入args参数来指定需要控制哪一个LED灯,即可完成整个LED灯的控制过程,最终运行程序后可以看到一个流水灯的效果:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define GEC6818_LED_ON        _IOW('L',  1, unsigned long)
#define GEC6818_LED_OFF        _IOW('L',  2, unsigned long)
int main(int argc, char **argv)
{
    int fd=-1;
    int len;
    int i=0;
    //打开gec6818_leds设备
    fd = open("/dev/gec6818_leds",O_RDWR);
    if(fd < 0)
    {
        perror("open /dev/gec6818_leds:");
        return fd;
    }
    while(1)
    {
        for(i=7; i<11; i++)
        {
            ioctl(fd,GEC6818_LED_ON,i);
            sleep(1);
            ioctl(fd,GEC6818_LED_OFF,i);
            sleep(1);    
        }
    }
    close(fd);
    return 0;
}

8.4本章总结

本章节主要讲解了驱动层中的一个比较重要的函数ioctl的实现过程,ioctl函数能够让程序实现对设备进行一些较为复杂的操作,同时讲解了关于应用层ioctl和驱动层ioctl的有关联系,以及给出了利用ioctl函数实现对设备操作的事例,加深对驱动中的ioctl的理解,为后续的设备驱动学习起到铺垫的作用。












全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务