美文网首页
L18. 实现标准化的字符设备驱动

L18. 实现标准化的字符设备驱动

作者: 拂去尘世尘 | 来源:发表于2020-09-20 19:05 被阅读0次

    1.概述

    在linux系统中许多外围设备都被规定为字符设备,诸如按键、触摸屏、重力传感器、LED、光敏传感器等,这些设备都需要字符设备驱动才能正常工作。本章就来实现一个标准的字符设备驱动框架模板,目的是为以后的设备驱动提供标准模板,提高开发效率与代码整洁度。

    2. 编程思想

    要想实现一个基础的代码模板,需要考虑到代码的标准化、独立性和可重用性。因此在写代码前需要构思一下字符设备驱动常用到哪些功能。这里列举一下常用到的功能,并一一记录实现的流程及意义。

    2.1框架搭建

    在实现字符驱动前,首先要做的是搭建字符设备驱动框架,先将固定的字符设备驱动框架搭建起来,然后再在相应的内容中添加相应的代码即可。这种框架大大减轻了开发者在编程中对代码流程设计的压力,同时为了方便以后的重用,这里将驱动通用的信息全部用driver_case、DRIVER_CASE代替。因此在重用时,只需要全局将driver_case、DRIVER_CASE替换成需要的字符即可。

    #include <linux/of.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/slab.h>
    #include <linux/module.h>
    #include <linux/platform_device.h>
    
    static int driver_case_open(struct inode *inode, struct file *filp)
    {
        return 0;
    }
    
    static ssize_t driver_case_write(struct file *file, const char __user *buf,
                                 size_t size, loff_t *offset)
    {
        return 0;
    }
    
    /* 驱动结构体 */
    static struct file_operations driver_case_fops = {
        .owner = THIS_MODULE,
        .open  = driver_case_open,
        .write = driver_case_write,
    };
    
    static int driver_case_probe(struct platform_device *pdev)
    {
        return 0;
    }
    
    int driver_case_remove(struct platform_device *pdev)
    {
        return 0;
    }
    
    const struct of_device_id driver_case_table[] = {
        {
            .compatible = COMPATABLE_NAME,
        },
        {
        },
    };
    
    static struct platform_driver driver_case_device_driver = {
        .probe  = driver_case_probe,
        .remove = driver_case_remove,
        .driver = {
            .name = PLATFORM_NAME,
            .owner = THIS_MODULE,
            .of_match_table = driver_case_table,
        },
    };
    
    
    static int __init driver_case_init(void)
    {
        platform_driver_register(&driver_case_device_driver);
        
        return 0;
    }
    
    static void __exit driver_case_exit(void)
    {
        platform_driver_unregister(&driver_case_device_driver);
    }
    
    module_exit(driver_case_exit);
    module_init(driver_case_init);
    
    MODULE_LICENSE("GPL");
    

    2.2 开头注释

    一篇标准的代码头部需要注释,这些注释主要内容包括公司信息、文件名、作者、版本、描述、修改日志以及其他信息等。

    /*
    ********************************************************************************
    * Copyright (C),1999-2020, Jimi IoT Co., Ltd.
    * File Name   :   driver_case.c
    * Author      :   dongxiang
    * Version     :   V1.0
    * Description :   General driver template, if wanting to use it, you can use 
    *                 global case matching to replace DRIVER_CASE and driver_case 
    *                 with your custom driver name.
    * Journal     :   2020-05-09 init v1.0 by dongxiang
    * Others      :   
    ********************************************************************************
    */
    

    2.3 LOG打印封装

    在驱动调试的时候,经常会使用打印函数printk,但是不同的调试需要打印不同的固定信息。如果只是用printk来实现,代码会显得会乱,且后期难于屏蔽log。因此我们可以针对不同的打印,对printk进行定制化封装。
    这里列举三个封装实例: PRINT_ERR用来打印报错log;PRINT_INFO用来打印正常log;PRINT_DEBUG用来打印调试log。可以看到PRINT_DEBUG前有DEBUG_LOG_SUPPORT宏,当注释掉宏时,PRINT_DEBUG便不会打印调试log,方便后期屏蔽调试log使用。

    #define DEBUG_LOG_SUPPORT
    
    #define PRINT_ERR(format,x...)    \
    do{ printk(KERN_ERR "ERROR: func: %s line: %04d info: " format,          \
                                     __func__, __LINE__, ## x); }while(0)
    #define PRINT_INFO(format,x...)   \
    do{ printk(KERN_INFO "[driver_case]" format, ## x); }while(0)
    
    #ifdef DEBUG_LOG_SUPPORT
    #define PRINT_DEBUG(format,x...)  \
    do{ printk(KERN_INFO "[driver_case] func: %s line: %d info: " format,    \
                                     __func__, __LINE__, ## x); }while(0)
    #else
    #define PRINT_DEBUG(format,x...)  
    #endif
    

    2.4 结构体封装

    在驱动编程中,要有面向对象编程思想。当需要定义一个驱动各种信息时,可以将所有相关的信息集合成一个结构体,各种类型信息定义在其结构体下的成员。这样不仅方便编程,而且能够快速理清各个信息变量之间的关系。
    如下图,driver_case_dev 表示字符驱动常用到的信息类型, driver_case_platform_data 表示需要从设备树获取的数据。其中driver_case_dev 包含driver_case_platform_data。

    struct driver_case_platform_data {
        int    gpio_num;
    };
    
    struct driver_case_dev {
        int    major;
        dev_t  devid;
        struct cdev cdev;
        struct class *class;
        struct device *device;
        struct driver_case_platform_data *platform_data;
    };
    

    2.5 功能模块封装

    驱动编程的入口函数中需要实现许多初始化工作,这些工作都大同小异,如果放到主干中不仅影响代码的可阅读性,同样影响代码的重用性。因此在编程过程中,针对实现特定功能的代码,需要将其模块化封装起来,只将模块入口放入主干之中。
    这里就列举出,在字符设备驱动编程中,probe函数中要实现设备树数据的获取以及字符驱动接口的注册,将其一一封装。
    字符驱动接口注册模块:

    static int register_driver(void)
    {
        PRINT_INFO("Entry %s \n", __func__);
        
        /* 1. 设置设备号 
         * 主设备号已知, 静态注册;未知, 动态注册。
         */
    #ifdef DEV_MAJOR
            driver_case.devid = MKDEV(DEV_MAJOR, 0);
            register_chrdev_region(driver_case.devid, DRIVER_CASE_NUM, DRIVER_CASE_NAME);
    #else    
            alloc_chrdev_region(&driver_case.devid, 0, DRIVER_CASE_NUM, DRIVER_CASE_NAME);
            driver_case.major = MAJOR(driver_case.devid);    
    #endif
    
        /* 2. 注册驱动结构体 */
        driver_case.cdev.owner = THIS_MODULE;
        cdev_init(&driver_case.cdev, &driver_case_fops);
        cdev_add(&driver_case.cdev, driver_case.devid, DRIVER_CASE_NUM);
        PRINT_DEBUG("driver_case_fops succesful! \n");
        
        /* 3. 创建类 */
        driver_case.class = class_create(THIS_MODULE, DRIVER_CASE_CLASS_NAME);    
        if(IS_ERR(driver_case.class)) {
            PRINT_ERR("%s under class created failed! \n", DRIVER_CASE_DEVICE_NAME);
            
            return ERROR;
        }
        
        /* 4.创建设备 */
        driver_case.device = device_create(driver_case.class, NULL, 
                                        driver_case.devid, NULL, DRIVER_CASE_DEVICE_NAME);
        if(NULL == driver_case.device) {   
            PRINT_ERR("%s device created failed! \n", DRIVER_CASE_DEVICE_NAME); 
            return ERROR;    
        }
    
        return OK;
    }
    

    设备树数据获取模块:

    static struct driver_case_platform_data  *driver_case_parse_dt(struct device *pdev)
    {
        struct driver_case_platform_data *pdata;
        PRINT_INFO("Entry %s \n", __func__);
        
        pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
        if (!pdata) {
            PRINT_ERR("could not allocate memory for platform data\n");
    
            return NULL;
        }
    
        return pdata;
    }
    

    3.测试

    以上内容基本上实现了字符驱动需要的常用功能,这里编译并烧录到开发板,测试一下是否能够跑通;这里测试效果主要看,在应用层调用后,能否打印出从设备树获取的节点数据。

    设备树代码:

        driver_case {
            compatible = "dx, driver_case";
            gpio_num = <24>;
            label = "driver_case";0
            status = "okay";
        };
    

    测试代码修改: 只需要将获取到的设备节点值打印出来即可。因此对driver_case_parse_dt 稍加修改

    static struct driver_case_platform_data  *driver_case_parse_dt (
                                                  struct device *pdev)
    {
        struct driver_case_platform_data *pdata;
        struct device_node *np = pdev->of_node;
        const char *str;
    
        PRINT_INFO("Entry %s \n", __func__);
        
        pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
        if (!pdata) {
            PRINT_ERR("Could not allocate memory for platform data \n");
    
            return NULL;
        }
     
        if(of_property_read_u32(np, "gpio_num", &pdata->gpio_nums) < 0){
            PRINT_ERR("Get gpio_num from device tree failed! \n");
    
            return NULL;
        }    
        PRINT_DEBUG("gpio_num: %d \n", pdata->gpio_nums);
     
        if(of_property_read_string(np, "label", 
                                   &str) < 0) {
        
            PRINT_ERR("Get label from device tree failed! \n");
    
            return NULL;
        }
        memcpy(pdata->label, str, strlen(str));
        PRINT_DEBUG("label1: %s \n", pdata->label);
    
        return pdata;
    }
    

    效果图:

    效果图.png

    4.总结

    本次文章主要介绍如何创建一个可重用的字符设备驱动代码模板。虽然看上去代码很少,但是也是经常一个多星期的推敲以及优化。再全局替换driver_case和DRIVER_CASE后,即可成为一个新的字符驱动,没有保留之前的痕迹。如此一来,以后的代码都可以采用此模板。

    代码链接: https://github.com/LinuxTaoist/Linux_drivers/blob/master/driver_case/2.0/driver_case_test.c

    5.后记

    本博客主要记录笔者在开发中的一些小总结,包括Linux驱动开发、单片机开发、C语言以及安卓驱动开发。如有技术交流需要,欢迎关注 公众号: “开源519”

    相关文章

      网友评论

          本文标题:L18. 实现标准化的字符设备驱动

          本文链接:https://www.haomeiwen.com/subject/nphyyktx.html