前言
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)