今天我们来学习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 [email protected]. 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 ([email protected])
* @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");
misc驱动演示
make以后加载设备,可以在/dev下看到我们的新设备
打印一下,可以看到主次设备号
红线画出的就是主设备号10,蓝色的144是我们指定的次设备号
然后再/sys/class/misc下可以看到我们新添加到设备
我们可以直接用前面的测试led的APP直接向/dev下的miscbeep写入0或1来操作蜂鸣器。