第八章Linux的应用层和驱动层的ioctl函数讲解
对于日常开发中的设备,它一般除了常规的读写操作来收发数据和保存数据外,通常还需要有更多其他的操作来实现更为复杂的功能,例如对于一个串口通信设备,它就需要具有波特率的设置和获取、数据帧格式的获取和设置等功能。
正因为硬件设备往往需要各自繁杂的操作,因此linux内核一般情况会将读写之外的I/o操作会分派给ioctl()函数来实现其他复杂的操作。
8.1应用层ioctl函数讲解
/*函数功能: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()函数命令的定义建议:
(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的理解,为后续的设备驱动学习起到铺垫的作用。