天天看點

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 [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從使用者态程式擷取了資料,根據資料打開或關閉外設。

最後放出完整的代碼

Linux驅動開發十五.MISC驅動使用
Linux驅動開發十五.MISC驅動使用
/**
 * @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下看到我們的新裝置

Linux驅動開發十五.MISC驅動使用

列印一下,可以看到主次裝置号

Linux驅動開發十五.MISC驅動使用

紅線畫出的就是主裝置号10,藍色的144是我們指定的次裝置号 

 然後再/sys/class/misc下可以看到我們新添加到裝置

Linux驅動開發十五.MISC驅動使用

我們可以直接用前面的測試led的APP直接向/dev下的miscbeep寫入0或1來操作蜂鳴器。