【linux】驅動-5-驅動框架分層分離&實戰

李柱明發表於2021-03-29


前言

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)

參考:

相關文章