前言
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)
參考:
- 《IMX6ULLRM(6ULL用戶手冊).pdf》
- 內核文檔html
- 李柱明博客:https://www.cnblogs.com/lizhuming/
- 本文鏈接:https://www.cnblogs.com/lizhuming
- 推薦去看源碼:
