【linux】驱动-5-驱动框架分层分离&实战
前言
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-驱动框架分层分离&实战的更多相关文章
- Linux下USB驱动框架分析【转】
转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...
- linux块设备驱动---概念与框架(转)
基本概念 块设备(blockdevice) --- 是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性 ...
- Linux网络设备驱动 _驱动模型
Linux素来以其强大的网络功能著名,同时, 网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对网络设备贯彻其"一切皆 ...
- Linux块设备驱动详解
<机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...
- 【驱动】linux下I2C驱动架构全面分析
I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...
- Linux 下wifi 驱动开发(三)—— SDIO接口WiFi驱动浅析
SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈.可以实现用户主平台数据通过SDIO口到无线网络之间的转 ...
- 领域驱动设计业务框架DMVP
DMVP,全称DDD-MVP,是基于领域驱动设计(DDD)搭建的业务框架,整体设计符合DDD领域模型的规范,业务上达成了领域模型和代码的一一映射,技术上达成了高内聚低耦合的架构设计,开发人员不需要关注 ...
- linux下I2C驱动架构全面分析【转】
本文转载自:http://blog.csdn.net/wangpengqi/article/details/17711165 I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一 ...
- Linux内核USB驱动【转】
本文转载自:http://www.360doc.com/content/12/0321/14/8363527_196286673.shtml 注意,该文件是2.4的内核的驱动源文件,并不保证在2.6内 ...
随机推荐
- ReactDOM API All In One
ReactDOM API All In One React DOM API render() hydrate() unmountComponentAtNode() findDOMNode() crea ...
- 开放式 Web 应用程序安全性项目 OWASP
开放式 Web 应用程序安全性项目 OWASP Open Web Application Security Project (OWASP) OWASP 基金会是谁? Open Web Applicat ...
- 如何使用 js 扩展 prototype 方法
如何使用 js 扩展 prototype 方法 expand prototype function enhancedLog(msg = ``) { // this.msg = msg; enhance ...
- ES6 Map vs ES5 Object
ES6 Map vs ES5 Object Map vs Object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Referenc ...
- PM2 All In One
PM2 All In One https://pm2.keymetrics.io/ https://pm2.io/ $ yarn global add pm2 # OR $ npm install p ...
- Python & file operation mode
Python & file operation mode create/read/write/append mode https://docs.python.org/3/library/fun ...
- CNN专访灵石CTO:Baccarat流动性挖矿能否持续?
近日,CNN记者Robert独家专访Baccarat的项目团队CTO STEPHEN LITAN,跟他特别聊了聊DeFi的近况. 以下是专访全文: Robert:推出Baccarat的契机是什么? S ...
- 新手如何通过内存和NGK DeFi Baccarat进行组合投资?
区块链市场在2020年迎来了大爆发,资本市场异常火热.无论是内存,还是DeFi,都无疑是这个火爆的区块链市场中的佼佼者.通过投资内存和DeFi,很多投资者都已经获取了非常可观的收益,尝到了资本市场带来 ...
- [转]LINUX下编译c++11的代码
转载地址: https://blog.csdn.net/lwb102063/article/details/50445201 C++11,(即ISO/IEC 14882:2011),是目前的C++编程 ...
- 20201228 买卖股票的最佳时机 IV(困难)
给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你最多可以完成 k 笔交易. 注意:你不能同时参 ...