5. 分离分层本章节记录实现LED驱动的大概步骤,且编程框架实现分离分层。
5.1 回顾-设备驱动实现步骤:
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 目录树分析:
5.3 设备主要内容:
现在设备资源格式文件中第一好格式:
/* 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 驱动实现驱动内容:
/* 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 系统,模块万事俱备,只欠东风。
以上两个函数的内容可以参考字符设备驱动实现步骤来实现。 给出 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) 参考:
|
|