字符设备驱动模型

  1. 字符设备驱动模型
    1. 1. 字符设备驱动的基本流程
  • linux 设备驱动模型
    1. 什么是设备树 dts(device tree)
      1. platform_device
        1. 设备注册
      2. platform_driver
        1. 驱动注册和卸载
  • 字符设备驱动模型

    1. 字符设备驱动的基本流程

    1. 分配主次设备号:

      • 使用 alloc_chrdev_region(动态分配) 或 register_chrdev_region (静态分配)函数分配设备号。
    2. 注册字符设备:

      2.1 字符设备的初始化

      ​ 使用 cdev_init 函数初始化 cdev 结构体,并关联 file_operations

      2.2 字符设备的添加

      ​ 使用 cdev_add 函数将字符设备添加到内核中。

    3. 创建设备节点:

      • 使用 mknod 命令或 udev 规则创建设备节点。
      • class_createdevice_create用于动态创建设备类和设备节点的函数。
      • **class_create**用于创建一个设备类,设备类通常对应于 /sys/class/ 下的一个目录。
      • device_create在设备类下创建设备节点。备节点会自动出现在 /dev/ 目录下,同时会在 /sys/class/ 下创建相应的属性文件。
    4. 实现文件操作函数:

      • 实现 file_operations 结构体中的函数指针,如 openreleasereadwrite 等。
    5. 注销字符设备:

      • 在模块卸载时,使用 cdev_delunregister_chrdev_region 函数注销字符设备并释放设备号。
    
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/uaccess.h>
    
    #define DEVICE_NAME "my_char_device"
    
    static int major;
    static struct cdev my_cdev;
    static struct class *my_class;
    static struct device *my_device;
    
    static int my_open(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "Device opened\n");
        return 0;
    }
    
    static int my_release(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "Device closed\n");
        return 0;
    }
    
    static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
    {
        printk(KERN_INFO "Read operation\n");
        return 0;
    }
    
    static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
    {
        printk(KERN_INFO "Write operation\n");
        return count;
    }
    
    static struct file_operations fops = {
        .owner = THIS_MODULE,
        .open = my_open,
        .release = my_release,
        .read = my_read,
        .write = my_write,
    };
    
    static int __init my_init(void)
    {
        dev_t dev;
        int ret;
    
        // 动态分配设备号
        ret = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
        if (ret < 0) {
            pr_err("Failed to allocate device number\n");
            return ret;
        }
        
        major = MAJOR(dev);
        
        // 初始化 cdev
        cdev_init(&my_cdev, &fops);
        my_cdev.owner = THIS_MODULE;
        
        // 注册字符设备
        ret = cdev_add(&my_cdev, dev, 1);
        if (ret < 0) {
            pr_err("Failed to add cdev\n");
            unregister_chrdev_region(dev, 1);
            return ret;
        }
        
        // 创建设备类
        my_class = class_create(THIS_MODULE, "my_device_class");
        if (IS_ERR(my_class)) {
            pr_err("Failed to create class\n");
            cdev_del(&my_cdev);
            unregister_chrdev_region(dev, 1);
            return PTR_ERR(my_class);
        }
        
        // 创建设备节点
        my_device = device_create(my_class, NULL, dev, NULL, "my_device");
        if (IS_ERR(my_device)) {
            pr_err("Failed to create device\n");
            class_destroy(my_class);
            cdev_del(&my_cdev);
            unregister_chrdev_region(dev, 1);
            return PTR_ERR(my_device);
        }
        
        printk(KERN_INFO "Device registered with major number %d\n", major);
        return 0;
    
    }
    
    static void __exit my_exit(void)
    {
        dev_t dev = MKDEV(major, 0);
    
        // 销毁设备节点
        device_destroy(my_class, dev);
        
        // 销毁设备类
        class_destroy(my_class);
        
        // 注销字符设备
        cdev_del(&my_cdev);
        unregister_chrdev_region(dev, 1);
        
        printk(KERN_INFO "Device unregistered\n");
    
    }
    
    module_init(my_init);
    module_exit(my_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Your Name");
    MODULE_DESCRIPTION("A simple character device driver using class_create and device_create");
    

    2.Linux设备模型

    linux 设备驱动模型

    设备模型是Linux内核中一个非常重要的概念,很多复杂的驱动(比如platform、USB、I2C),都是以设备模型为基础进行构建的。如果你在阅读驱动源码时感觉很吃力,感觉太复杂,错综复杂,无法真正理解其全景框架和底层的运行逻辑,这里真诚地建议你可以尝试从设备模型学起:设备模型以最核心的kobject和kset数据结构构建了设备树的基本骨架,又通过device、bus、driver、class进一步封装,构建了设备模型的基本能力:总线匹配、电源管理、热插拔机制… 本期课程从设备模型最核心的kobject和kset讲起,全网首创使用OOP思想进行讲解,一步一步讲解内核中设备模型的封装过程,通过实际编程,从零编写一个总线子系统,向大家展示一个内核模块是如何从最基本的功能,慢慢迭代和进化成一个子系统的,总线是如何match的,设备是如何probe的,热插拔事件是如何产生和发送的,我们如何监听和解析这些热插拔事件?设备节点是如何自动生成的?通过本期课程的学习,通过自己编程来实现这些功能,你将真正理解这些底层细节,真实地感受到它是如何一步一步实现和运行的,而不仅仅是停留在脑海中的一个抽象概念。 通过本期课程的学习,你的预期收获如下: 理解kobject、kset、attribute、uevent在设备模型中的作用 掌握sysfs文件系统:注册、挂载、文件的打开读写流程 理解sysfs文件系统和设备模型的关联 掌握驱动中的device、bus、device_driver、class编程接口 真正理解热插拔事件:hotplug/uevent 学会自定义、发送、解析热插拔uevent事件 学会自己编程,实现设备节点的自动创建 学会如何编写总线型(bus)驱动 学会从零实现一个bus子系统 理解udev/mdev在设备模型中的作用

    什么是设备树 dts(device tree)

    设备树(Device Tree)是描述计算机的特定硬件设备信息的数据结构,以便于操作系统的内核可以管理和使用这些硬件,包括CPU或CPU,内存,总线和其他一些外设。dtb文件会被保存到ROM中,最终通过bootbolader被加载到内核,这样内核就可以通过解析设备树来让驱动去控制实际的硬件了。

    (1) dts

    硬件的相应信息都会写在.dts为后缀的文件中,每一款硬件可以单独写一份xxxx.dts,一般在Linux源码中存在大量的dts文件,对于arm架构可以在arch/arm/boot/dts找到相应的dts

    (2) dtc

    dtc是编译dts的工具,可以在Ubuntu系统上通过指令apt-get install device-tree-compiler安装dtc工具,不过在内核源码scripts/dtc路径下已经包含了dtc工具。

    (3) dtb

    dtb(Device Tree Blob),dts经过dtc编译之后会得到dtb文件,dtb通过Bootloader引导程序加载到内核。所以Bootloader需要支持设备树才行;Kernel也需要加入设备树的支持。
    dtb

    platform_device

    struct platform_device
    {
         //设备的名字,用于和驱动进行匹配的`
        const char *name;
        
        //内核中维护的所有的设备必须包含该成员
        struct device    dev;
    
        //资源个数
        u32 num_resources;
    
        //描述资源
        struct resource    * resource;
        ...
    }
    

    设备注册

    platform_device_register(struct platform_device *);

    platform_driver

    struct platform_driver
     {
         //当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中
        int (*probe)(struct platform_device *);  
        
    
        //硬件信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中
        int (*remove)(struct platform_device *);
    
        void (*shutdown)(struct platform_device *);
        int (*suspend)(struct platform_device *, pm_message_t state);
        int (*resume)(struct platform_device *);
    
        //内核维护的所有的驱动必须包含该成员,通常driver->name用于和设备进行匹配
        struct device_driver driver;
    
        //往往一个驱动可能能同时支持多个硬件,这些硬件的名字都放在该结构体数组中
        const struct platform_device_id *id_table;
    
        bool prevent_deferred_probe;
    
     }
    

    驱动注册和卸载

    platform_driver_register(struct platform_driver *);
    platform_driver_unregister(struct platform_driver *);

    platform流程图


    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

    ×

    喜欢就点赞,疼爱就打赏