前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >标准字符设备驱动模板

标准字符设备驱动模板

作者头像
开源519
发布2020-09-22 10:01:40
9790
发布2020-09-22 10:01:40
举报
文章被收录于专栏:开源519开源519

1.概述

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

2. 编程思想

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

2.1框架搭建

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

代码语言:javascript
复制
#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 开头注释

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

代码语言:javascript
复制
/*
********************************************************************************
* 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使用。

代码语言:javascript
复制
#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。

代码语言:javascript
复制
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函数中要实现设备树数据的获取以及字符驱动接口的注册,将其一一封装。 字符驱动接口注册模块:

代码语言:javascript
复制
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;
}

设备树数据获取模块:

代码语言:javascript
复制
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.测试

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

设备树代码:

代码语言:javascript
复制
    driver_case {
        compatible = "dx, driver_case";
        gpio_num = <24>;
        label = "driver_case";0
        status = "okay";
    };

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

代码语言:javascript
复制
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;
}

效果图:

4.总结

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源519 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.概述
  • 2. 编程思想
    • 2.1框架搭建
      • 2.2 开头注释
        • 2.3 LOG打印封装
          • 2.4 结构体封装
            • 2.5 功能模块封装
            • 3.测试
            • 4.总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档