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");
misc驱动演示

make以后加载设备,可以在/dev下看到我们的新设备

打印一下,可以看到主次设备号

 

红线画出的就是主设备号10,蓝色的144是我们指定的次设备号 

 

 然后再/sys/class/misc下可以看到我们新添加到设备

 

我们可以直接用前面的测试led的APP直接向/dev下的miscbeep写入0或1来操作蜂鸣器。

posted @ 2022-08-21 22:18  银色的音色  阅读(542)  评论(0编辑  收藏  举报