• 嵌入式Linux 开发经验:注册一个 misc 设备


    前言

    • 之前买过好几本Linux 设备驱动的书,不过对设备驱动一知半解,什么叫设备,什么又叫驱动?最近工作需要,从源码级别深入的研究了一下 Linux 下的设备与驱动的概念,略有所收获

    • 一般提起驱动开发,都是面向硬件的,至少是底层开发,依赖具体的平台,Linux 作为一个通用的操作系统内核,当前不可能顾及所以的具体外设驱动,只能抽取出驱动的共性,抽象出一个设备驱动模型(框架)出来,这个设备模型从上层看,无论设备多么简单或者复杂,把共性的设备操作,如:注册、反注册、打开、关闭、控制、读写等 封装成 总线、驱动、设备,与设备树配合起来,为上层提供通用的设备操作接口,如文件接口、socket 接口,为底层设备驱动开发提供便利,降低驱动开发的难度。

    • 其实就是抽象与分层,让驱动开发就像是【填空题】,照着模板填空补充,就可以开发具体的设备驱动了。设备驱动的目的,就是驱使设备工作起来,可以让上层应用操作。

    测试环境搭建

    • ubuntu 20.04

    • VMware Workstation Pro 16

    • 基于qemu(模拟器),vexpress-a9 平台

    • Linux 6.0.10 (当前最新版本)

    • 注册一个简单的misc 设备,掌握misc 设备注册的方法

    misc 设备是什么设备

    • 大家经常提到的是 三大类设备:【字符设备】、【块设备】、【网络设备】。

    • misc 设备是什么呢?为何使用 misc 设备?

    • 如今Linux 设备驱动非常的庞大,所以当前接触的一些外设,都有类似的驱动模型,misc (杂类设备)属于 char 字符设备。

    • 使用 misc 设备的好处就是 Linux 提供了完善的 misc 设备管理,使用 misc 设备提供的API,就可以方便的注册管理 一个 misc 具体设备,使用 misc 设备最核心的 一般是使用 open、close、ioctl 接口,这些接口,可以让用户太的应用操作设备。

    • 注册了一个 misc 设备,如 led0,用户态程序通过 open("/dev/led0", O_RDWR),就可以打开内核驱动misc 设备,通过 ioctl 就可以控制 内核驱动 misc 设备。

    • 也就有一些设备,不是直接读写的,大部分操作都是控制命令,如空调的控制,一般有打开空调、关闭空调、调节空调的温度、模式等操作,可以把空调作为misc 设备来控制,打开关闭使用 open close,调节温度、模式等使用 ioctl。

    注册 misc 设备

    • 这里就注意一点: 驱动与设备的概念,这里 misc 属于设备。

    • 这里为了方便,注册一个简单的设备,与上一篇 嵌入式Linux 开发经验:platform_driver_register 的使用方法 平台驱动 配合起来,当 平台驱动匹配设备树节点成功后,再初始化 misc 设备。

    • 新建 linux-6.0.10/drivers/led_control/led_misc.c

    #include "led_misc.h"
    
    #define LED_MISC_DEVICE_NAME        "led_misc"
    
    struct led_misc_dev
    {
    	struct miscdevice misc;
    };
    
    struct led_misc_dev *led_miscdev;
    
    /* 打开设备,用户态执行 open 命令,就会走到这里 */
    static int led_misc_open(struct inode *inode, struct file *filp)
    {
        printk(KERN_INFO "%s : enter\n", __func__);
        return 0;
    }
    
    /* 打开设备,用户态执行 close 命令,就会走到这里 */
    static int led_misc_close(struct inode *inode, struct file *filp)
    {
        printk(KERN_INFO "%s : enter\n", __func__);
        return 0;
    }
    
    /* 内存映射,大部分功能都差不多 */
    static int led_misc_mmap(struct file *filp, struct vm_area_struct *vma)
    {
        int ret = 0;
    
        if (filp == NULL)
        {
            printk(KERN_ERR "invalid file!");
            return -EFAULT;
        }
        if (vma == NULL)
        {
            printk(KERN_ERR "invalid vma area");
            return -EFAULT;
        }
    
        ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
            vma->vm_end - vma->vm_start, vma->vm_page_prot);
    
        printk(KERN_INFO "%s : ret = %d\n", __func__, ret);
    
        return ret;
    }
    
    /* 设备控制类,用户态执行 ioctl 命令,就会走到这里 */
    static long led_misc_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
    {
        printk(KERN_INFO "%s : enter\n", __func__);
        return 0;
    }
    
    /* 设备的操作,用户态 应用通过【文件】操作接口操作 */
    static const struct file_operations led_misc_fops =
    {
        .owner  = THIS_MODULE,
        .llseek = no_llseek,
        .unlocked_ioctl = led_misc_ioctl,
    #ifdef CONFIG_COMPAT
        .compat_ioctl = led_misc_ioctl,
    #endif
        .mmap = led_misc_mmap,
        .open = led_misc_open,
        .release = led_misc_close,
    };
    
    /* 注意这个 初始化不是自动初始化,放在 平台驱动 probe 函数  */
    int led_miscdev_init(void)
    {
        int ret;
    
        led_miscdev = kzalloc(sizeof(*led_miscdev), GFP_KERNEL);
        if (!led_miscdev)
            return -ENOMEM;
    
        led_miscdev->misc.minor = MISC_DYNAMIC_MINOR;
        led_miscdev->misc.fops = &led_misc_fops;
        led_miscdev->misc.name = LED_MISC_DEVICE_NAME;
        led_miscdev->misc.nodename = LED_MISC_DEVICE_NAME;
    
        ret = misc_register(&led_miscdev->misc);
        if (ret < 0)
        {
            printk(KERN_INFO "%s : error\n", __func__);
        }
        else
        {
            printk(KERN_INFO "%s : ok\n", __func__);
        }
    
        return ret;
    }
    
    
    /* 可以放在 平台驱动 remove 函数  */
    void led_miscdev_exit(void)
    {
        misc_deregister(&led_miscdev->misc);
        printk(KERN_INFO "%s : ok\n", __func__);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 新建 linux-6.0.10/drivers/led_control/led_misc.h
    #ifndef __LED_MISC_H__
    #define __LED_MISC_H__
    
    #include 
    #include 
    #include 
    
    int led_miscdev_init(void);
    void led_miscdev_exit(void);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其他修改

    • 上面是 注册 misc 设备的实现,但是需要调用才能执行,配合 平台驱动, led_miscdev_init 放在 平台驱动的 probe 函数中,led_miscdev_exit 可以放在 平台驱动 remove 函数,也可以放在 平台驱动 module_exit 的执行函数中

    • 修改 linux-6.0.10/drivers/led_control/led_control.c

    static int led_control_probe(struct platform_device *pdev)
    {
    	printk(KERN_INFO "%s : enter\n", __func__);
    	led_miscdev_init();  /* 设备树节点匹配后,调用 */
    	return 0;
    }
    
    static int led_control_remove(struct platform_device *pdev)
    {
    	printk(KERN_INFO "%s : enter\n", __func__);
    	//led_miscdev_exit();  /* 移除设备驱动时,释放 misc 设备  */
    	return 0;
    }
    
    static void __exit led_control_driver_exit(void)
    {
    	printk(KERN_INFO "%s : enter\n", __func__);
    	led_miscdev_exit();  /* 移除设备驱动时,释放 misc 设备  */
    	platform_driver_unregister(&led_control_driver);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 修改 linux-6.0.10/drivers/led_control/led_control.h,添加 #include "led_misc.h"
    #ifndef __LED_CONTROL_H__
    #define __LED_CONTROL_H__
    
    #include 
    #include 
    
    #include "led_misc.h"
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 修改 linux-6.0.10/drivers/led_control/Makefile,增加
    obj-$(CONFIG_LED_CONTROL) += led_control.o
    obj-$(CONFIG_LED_CONTROL) += led_misc.o
    
    • 1
    • 2

    编译与运行

    #!/bin/bash
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- $1 $2 $3
    
    • 1
    • 2
    • chmod +x mk.sh 增加执行权限

    • 编译时: ./mk.sh -j4 就可以编译了

    在这里插入图片描述

    • 更新 zImge Linux 内核编译的产物,开启 qemu 查看注册的 misc 设备

    • 启动 qemu 的信息包括 如下:

    led_control_driver_init : enter
    led_control_probe : enter
    led_miscdev_init : ok
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    • 说明设备树节点匹配后,正确调用了 misc 设备的初始化函数 led_miscdev_init

    • Linux shell 查看 注册的 misc 设备

    ls /sys/class/misc/ -la, 可以查看 ,注意在 /sys/class/misc/ 目录下

    在这里插入图片描述

    • 注册 misc 设备成功了,接下来可以编写 用户态的应用,通过文件操作接口,如 open close ioctl 来控制这个 内核 misc 设备了

    小结

    • 本篇与上一篇 平台驱动配合,记录了一下 平台驱动+ misc 设备的操作流程,部分简单的设备,可以利用Linux 设备驱动框架提供的便利,想填空题一样快速开发构建自己的实际的设备驱动。

    • 使用 Linux 设备种类大概有三种,不过细分, misc 属于 char 字符设备,当前还有各种形形色色的功能不同的设备。 Misc 设备属于比较常用的简单的控制类设备

  • 相关阅读:
    C++命名空间详解
    什么是大数据测试?有哪些类型?应该怎么测?
    vue 自动生成面包屑导航
    java——mybatis——Mybatis注解开发——@Update——修改数据
    SpirngBoot<读完包你更上一层楼>
    SpringBoot(基础篇 ==> 框架介绍、创建方式
    Ubuntu LTS 坚持 10 年更新不动摇
    今天的码农女孩学习了关于jQuery遍历节点、查询节点以及插件的知识
    算法面试高频题解指南【一】
    10-QNX与Android双系统通讯之FDBUS(1)
  • 原文地址:https://blog.csdn.net/tcjy1000/article/details/128158812