Linux驱动开发十五.MISC驱动使用
今天我们来学习platform下单一个杂项驱动——MISC驱动。如果我们的某些外设不好进行分类的时候,就可以把它归纳到这个杂项驱动的分类里。
MISC设备驱动
随着我们使用的设备越来越多,现有的设备号资源变得越来越紧张,特别是主设备号(12位,对应0~4095)。这时候MISC驱动就应运而生了。并且MISC驱动还有个非常强大的优势,就是不用我们自己手动创建节点,MISC驱动简化了字符驱动的编写过程,祝需要向Linux注册一个miscdevice设备,就会自动在/dev下创建新的设备节点。
MISC设备
使用MISC驱动前应该先定义一个MISC设备,这个设备是用一个叫miscdevice的结构体描述的(include/linux/miscdevice.h)。
1 struct miscdevice { 2 int minor; 3 const char *name; 4 const struct file_operations *fops; 5 struct list_head list; 6 struct device *parent; 7 struct device *this_device; 8 const struct attribute_group **groups; 9 const char *nodename; 10 umode_t mode; 11 };
成员minor就是我们需要定义的硬件的从设备号,这个设备号在miscdevice.h里面有很多宏供我们使用
1 /* 2 * These allocations are managed by device@lanana.org. If you use an 3 * entry that is not in assigned your entry may well be moved and 4 * reassigned, or set dynamic if a fixed value is not justified. 5 */ 6 7 #define PSMOUSE_MINOR 1 8 #define MS_BUSMOUSE_MINOR 2 /* unused */ 9 #define ATIXL_BUSMOUSE_MINOR 3 /* unused */ 10 /*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */ 11 #define ATARIMOUSE_MINOR 5 /* unused */ 12 #define SUN_MOUSE_MINOR 6 /* unused */ 13 #define APOLLO_MOUSE_MINOR 7 /* unused */ 14 #define PC110PAD_MINOR 9 /* unused */ 15 /*#define ADB_MOUSE_MINOR 10 FIXME OBSOLETE */ 16 #define WATCHDOG_MINOR 130 /* Watchdog timer */ 17 #define TEMP_MINOR 131 /* Temperature Sensor */ 18 #define RTC_MINOR 135 19 #define EFI_RTC_MINOR 136 /* EFI Time services */ 20 #define VHCI_MINOR 137 21 #define SUN_OPENPROM_MINOR 139 22 #define DMAPI_MINOR 140 /* unused */ 23 #define NVRAM_MINOR 144 24 #define SGI_MMTIMER 153 25 #define STORE_QUEUE_MINOR 155 /* unused */ 26 #define I2O_MINOR 166 27 #define MICROCODE_MINOR 184 28 #define VFIO_MINOR 196 29 #define TUN_MINOR 200 30 #define CUSE_MINOR 203 31 #define MWAVE_MINOR 219 /* ACP/Mwave Modem */ 32 #define MPT_MINOR 220 33 #define MPT2SAS_MINOR 221 34 #define MPT3SAS_MINOR 222 35 #define UINPUT_MINOR 223 36 #define MISC_MCELOG_MINOR 227 37 #define HPET_MINOR 228 38 #define FUSE_MINOR 229 39 #define KVM_MINOR 232 40 #define BTRFS_MINOR 234 41 #define AUTOFS_MINOR 235 42 #define MAPPER_CTRL_MINOR 236 43 #define LOOP_CTRL_MINOR 237 44 #define VHOST_NET_MINOR 238 45 #define UHID_MINOR 239 46 #define MISC_DYNAMIC_MINOR 255
可以注意下最后一行,如果指定成MISC_DYNAMIC_MINOR就是动态分配从设备号,内核会根据实际情况去对我们新添加到设备分配一个从设备号。
name是新设备的名字,在设备注册成功后会在/dev路径下新建一个名字为name值的设备节点。
fops就是文件操作集合,就和我们前面写字符驱动时候的文件操作集合是一样的功能。对应的被操作文件就是/dev路径下的新设备节点。
设备注册和卸载
misc设备注册和卸载可以直接使用下面的函数
extern int misc_register(struct miscdevice *misc); extern int misc_deregister(struct miscdevice *misc);
使用的时候一定要注意红色加粗的那个deregister,前面我们用的卸载都是unregister,这两个是不同的。
还记得我们在前面注册设备的时候是怎么生成/dev下的设备节点的么?
1 alloc_chrdev_region(); /* 申请设备号 */ 2 cdev_init(); /* 初始化 cdev */ 3 cdev_add(); /* 添加 cdev */ 4 class_create(); /* 创建类 */ 5 device_create(); /* 创建设备 */
在执行力misc_register函数以后,内核会直接帮我们完成上面的步骤,同样在执行了misc_deregister函数以后内核也会执行下面的流程释放相应资源
cdev_del(); /* 删除 cdev */ unregister_chrdev_region(); /* 注销设备号 */ device_destroy(); /* 删除设备 */ class_destroy(); /* 删除类 */
是不是简单的多了!
misc驱动实际使用
因为LED在GPIO下一直没法点亮,这里还用蜂鸣器来演示misc驱动的使用方法。这个驱动的构建和正点原子的教程上有点点的区别。我把miscdevice的结构体嵌入到顶层的设备了,便于后期哪个函数需要传递参数时好用。
设备树信息
因为misc驱动还是属于platform驱动下的,所以为了简化使用还是要在设备树下创建一个新的节点(这里就用前面那个蜂鸣器的节点信息)
/*gpio蜂鸣器节点*/ beep{ compatible = "alientek,beep"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpiobeep>; beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>; status = "okay"; };
beep还是在根节点下的,pinctrl的配置也是在讲GPIO的时候讲过了。compatible的属性在驱动里哪个match的table里能对应上就行了。
驱动框架
先把驱动的大致框架列出来
1 #define MISCBEEP_NAME "miscbeep" 2 #define MISCBEEP_MINOR 144 3 4 /*platform 设备树匹配table*/ 5 static const struct of_device_id beep_of_match[] = { 6 {.compatible = "alientek,beep"}, //设备树中设备compatible属性值 7 {}, 8 }; 9 10 /*设备结构体*/ 11 struct beep_dev{ 12 struct device_node *nd; //设备节点 13 int beep_gpio; //gpio值 14 struct miscdevice *misc_dev; //misc设备结构体指针 15 }; 16 17 struct beep_dev beep; 18 19 //设备文件open操作 20 static int beep_open(struct inode *inode ,struct file *filp) 21 { 22 filp->private_data = &beep; //私有变量 23 return 0; 24 } 25 26 //设备文件写操作 27 static ssize_t beep_write(struct file *filp,const char __user *buf, 28 size_t count,loff_t *ppos) 29 { 30 return 0; 31 } 32 33 //设备文件关闭 34 static int beep_release(struct inode *inode ,struct file *filp){ 35 return 0; 36 } 37 38 //设备文件操作集合 39 static struct file_operations beep_fops = { 40 .owner = THIS_MODULE, 41 .open = beep_open, 42 .write = beep_write, 43 .release = beep_release, 44 }; 45 46 //misc设备 47 static struct miscdevice beep_misc = { 48 .minor = MISCBEEP_MINOR, 49 .name = MISCBEEP_NAME, 50 .fops = &beep_fops, 51 }; 52 53 //probe函数 54 static int beep_probe(struct platform_device *p_dev) 55 { 56 int ret = 0; 57 printk("beep_probe match\r\n"); 58 59 //1.初始化IO 60 beep.nd = p_dev->dev.of_node; //获取设备节点 61 beep.beep_gpio = of_get_named_gpio(beep.nd,"beep-gpios",0); //获取设备节点中GPIO属性 62 beep.misc_dev = &beep_misc; //设备misc设备指向实例化后的对象 63 64 if(beep.beep_gpio<0){ 65 ret = -EINVAL; 66 goto fail_findgpio; 67 } 68 69 ret = gpio_request(beep.beep_gpio,"beep-gpio"); //GPIO资源申请 70 if(ret){ 71 printk("can't request gpio\r\n"); 72 ret = -EINVAL; 73 goto fail_findgpio; 74 } 75 76 ret = gpio_direction_output(beep.beep_gpio,1); //GPIO输出模式设置 77 if(ret<0){ 78 goto fail_gpioset; 79 } 80 81 //2.misc驱动注册 82 ret = misc_register(beep.misc_dev); //注册misc设备驱动 83 if(ret<0){ 84 goto fail_gpioset; 85 } 86 return 0; 87 88 fail_gpioset: 89 gpio_free(beep.beep_gpio); 90 fail_findgpio: 91 return ret; 92 } 93 94 static int beep_remove(struct platform_device *p_dev) 95 { 96 gpio_set_value(beep.beep_gpio,1); //关闭GPIO设备 97 gpio_free(beep.beep_gpio); //释放GPIO资源 98 misc_deregister(beep.misc_dev); //卸载misc设备 99 return 0; 100 } 101 102 /*platform驱动结构体*/ 103 static struct platform_driver platform_beep_driver = 104 { 105 .driver = { 106 .name = "imx6ul-beep", 107 .of_match_table = beep_of_match, 108 }, 109 .probe = beep_probe, 110 .remove = beep_remove, 111 }; 112 113 static int __init beep_init(void) 114 { 115 return platform_driver_register(&platform_beep_driver); 116 } 117 118 static void __exit beep_exit(void) 119 { 120 platform_driver_unregister(&platform_beep_driver); 121 } 122 123 module_init(beep_init); 124 module_exit(beep_exit); 125 MODULE_LICENSE("GPL"); 126 MODULE_AUTHOR("ZZQ");
我直接在设备结构体里定义了misc的设备结构指针,这样在后面的probe函数中是可以直接用p_dev里的指针指向这个结构体,不用全局变量可以让效率高一些。这个驱动架构可以从后往前分析:
最后是platform_driver的加载和卸载,对应的第103行的platform_driver对象platform_beep_driver。platform_driver里主要是定义的probe和remove两个函数,还有of_match_table,用来从设备树里匹配设备。设备的定义在第5~8行,虽然就一个设备,但是最后要留一个空的内容(第7行)。
在probe函数里的主要内容已经注释掉很清楚了。主要就是初始化IO和注册misc设备。因为misc设备在设备结构体里是个指针,要在62行进行指针传递。并且在注册misc设备的时候并没有加取址符&。
misc设备的定义在47~51行,在里面指定了从设备号、设备名称然后绑定了文件操作集合。
remove函数里就是要先释放GPIO资源,最后通过misc设备卸载释放了相关的资源,对了,在释放GPIO资源前一定要先把GPIO输出值拉高关闭蜂鸣器。
剩下的open、write和release就没什么好说的了。主要就是write从用户态程序获取了数据,根据数据打开或关闭外设。
最后放出完整的代码
/** * @file miscbeep.c * @author your name (you@domain.com) * @brief misc驱动试验(beep驱动) * @version 0.1 * @date 2022-08-21 * * @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/irq.h> #include <linux/interrupt.h> #include <linux/fcntl.h> #include <linux/ide.h> #include <linux/platform_device.h> #include <linux/miscdevice.h> #define MISCBEEP_NAME "miscbeep" #define MISCBEEP_MINOR 144 /*platform 设备树匹配table*/ static const struct of_device_id beep_of_match[] = { {.compatible = "alientek,beep"}, //设备树中设备compatible属性值 {}, }; /*设备结构体*/ struct beep_dev{ struct device_node *nd; //设备节点 int beep_gpio; //gpio值 struct miscdevice *misc_dev; //misc设备结构体指针 }; struct beep_dev beep; //设备文件open操作 static int beep_open(struct inode *inode ,struct file *filp) { filp->private_data = &beep; //私有变量 return 0; } //设备文件写操作 static ssize_t beep_write(struct file *filp,const char __user *buf, size_t count,loff_t *ppos) { int ret; unsigned char databuf[1]; struct beep_dev *dev = filp->private_data; ret = copy_from_user(databuf,buf,count); if(databuf[0] == 1){ gpio_set_value(dev->beep_gpio,0); } else{ gpio_set_value(dev->beep_gpio,1); } return 0; } //设备文件关闭 static int beep_release(struct inode *inode ,struct file *filp){ return 0; } //设备文件操作集合 static struct file_operations beep_fops = { .owner = THIS_MODULE, .open = beep_open, .write = beep_write, .release = beep_release, }; //misc设备 static struct miscdevice beep_misc = { .minor = MISCBEEP_MINOR, .name = MISCBEEP_NAME, .fops = &beep_fops, }; //probe函数 static int beep_probe(struct platform_device *p_dev) { int ret = 0; printk("beep_probe match\r\n"); //1.初始化IO beep.nd = p_dev->dev.of_node; //获取设备节点 beep.beep_gpio = of_get_named_gpio(beep.nd,"beep-gpios",0); //获取设备节点中GPIO属性 beep.misc_dev = &beep_misc; //设备misc设备指向实例化后的对象 if(beep.beep_gpio<0){ ret = -EINVAL; goto fail_findgpio; } ret = gpio_request(beep.beep_gpio,"beep-gpio"); //GPIO资源申请 if(ret){ printk("can't request gpio\r\n"); ret = -EINVAL; goto fail_findgpio; } ret = gpio_direction_output(beep.beep_gpio,1); //GPIO输出模式设置 if(ret<0){ goto fail_gpioset; } //2.misc驱动注册 ret = misc_register(beep.misc_dev); //注册misc设备驱动 if(ret<0){ goto fail_gpioset; } return 0; fail_gpioset: gpio_free(beep.beep_gpio); fail_findgpio: return ret; } static int beep_remove(struct platform_device *p_dev) { gpio_set_value(beep.beep_gpio,1); //关闭GPIO设备 gpio_free(beep.beep_gpio); //释放GPIO资源 misc_deregister(beep.misc_dev); //卸载misc设备 return 0; } /*platform驱动结构体*/ static struct platform_driver platform_beep_driver = { .driver = { .name = "imx6ul-beep", .of_match_table = beep_of_match, }, .probe = beep_probe, .remove = beep_remove, }; static int __init beep_init(void) { return platform_driver_register(&platform_beep_driver); } static void __exit beep_exit(void) { platform_driver_unregister(&platform_beep_driver); } module_init(beep_init); module_exit(beep_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZZQ");
make以后加载设备,可以在/dev下看到我们的新设备
打印一下,可以看到主次设备号
红线画出的就是主设备号10,蓝色的144是我们指定的次设备号
然后再/sys/class/misc下可以看到我们新添加到设备
我们可以直接用前面的测试led的APP直接向/dev下的miscbeep写入0或1来操作蜂鸣器。