今天我們來學習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來操作蜂鳴器。