前言

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. JavaScript 实现 (ECMAScript 6)

    JavaScript 的核心 ECMAScript 描述了该语言的语法和基本对象: DOM 描述了处理网页内容的方法和接口: BOM 描述了与浏览器进行交互的方法和接口. ECMAScript.DOM ...

  2. React render algorithm & Fiber vs Stack

    React render algorithm & Fiber vs Stack React 渲染算法 & Fiber vs Stack https://stackoverflow.co ...

  3. JavaScript for, for...in, for...of, for-await...of difference All In One

    JavaScript for, for...in, for...of, for-await...of difference All In One for for...in for...of for-a ...

  4. Web 全栈开发 MySQL 面试题

    Web 全栈开发 MySQL 面试题 MySQL MySQL 读写分离 读写分离原理 MySQL的主从复制和MySQL的读写分离两者有着紧密联系,首先部署主从复制,只有主从复制完了,才能在此基础上进行 ...

  5. Array.fill & String.padStart & String.padEnd

    Array.fill & String.padStart & String.padEnd Array.fill arr.fill(value[, start[, end]]) http ...

  6. mysql一张表到底能存多少数据?

    前言 程序员平时和mysql打交道一定不少,可以说每天都有接触到,但是mysql一张表到底能存多少数据呢?计算根据是什么呢?接下来咱们逐一探讨 知识准备 数据页 在操作系统中,我们知道为了跟磁盘交互, ...

  7. 数据库范式(1NF/2NF/3NF)

    本文转载自数据库范式(1NF/2NF/3NF) 概述 范式:英文名称是 Normal Form,它是英国人 E.F.Codd(关系数据库的老祖宗)在上个世纪70年代提出关系数据库模型后总结出来的,范式 ...

  8. 分布式事务 SEATA-1.4.1 AT模式 配合NACOS 应用

    SEATA 配置 目录 SEATA 配置 TC (Transaction Coordinator) - 事务协调者 配置参数 nacos bash 脚本 同步 config 配置到 nacos 使用 ...

  9. (十一) 数据库查询处理之连接(Join)

    (十一) 数据库查询处理之连接(Join) 1. 连接操作的一个例子 把外层关系和内层关系中满足一定关系的属性值拼接成一个新的元组 一种现在仍然十分有用的优化思路Late Materializatio ...

  10. 微信小程序:picker组件实现下拉框效果

    一.wxml中代码 <view class="in_order_Param">             <text>状态:</text>     ...