前言

5. 分离分层

本章节记录实现LED驱动的大概步骤,且编程框架实现分离分层。

分离分层:

  • 上层:系统 相关。如模块注册于注销。

  • 下层:硬件操作。如提供 file_operations 。分离

    • 设备。提供板卡信息,如使用哪一个引脚。
    • 驱动。引脚的具体操作。
  • 以下以 LED 为例。

5.1 回顾-设备驱动实现

步骤

  • 模块

    • 入口函数
    • 出口函数
    • 协议
  • 驱动

    • 驱动代码:实现 file_operations
    • 申请设备号
    • 初始化内核设备文件结构体+绑定驱动代码 file_operations
    • 添加内核设备文件结构体到内核+绑定设备号
    • 创建设备类
    • 创建设备节点+绑定设备号
  • 具体实现参考《linux-驱动-3-字符设备驱动》或往下看。

5.2 分离分层

把一个字符设备驱动工程分层分离。(看章前分析

得出以下目录树:

dev_drv
|__ xxx_module.c
|__ include
| |__ xxx_resource.h
|__ device
| |__ xxx_dev_a.c
| |__ xxx_dev_a.h
|__ driver
|__ xxx_drv.c
|__ xxx_drv.h

目录树分析:

  • dev_drv:字符设备模块目录

    • xxx_module.c:上层。系统。用于注册、注销模块,及操作驱动与内核的联系部分。
    • include:系统、设备、驱动共用的自定义头文件。
      • xxx_resource.h:资源文件。包含了设备资源传给驱动文件的结构体。
    • device:设备目录。硬件设备内容,提供给驱动文件使用,即是提供资源。
      • xxx_dev_a.c:板卡a。以规定的格式提供硬件资源。
      • xxx_dev_a.h:板卡a。头文件。
    • driver:驱动目录。实现驱动 file_operations 的目录。
      • xxx_drv.c:驱动。实现驱动内容。
      • xxx_drv.c:驱动。头文件。

5.3 设备

主要内容:

  • 提供设备资源;
  • 提供获取设备资源接口。

现在设备资源格式文件中第一好格式:

  • 设备资源:(led_resource.h)
/* led 资源结构体 */
struct LED_RESOURCE_T
{
    unsigned long pa_dr; // 数据寄存器  物理地址
    unsigned long pa_gdir; // 输入输出寄存器  物理地址
    unsigned long pa_iomuxc_mux; // 端口复用寄存器  物理地址
    unsigned long pa_ccm_ccgrx; // 端口时钟寄存器  物理地址
    unsigned long pa_iomux_pad; // 电气属性寄存器  物理地址
    unsigned int pin; // 引脚号
    unsigned int clock_offset; // 时钟偏移
};
typedef struct LED_RESOURCE_T led_resource_t;
  • 获取设备资源接口:
/** @brief  get_led_resource  获取资源句柄
  * @param  led 参数
  * @retval 
  * @author lzm
  */
led_resource_t *get_led_resource(char ch)
{
    if(ch == 'R' || ch == 'r' || ch == '0')
        return &led_r;
    else if(ch == 'G' || ch == 'g' || ch == '1')
        return &led_g;
    else if(ch == 'B' || ch == 'b' || ch == '2')
        return &led_b;
    
    return 0;
}

5.4 驱动

实现驱动内容:

  • file_operations

  • 使用设备数组模式,实现统一管理,且达到时间复杂度为 O(1) 的性能。

  • file_operations

    • int led_dev_open(struct inode *inode, struct file *filp):打开设备节点。
    • int led_dev_release(struct inode *inode, struct file *filp):关闭设备节点。
    • ssize_t led_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos):写函数。
    • ssize_t led_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos):读函数。
  • 设备数组:

    • static led_dev_t led_dev_elem[LED_DEV_CNT];:led 设备列表。使用 id 作为下标去定位哪一个设备。
    • 设备结构体:
/* my led device struct */
typedef void (*led_init_f)(unsigned char);
typedef void (*led_exit_f)(unsigned char);
typedef void (*led_ctrl_f)(unsigned char, unsigned char);
struct LED_DEV_T
{
    /* 设备 ID 次设备号-1 因为次设备号0一般代表所有设备*/
    unsigned char id;
    /* 设备名称 */
    char name[10]; // 限定十个字符
    /* 设备参数 */
    dev_t dev_num; // 设备号
    struct cdev dev; // 内核设备文件 结构体
    /* led 状态 */
    unsigned char status; // 0: 关闭; 1:开
    /* 引脚参数 */
    led_pin_t *pin_data;
    /* 设备函数 */
    led_init_f init; // 初始化函数
    led_exit_f exit; // 出口函数
    led_ctrl_f ctrl; // 控制函数
};
typedef struct LED_DEV_T led_dev_t; 

5.5 系统,模块

万事俱备,只欠东风。

下层硬件的资源和驱动函数都准备好了,现在只需要实现模块即可。

主要三个点:

  • static int __init led_chrdev_init(void):入口函数。(module_init(led_chrdev_init)
  • static void __exit led_chrdev_exit(void):出口函数。(module_exit(led_chrdev_exit)
  • MODULE_LICENSE("GPL"):协议。

以上两个函数的内容可以参考字符设备驱动实现步骤来实现。

除了以上三个函数外,还有把驱动内容填入驱动文件中 file_operations 实体。

给出 led_module.c 文件参考:

/** @file         led_module.c
 *  @brief        驱动。
 *  @details      led 模块文件。
 *  @author       lzm
 *  @date         2021-03-06 10:23:03
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/
/* 系统库 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
/* 私人库 */
#include "led_resource.h"
#include "led_drv.h"
/* 变量 */
dev_t led_dev_num_start; // 开始设备号
static struct cdev led_cdev[LED_DEV_CNT+1]; // 全设备+LED_DEV_CNT个子设备
struct class *led_dev_class; // 设备类 (*用于设备节点*)
/* [drv][file_operations] */
static struct file_operations led_dev_fops = 
{
    .owner = THIS_MODULE,
    .open = led_dev_open,
    .release = led_dev_release,
    .write = led_dev_write,
    .read = led_dev_read,
};
/* [module][1] */
/** @brief   led_chrdev_init
  * @details led module 入口函数
  * @param  
  * @retval 
  * @author lzm
  */
static int __init led_chrdev_init(void)
{
    unsigned char i;
    printk("chrdev_init\n");
    /* my 设备文件初始化 */
    led_dev_init();
    /* [module][1][1] 申请设备号 */
    alloc_chrdev_region(&led_dev_num_start, 0, LED_DEV_CNT+1, LED_DEV_NAME);
    /* [module][1][2] 创建设备节点 */
    led_dev_class = class_create(THIS_MODULE, LED_DEV_CLASS);    
    for(i=0; i<LED_DEV_CNT+1; i++)
    {
        /* [module][1][3] 初始化内核设备文件 */
        cdev_init(&led_cdev[i], &led_dev_fops); // 把驱动程序初始化到内核设备文件中
        /* [module][1][4] 把内核设备文件注册到内核 */
        cdev_add(&led_cdev[i], MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i), 1); // 内核设备文件绑定设备号,并注册到内核
        /* [module][1][5] 创建设备节点 */
        if(!i)
            device_create(led_dev_class, NULL, MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i), NULL, LED_DEV_NAME); // 总设备
        else
            device_create(led_dev_class, NULL, MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i), NULL, LED_DEV_NAME"_%d",i);
    }
    return 0;
}
module_init(led_chrdev_init);
/* [module][2] */
/** @brief   led_chrdev_exit
  * @details led module 出口函数
  * @param  
  * @retval 
  * @author lzm
  */
static void __exit led_chrdev_exit(void)
{
    unsigned char i;
    printk("chrdev_exit!\n");
    for(i=0; i<LED_DEV_CNT+1; i++)
    {
        /* [module][2][1] 删除设备节点 */
        device_destroy(led_dev_class, MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i));
        /* [module][2][2] 注销设备文件 */
        cdev_del(&led_cdev[i]);
    }
    /* [module][2][3] 归还设备号 */
    unregister_chrdev_region(led_dev_num_start, LED_DEV_CNT+1);
    /* [module][2][4] 删除设备类 */
    class_destroy(led_dev_class);
    return;
}
module_exit(led_chrdev_exit);
/* [module][3] 协议 */
MODULE_AUTHOR("lizhuming");
MODULE_LICENSE("GPL");

5.6 Makefile

参考 《一个通用驱动Makefile-V2-支持编译多目录》

以下只给出源码:

# @file         Makefile
# @brief        驱动。
# @details      led 驱动模块 Makefile 例程。
# @author       lzm
# @date         2021-03-14 10:23:03
# @version      v1.1
# @copyright    Copyright By lizhuming, All Rights Reserved
#
# ********************************************************
# @LOG 修改日志:
# ******************************************************** # 编译后内核路径
KERNEL_DIR = /home/lss/work/kernel/imx6/ebf-buster-linux/build_image/build
# 定义框架
# ARCH 为 x86 时,编译链头为 
# ARCH 为 arm 时,编译链头为 arm-linux-gnueabihf-
ARCH = arm
ifeq ($(ARCH),x86)
CROSS_COMPILE = # 交叉编译工具头,如:
else
CROSS_COMPILE = arm-linux-gnueabihf-# 交叉编译工具头,如:arm-linux-gnueabihf-
endif
CC      = $(CROSS_COMPILE)gcc # 编译器,对 C 源文件进行编译处理,生成汇编文件
# 共享到sub-Makefile
export  ARCH  CROSS_COMPILE # 路径
PWD := $(shell pwd)
# 当前模块路径
# $(src) 是内和文件定义并传过来的当前模块 M= 的路径。
MODDIR := $(src) # 注意:驱动目标不要和文件名相同
TARGET_DRV := led_device_driver
TARGET_APP := led_app # 本次整个编译需要源 文件 和 目录
# 这里的“obj-m” 表示该模块不会编译到zImage ,但会生成一个独立的xxx.ko 静态编译(由内核源码顶层Makefile识别)
# 模块的多文件编译:obj-m 是告诉 makefile 最总的编译目标。而 $(TARGET)-y 则是告诉 makefile 该总目标依赖哪些目标文件。(也可以使用 xxx-objs)
$(TARGET_DRV)-y += led_module.o
$(TARGET_DRV)-y += ./device/led_dev_a.o
$(TARGET_DRV)-y += ./driver/led_drv.o
obj-m := $(TARGET_DRV).o
# obj-m += $(patsubst %.c,%.o,$(shell ls *.c)) # 编译条件处理
# 指定头文件 由于该文件是 -C 后再被调用的,所以部分参数不能使用 $(shell pwd)
# $(src) 是内和文件定义并传过来的当前模块 M= 的路径。
ccflags-y := -I$(MODDIR)/include
ccflags-y += -I$(MODDIR)/device
ccflags-y += -I$(MODDIR)/driver # 第一个目标 CURDIR 是该makefile内嵌变量,自动设置为当前目录
all :
    @$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR)  modules
#   make mobailes 就是是编译模块,上面是其添加参数的指令
#   $(CROSS_COMPILE)gcc -o $(TARGET_APP) $(TARGET_APP).c
    
# 清理
.PHONY:clean
clean:
    $(MAKE)  -C $(KERNEL_DIR) M=$(CURDIR) clean
#   rm $(TARGET_APP)

参考:

【linux】驱动-5-驱动框架分层分离&实战的更多相关文章

  1. Linux下USB驱动框架分析【转】

    转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...

  2. linux块设备驱动---概念与框架(转)

    基本概念   块设备(blockdevice) --- 是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性 ...

  3. Linux网络设备驱动 _驱动模型

    Linux素来以其强大的网络功能著名,同时, 网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对网络设备贯彻其"一切皆 ...

  4. Linux块设备驱动详解

    <机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...

  5. 【驱动】linux下I2C驱动架构全面分析

    I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...

  6. Linux 下wifi 驱动开发(三)—— SDIO接口WiFi驱动浅析

    SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈.可以实现用户主平台数据通过SDIO口到无线网络之间的转 ...

  7. 领域驱动设计业务框架DMVP

    DMVP,全称DDD-MVP,是基于领域驱动设计(DDD)搭建的业务框架,整体设计符合DDD领域模型的规范,业务上达成了领域模型和代码的一一映射,技术上达成了高内聚低耦合的架构设计,开发人员不需要关注 ...

  8. linux下I2C驱动架构全面分析【转】

    本文转载自:http://blog.csdn.net/wangpengqi/article/details/17711165 I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一 ...

  9. Linux内核USB驱动【转】

    本文转载自:http://www.360doc.com/content/12/0321/14/8363527_196286673.shtml 注意,该文件是2.4的内核的驱动源文件,并不保证在2.6内 ...

随机推荐

  1. ReactDOM API All In One

    ReactDOM API All In One React DOM API render() hydrate() unmountComponentAtNode() findDOMNode() crea ...

  2. 开放式 Web 应用程序安全性项目 OWASP

    开放式 Web 应用程序安全性项目 OWASP Open Web Application Security Project (OWASP) OWASP 基金会是谁? Open Web Applicat ...

  3. 如何使用 js 扩展 prototype 方法

    如何使用 js 扩展 prototype 方法 expand prototype function enhancedLog(msg = ``) { // this.msg = msg; enhance ...

  4. ES6 Map vs ES5 Object

    ES6 Map vs ES5 Object Map vs Object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Referenc ...

  5. PM2 All In One

    PM2 All In One https://pm2.keymetrics.io/ https://pm2.io/ $ yarn global add pm2 # OR $ npm install p ...

  6. Python & file operation mode

    Python & file operation mode create/read/write/append mode https://docs.python.org/3/library/fun ...

  7. CNN专访灵石CTO:Baccarat流动性挖矿能否持续?

    近日,CNN记者Robert独家专访Baccarat的项目团队CTO STEPHEN LITAN,跟他特别聊了聊DeFi的近况. 以下是专访全文: Robert:推出Baccarat的契机是什么? S ...

  8. 新手如何通过内存和NGK DeFi Baccarat进行组合投资?

    区块链市场在2020年迎来了大爆发,资本市场异常火热.无论是内存,还是DeFi,都无疑是这个火爆的区块链市场中的佼佼者.通过投资内存和DeFi,很多投资者都已经获取了非常可观的收益,尝到了资本市场带来 ...

  9. [转]LINUX下编译c++11的代码

    转载地址: https://blog.csdn.net/lwb102063/article/details/50445201 C++11,(即ISO/IEC 14882:2011),是目前的C++编程 ...

  10. 20201228 买卖股票的最佳时机 IV(困难)

    给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你最多可以完成 k 笔交易. 注意:你不能同时参 ...