【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)

參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM