文章目录
次设备号使用及混杂设备驱动开发
使用次设备号
字符设备驱动——struct file数据结构
案例:将四个LED灯作为四个相同的设备个体,共享一个驱动,但是对应不同的次设备号。
驱动示例代码实现:
示例运行:
Linux混杂设备驱动开发
Linux内核混杂设备特点
Linux内核描述混杂设备的数据结构
代码示例(gpio-led):
执行结果
次设备号使用及混杂设备驱动开发
使用次设备号
之前我们都是使用一个主设备号创建一个字符设备文件进行使用,但是当具有多个同样设备的时候,就会用到次设备号,例如:使用操作多个LED灯(每个LED灯分别对应一个设备),多个USB设备对象、多个串口设备对象等等情况。
这里只以多个LED灯作为多个设备来实现这种情况下的使用。
字符设备驱动——struct file数据结构
- 在设备操作接口中open/release/read/write/unlocked_ioctl等接口中的形参struct file *file指向的就是用户空间下应用程序操作的设备文件(例如:/dev/myled)
struct file { //指向驱动开发者自己定义初始化的硬件操作接口对象led_fops const struct file_operations *f_op; ... };
- 功能:描述一个文件被打开(open)后的状态属性。
- 生命周期:每次open一个文件成功后,内核会用此数据结构定义一个file对象来描述这个文件代开以后的状态属性(O_RDWR),每当close文件时,内核也会删除对应的file对象
- 所以在硬件操作接口中的形参指向的就是内核创建的file对象。通过这个参数能够获得当前操作的设备文件的设备号等信息。
struct inode *inode = file->f_path.dentry->d_inode; int minor = iminor(inode); //获取次设备号 int major = imajor(inode); //获取主设备号
案例:将四个LED灯作为四个相同的设备个体,共享一个驱动,但是对应不同的次设备号。
分析:实现以上情况,我们需要编写一个驱动,驱动内申请一个主设备号,4个次设备号分别对应四个设备文件,但是cdev对象只需要一个,因为是同样的设备,设备接口实现一个就可以了。
驱动示例代码实现:
- led_drv.c
#include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <mach/platform.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> //copy_from_user声明 #include <linux/device.h> //设备文件的自动创建 //声明描述LED硬件信息的数据结构 struct led_resource { int gpio; //GPIO编号 char *name; //LED名称 }; //定义初始化LED的硬件信息对象 static struct led_resource led_info[] = { { .name = "LED1", .gpio = PAD_GPIO_C+12 }, { .name = "LED2", .gpio = PAD_GPIO_C+7 }, { .name = "LED3", .gpio = PAD_GPIO_C+11 }, { .name = "LED4", .gpio = PAD_GPIO_B+26 } }; //调用关系:应用ioctl->软中断->内核sys_ioctl->驱动led_ioctl //例子:int index=1;ioctl(fd, LED_ON, &index);//开第1个灯 //参数关系: // fd<---->file 亲戚关系 // cmd=LED_ON或者cmd=LED_OFF // arg=(unsigned long)&index #define LED_ON 0x100001 //开灯命令 #define LED_OFF 0x100002 //关灯命令 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { //1.应用通过fd来获取file,驱动通过file来获取inode struct inode *inode = file->f_path.dentry->d_inode; //2.通过inode来获取次设备号 //int minor = iminor(inode); int minor = MINOR(inode->i_rdev); //3.解析用户发送过来的命令 switch(cmd) { case LED_ON: gpio_set_value(led_info[minor].gpio, 0); printk("%s:开第%d个灯", __func__, minor+1); break; case LED_OFF: gpio_set_value(led_info[minor].gpio, 1); printk("%s:关第%d个灯", __func__, minor+1); break; default: printk("无效命令!\n"); return -1; } return 0; //执行成功返回0,执行失败返回负值 } //定义初始化LED的硬件操作接口对象 static struct file_operations led_fops = { .unlocked_ioctl = led_ioctl, //不仅仅向硬件设备发送控制命令,还能和设备进行读写操作 }; //定义设备号对象 static dev_t dev; //定义字符设备对象 static struct cdev led_cdev; //定义设备类指针 static struct class *cls; static int led_init(void) { int i; //1.申请设备号 //只需要在这里多加此设备号的申请就好了 alloc_chrdev_region(&dev, 0, 4, "myled"); //2.初始化字符设备对象,本质就是给字符设备添加操作接口 cdev_init(&led_cdev, &led_fops); //3.向内核注册字符设备对象并且提供硬件操作接口 cdev_add(&led_cdev, dev, 4); //4.申请GPIO资源配置为输出,输出1 for(i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_request(led_info[i].gpio, led_info[i].name); gpio_direction_output(led_info[i].gpio, 1); } //5.创建设备类对象,类似长树枝 //将来会创建:rootfs/sys/class/zhangsan cls = class_create(THIS_MODULE, "zhangsan"); //6.创建设备文件/dev/myled,类似长苹果 //dev:就是提供创建设备文件时所需的设备号 //myled:就是提供创建设备文件是所需的设备文件名 device_create(cls, NULL, dev, NULL, "myled"); return 0; } static void led_exit(void) { int i; //1.输出1,释放GPIO资源 for (i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_set_value(led_info[i].gpio, 1); gpio_free(led_info[i].gpio); } //2.释放设备号 unregister_chrdev_region(dev, 1); //3.卸载字符设备对象 cdev_del(&led_cdev); //4.删除设备文件(摘苹果)和设备类(砍树枝) device_destroy(cls, dev); class_destroy(cls); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
- led_test.c(实现流水灯)
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #define LED_ON 0x100001 //开灯命令 #define LED_OFF 0x100002 //关灯命令 int main(int argc, char *argv[]) { int fd1,fd2,fd3,fd4; //打开设备 fd1 = open("/dev/myled1", O_RDWR); if (fd1 < 0) { printf("打开设备失败!\n"); return -1; } fd2 = open("/dev/myled2", O_RDWR); if (fd2 < 0) { printf("打开设备失败!\n"); return -1; } fd3 = open("/dev/myled3", O_RDWR); if (fd3 < 0) { printf("打开设备失败!\n"); return -1; } fd4 = open("/dev/myled4", O_RDWR); if (fd4 < 0) { printf("打开设备失败!\n"); return -1; } while(1) { ioctl(fd1, LED_ON); sleep(1); ioctl(fd2, LED_ON); sleep(1); ioctl(fd3, LED_ON); sleep(1); ioctl(fd4, LED_ON); sleep(1); ioctl(fd1, LED_OFF); sleep(1); ioctl(fd2, LED_OFF); sleep(1); ioctl(fd3, LED_OFF); sleep(1); ioctl(fd4, LED_OFF); sleep(1); } //关闭设备 close(fd1); close(fd2); close(fd3); close(fd4); return 0; }
示例运行:
- 加载驱动,分别按照次设备号创建多个设备文件,使用测试程序:
insmod led_drv.ko cat /proc/devices mknod /dev/myled1 c 244 0 mknod /dev/myled2 c 244 1 mknod /dev/myled3 c 244 2 mknod /dev/myled4 c 244 3 ./led_test
Linux混杂设备驱动开发
Linux内核混杂设备特点
混杂设备本质就是字符设备,只是混杂设备的主设备号由内核已经定义好了(默认为10),将来各个混杂设备个体(驱动)通过次设备号来进行区分。
Linux内核描述混杂设备的数据结构
struct miscdevice { int minor; const char *name; const struct file_operations *fops; };
- 成员说明:
- minor:混杂设备对应的次设备号,注意哦:主设备号为10,一般指定为宏MISC_DYNAMIC_MINOR,表示让内核来帮你分配一个次设备号。
- name:就是将来的设备文件名,并且设备文件是自动创建,无需调用四个函数,但是三个保证还是需要滴
- fops:给混杂设备添加的硬件操作接口
- 配套函数:
//向内核注册一个混杂设备对象 //内核会帮你自动创建一个名称为name的设备文件 并且帮你分配一个次设备号 misc_register(&混杂设备对象) //从内核卸载一个混杂设备对象 //内核会帮你删除创建的设备文件 内核会帮你释放申请的次设备号 misc_deregister(&混杂设备对象)
代码示例(gpio-led):
- led_drv.c
#include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> //混杂设备 #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <mach/platform.h> //声明描述LED硬件信息的数据结构 struct led_resource { int gpio; //GPIO编号 char *name; //LED名称 }; //定义初始化LED的硬件信息对象 static struct led_resource led_info[] = { { .name = "LED1", .gpio = PAD_GPIO_C+12 }, { .name = "LED2", .gpio = PAD_GPIO_C+7 }, { .name = "LED3", .gpio = PAD_GPIO_C+11 }, { .name = "LED4", .gpio = PAD_GPIO_B+26 } }; #define LED_ON 0x100001 #define LED_OFF 0x100002 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { //1.分配内核缓冲区 int kindex; //2.拷贝用户缓冲区数据到内核缓冲区 copy_from_user(&kindex, (int *)arg, sizeof(kindex)); //3.解析用户命令 switch(cmd) { case LED_ON: gpio_set_value(led_info[kindex-1].gpio, 0); break; case LED_OFF: gpio_set_value(led_info[kindex-1].gpio, 1); break; default: return -1; } return 0; } //定义初始化硬件操作接口对象 static struct file_operations led_fops = { .owner = THIS_MODULE,//模块的所属者 .unlocked_ioctl = led_ioctl }; //定义初始化混杂设备对象 static struct miscdevice led_misc = { .minor = MISC_DYNAMIC_MINOR, //让内核帮你分配次设备号 .name = "myled",//内核帮你创建设备文件/dev/myled .fops = &led_fops //给混杂设备对象添加硬件操作接口 }; static int led_init(void) { int i; //1.申请GPIO资源,配置为输出,输出1 for(i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_request(led_info[i].gpio, led_info[i].name); gpio_direction_output(led_info[i].gpio, 1); } //2.向内核注册混杂设备对象 misc_register(&led_misc); return 0; } static void led_exit(void) { int i; //1.卸载混杂设备对象 misc_deregister(&led_misc); //2.输出1,释放GPIO资源 for (i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_set_value(led_info[i].gpio, 1); gpio_free(led_info[i].gpio); } } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
- led_test.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #define LED_ON 0x100001 //开灯命令 #define LED_OFF 0x100002 //关灯命令 int main(int argc, char *argv[]) { int fd; int index; //分配用户缓冲区,保存操作灯的编号 if(argc != 3) { printf("用法:%s <on|off> <1|2|3|4>\n", argv[0]); return -1; } //打开设备 fd = open("/dev/myled", O_RDWR); if (fd < 0) { printf("打开设备失败!\n"); return -1; } //"1"->1 index = strtoul(argv[2], NULL, 0); //应用ioctl->软中断->内核sys_ioctl->驱动led_ioctl if(!strcmp(argv[1], "on")) ioctl(fd, LED_ON, &index); else if(!strcmp(argv[1], "off")) ioctl(fd, LED_OFF, &index); //关闭设备 close(fd); return 0; }
- Makefile
obj-m += led_drv.o all: make -C /opt/kernel SUBDIRS=$(PWD) modules clean: make -C /opt/kernel SUBDIRS=$(PWD) cl
执行结果
- 执行
insmod led_drv.ko ls /dev/ ./led_test
- 可以看到文件自动创建了/dev/myled
- 运行测试程序没问题。
- 看创建的设备文件
执行以下命令可以看到申请的主设备号和次设备号
cat /sys/class/misc/myled/dev