嵌入式Linux驅動——3 總線設備驅動模型

目錄

1.總線設備驅動模型

1.1?總線設備驅動模型

1.2 設備樹

1.3 platform_device 和 platform_driver 的匹配規則

1.3.1 最先比較

1.3.2?然后比較?

1.3.3?最后比較

2.LED 模板驅動程序的改造:總線設備驅動模型


1.總線設備驅動模型

在前面的 led 驅動程序中,我們使用分離的思想驅動程序分為了 led_resource.c 和 led_drv.c

分離思想為不同種類的硬件資源定義了不同的 resource 結構體,但是在設備有各種資源,難道每種資源都用一個結構體表示?

這并不現實,因此在分離思想中擴展出總線設備驅動模型

ps:其實并沒有解決這個問題,這也是一種分離的思想方法

1.1?總線設備驅動模型

引入 platform_device platform_driver,將“資源”與“驅動”分離開來:

可以看到,一種硬件資源通過一個 platform_device 結構體表示,并通過總線 bus 與其相對的驅動 platform_driver 結構體相連接

(ps:好像并沒有解決一種資源用一個結構體表示的問題,不過韋老師在視頻是這樣介紹的...不懂了)
答:因為這也是一種分離的思想,確實是沒有解決這個問題

?特點:

  1. 代碼稍微復雜,但是易于擴展。跟“分離思想”類似,可隨時修改硬件資源,不像傳統寫法,引腳的使用和操作都寫死在代碼中
  2. 冗余代碼太多,修改引腳時設備端的代碼需要重新編譯

1.2 設備樹

因每個板子的硬件資源都不同,因此會存在大量的硬件資源 .c 文件存在于Linux內核中,這就會導致內核十分龐大臃腫

由此引出設備樹

  1. 對于每個單板都會有其對應的 dts 文件包含它的所有硬件資源
  2. 需要使用時,將該單板的 dts 文件編譯成 dtb 文件并傳入內核
  3. 內核會解析dtb文件并構造出一系列的 platform_device?

因 dts 文件放在內核之外,這樣就保持了 Linux 內核的干凈

并不是說有了設備樹文件就不用再編寫驅動:設備樹僅用于描述硬件資源,具體操作還得編寫驅動代碼

1.3 platform_device 和 platform_driver 的匹配規則

1.3.1 最先比較

若?platform_device 中定義了?driver_override

則最先比較 platform_device.driver_override platform_driver.driver.name

可以通過設置 platform_device 的 driver_override,強制選擇某個 platform_driver (非某人不嫁)


1.3.2?然后比較?

platform_device. nameplatform_driver.id_table[i].name

Platform_driver.id_table 是 “platform_device_id” 指針,表示該 drv 支持若干個 device,它里面列出了各個 device 的{.name, .driver_data},其中的“name”表示該 drv 支持的設備的名字driver_data 是些提供給該 device 的私有數據

通過 name 進行匹配


1.3.3?最后比較

platform_device.nameplatform_driver.driver.name

platform_driver.id_table 可能為空, 這時可以根據 platform_driver.driver.name 來尋找同名的 platform_device。


2.LED 模板驅動程序的改造:總線設備驅動模型

board_A_led.c:

  1. 定義?platform_device 結構體中的內容:name、num_resources、resource等成員
  2. 在入口函數和出口函數中分別注冊和注銷?platform_device 結構體

demo 示例代碼:

#include "led_resource.h"static struct led_resource board_A_led = {.pin = GROUP_PIN(3,1),
};/*定義resource資源*/
/*flag隨便拿一個IORESOURCE_IRQ用著先*/
static struct resource resource[] = {{.start = GROUP_PIN(3,1),.flags = IORESOURCE_IRQ,},{.start = GROUP_PIN(5,8),.flags = IORESOURCE_IRQ,},
}/*定義platform_device結構體*/
static struct platform_device board_A_led_dev ={.name = "100ask_led",.num_resources = ARRAY_SIZE(resource),.resource = resource,
};/*入口函數*/
static int led_dev_init(void)
{int err;/*注冊*/err = platform_device_register(&board_A_led_dev);return 0;
}/*出口函數*/
static void led_dev_exit(void)
{int err;/*注銷*/err = platform_device_unregister(&board_A_led_dev);return 0;
}module_init(led_dev_init);
module_exit(led_dev_exit);MODULE_LICENSE("GPL");

?chip_demo_gpio.c:

  1. 老規矩,配置 led_operations 結構體,定義實現結構體成員的函數 board_demo_led_init 和?board_demo_led_ctl(硬件操作)
  2. 配置平臺驅動 platform_driver 結構體,實現成員函數:
    chip_demo_gpio_probe:用于初始化設備,只要有 platform_device 與 platform_driver 配對,就會執行這個函數,可以看出,把之前在 leddrv.c 中的創建設備節點的命令放到了這里
    chip_demo_gpio_remove:用于清除設備,就是和上面相反
  3. 最后也是老規矩,實現該驅動的入口函數和出口函數

注意:入口函數中的 register_led_operations 操作,這是給 leddrv.c 傳入led_operations 結構體用的,為什么不在 leddrv.c 中調用定義好的 get_board_led_opr 函數??

????????這涉及一個相互依賴問題,因為前面 chip_demo_gpio_probe 中創建和清除設備節點的函數 led_class_create_device 依賴于底層的 leddrv.c ,如果此時又在底層的?leddrv.c 中調用上層的?get_board_led_opr ,會造成的交叉依賴的問題,因此只能通過指針在上層傳給leddrv.c


demo程序代碼:(只打印信息,不做具體操作)

#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>#include "led_opr.h"
#include "leddrv.h"
#include "led_resource.h"// 全局LED引腳數組和計數器
static int g_ledpins[100]; 
static int g_ledcnt = 0;// LED初始化函數:配置指定LED的GPIO引腳
static int board_demo_led_init(int which) 
{printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));// 根據GPIO組號執行不同的初始化操作switch(GROUP(g_ledpins[which])) {case 0: printk("init pin of group 0 ...\n"); break;case 1: printk("init pin of group 1 ...\n"); break;case 2: printk("init pin of group 2 ...\n"); break;case 3: printk("init pin of group 3 ...\n"); break;}return 0;
}// LED控制函數:設置指定LED的開關狀態
static int board_demo_led_ctl(int which, char status) 
{printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));// 根據GPIO組號執行不同的控制操作switch(GROUP(g_ledpins[which])) {case 0: printk("set pin of group 0 ...\n"); break;case 1: printk("set pin of group 1 ...\n"); break;case 2: printk("set pin of group 2 ...\n"); break;case 3: printk("set pin of group 3 ...\n"); break;}return 0;
}// LED操作接口結構體
static struct led_operations board_demo_led_opr = {.init = board_demo_led_init,.ctl  = board_demo_led_ctl,
};// 獲取LED操作接口
struct led_operations *get_board_led_opr(void)
{return &board_demo_led_opr;
}// 平臺設備探測函數:初始化LED設備
static int chip_demo_gpio_probe(struct platform_device *pdev)
{struct resource *res;int i = 0;// 遍歷并注冊所有LED資源while (1) {res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);if (!res) break;g_ledpins[g_ledcnt] = res->start;led_class_create_device(g_ledcnt);g_ledcnt++;}return 0;
}// 平臺設備移除函數:清理LED設備
static int chip_demo_gpio_remove(struct platform_device *pdev)
{struct resource *res;int i = 0;// 注銷所有LED設備while (1) {res = platform_get_resource(pdev, IORESOURCE_IRQ, i);if (!res) break;led_class_destroy_device(i);i++;g_ledcnt--;}return 0;
}// 平臺驅動定義
static struct platform_driver chip_demo_gpio_driver = {.probe      = chip_demo_gpio_probe,.remove     = chip_demo_gpio_remove,.driver     = {.name   = "100ask_led",  // 驅動名稱},
};// 驅動初始化
static int __init chip_demo_gpio_drv_init(void)
{int err;// 注冊平臺驅動和LED操作err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&board_demo_led_opr);return 0;
}// 驅動退出
static void __exit lchip_demo_gpio_drv_exit(void)
{platform_driver_unregister(&chip_demo_gpio_driver);
}module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");


?

leddrv.c:

  1. 使用 EXPORT_SYMBOL() 函數將創建、刪除設備號和讀取 led_operations 結構體函數導出給 chip_demo_gpio.c 使用
    EXPORT_SYMBOL():使其對所有內核代碼可見,從而可以在其他內核模塊中直接調用。若只包含在頭文件中,則只有在該模塊中可以使用
  2. 其他就是正常的 led 驅動操作,配置 file_operations 結構體,實現結構體成員的各種函數

上面已經解釋了為什么不能通過get_board_led_opr函數獲取led_operations結構體,這里不再重復


demo程序代碼:(只打印信息,不做具體操作)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_opr.h"  // 自定義LED操作接口頭文件/* 主設備號:0表示由內核動態分配 */
static int major = 0;
static struct class *led_class;          // 設備類指針
struct led_operations *p_led_opr;        // LED操作函數集指針/* 工具宏:取最小值 */
#define MIN(a, b) (a < b ? a : b)/**************************** 設備管理接口 ****************************/
/*** @brief 創建設備節點* @param minor 次設備號*/
void led_class_create_device(int minor)
{// 在/dev目錄下創建設備節點,命名為100ask_led0, 100ask_led1等device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor);
}/*** @brief 銷毀設備節點 * @param minor 次設備號*/
void led_class_destroy_device(int minor)
{device_destroy(led_class, MKDEV(major, minor));
}/*** @brief 注冊LED操作函數集* @param opr 包含init/ctl等操作函數的指針結構體*/
void register_led_operations(struct led_operations *opr)
{p_led_opr = opr;  // 保存外部傳入的操作函數集
}/* 導出符號供其他模塊使用 */
EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);/**************************** 文件操作接口 ****************************/
/* read操作(未實現實際功能) */
static ssize_t led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/*** @brief write操作:控制LED狀態* @param file 文件結構體* @param buf 用戶空間數據緩沖區(包含控制命令)* @param size 數據大小* @param offset 文件偏移* @return 成功寫入的字節數*/
static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;  // 存儲LED狀態(0/1)struct inode *inode = file_inode(file);int minor = iminor(inode);  // 獲取次設備號printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 從用戶空間拷貝控制數據err = copy_from_user(&status, buf, 1);/* 調用注冊的LED控制函數 */p_led_opr->ctl(minor, status);return 1;  // 返回已處理的字節數
}/*** @brief open操作:初始化LED* @param node inode結構體* @param file 文件結構體* @return 成功返回0*/
static int led_drv_open(struct inode *node, struct file *file)
{int minor = iminor(node);  // 獲取次設備號printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 調用注冊的LED初始化函數 */p_led_opr->init(minor);return 0;
}/* release操作(未實現實際功能) */
static int led_drv_close(struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 文件操作結構體定義 */
static struct file_operations led_drv = {.owner   = THIS_MODULE,  // 模塊所有者.open    = led_drv_open,.read    = led_drv_read,.write   = led_drv_write,.release = led_drv_close,
};/**************************** 模塊初始化/退出 ****************************/
/*** @brief 模塊初始化函數* @return 成功返回0,失敗返回錯誤碼*/
static int __init led_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 注冊字符設備(動態分配主設備號) */major = register_chrdev(0, "100ask_led", &led_drv);/* 2. 創建設備類 */led_class = class_create(THIS_MODULE, "100ask_led_class");if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "led");return -1;}return 0;
}/*** @brief 模塊退出函數*/
static void __exit led_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 銷毀設備類 */class_destroy(led_class);/* 2. 注銷字符設備 */unregister_chrdev(major, "100ask_led");
}/* 指定模塊的初始化和退出函數 */
module_init(led_init);
module_exit(led_exit);/* 模塊許可證聲明(必需) */
MODULE_LICENSE("GPL");

?ledtest.c:

正常的測試函數,跟之前的都一樣

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./ledtest /dev/100ask_led0 on* ./ledtest /dev/100ask_led0 off*/
int main(int argc, char **argv)
{int fd;char status;/* 1. 判斷參數 */if (argc != 3) {printf("Usage: %s <dev> <on | off>\n", argv[0]);return -1;}/* 2. 打開文件 */fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}/* 3. 寫文件 */if (0 == strcmp(argv[2], "on")){status = 1;write(fd, &status, 1);}else{status = 0;write(fd, &status, 1);}close(fd);return 0;
}

Makefile:

跟以往不同,這里需要把不同的驅動.c文件各自編譯成單獨的ko文件

1.必須拆分為獨立.ko的情況:

  • 設備信息來自硬件(如DTB或ACPI)

  • 同一總線支持多種設備(如USB鍵盤/鼠標共用一個USB總線驅動)

2.可合并的情況一個.ko的情況:

  • 純軟件模擬的總線(如虛擬平臺設備)

  • 設備固定且無需動態匹配

KERN_DIR = all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f ledtest# 參考內核源碼drivers/char/ipmi/Makefile
# 要想把a.c, b.c編譯成ab.ko, 可以這樣指定:
# ab-y := a.o b.o
# obj-m += ab.oobj-m += leddrv.o chip_demo_gpio.o board_A_led.o

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/76086.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76086.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/76086.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

操作系統常用命令

邏輯卷創建及掛載步驟&#xff1a; vgcreate vg_app /dev/sda //在sda盤上創建vg_app卷組 lvcreate -L 50G -n lv_mysql vg_app //在vg_app卷組上創建邏輯卷lv_mysql mkfs.xfs /dev/vg_app/lv_mysql //對lv_mysql 邏輯卷創建文件系統 mkdir mysql //創建mysql目錄 ech…

Git 的進階功能和技巧

1、分支的概念和使用 1.1、什么是分支&#xff1f; 分支&#xff08;Branch&#xff09;是在版本控制中非常重要的概念。幾乎所有版本控制系統都支持某種形式的分支。在 Git 中&#xff0c;分支是 Git 強大功能之一&#xff0c;它允許我們從主開發線分離出來&#xff0c;在不…

mapbox基礎,加載F4Map二維地圖

????? 主頁: gis分享者 ????? 感謝各位大佬 點贊?? 收藏? 留言?? 加關注?! ????? 收錄于專欄:mapbox 從入門到精通 文章目錄 一、??前言1.1 ??mapboxgl.Map 地圖對象1.2 ??mapboxgl.Map style屬性二、??F4Map 簡介2.1 ??技術特點2.2 ??核…

Conda使用方法詳解

Conda是一個開源的包管理和環境管理系統&#xff0c;主要用于Python/R等科學計算領域&#xff0c;可以輕松管理不同項目的依賴關系。以下是Conda的詳細使用方法&#xff1a; 一、安裝與配置 1.安裝Miniconda/Anaconda Miniconda是精簡版&#xff0c;只包含conda和Python Ana…

Unity ViewportConstraint

一、組件功能概述 ViewportConstraint是一個基于世界坐標的UI邊界約束組件&#xff0c;主要功能包括&#xff1a; 將UI元素限制在父容器范圍內支持自定義內邊距&#xff08;padding&#xff09;可獨立控制水平和垂直方向的約束 二、實現原理 1. 邊界計算&#xff08;世界坐…

代碼隨想錄-動態規劃24

leetcode-300-最長遞增子序列 dp[i]表示i之前包括i的以nums[i]結尾的最長遞增子序列的長度 dp[j]是(0,i-1)不包括i的以nums[i-1]結尾的最長遞增子序列長度 int lengthOfLIS(int* nums, int numsSize) {if(numsSize < 1)return numsSize;int dp[numsSize];for(int i 0 ; i &…

銀河麒麟V10 Ollama+ShellGPT打造Shell AI助手——筑夢之路

環境說明 1. 操作系統版本: 銀河麒麟V10 2. CPU架構&#xff1a;X86 3. Python版本&#xff1a;3.12.9 4. 大模型&#xff1a;mistral:7b-instruct 準備工作 1. 編譯安裝python 3.12 # 下載python 源碼wget https://www.python.org/ftp/python/3.12.9/Python-3.12.9.tg…

2025 跨平臺技術如何選:KMP 與 Flutter 的核心差異

前言 在移動開發的演進歷程中&#xff0c;跨平臺技術始終是一個充滿爭議卻無法回避的話題。從早期的 React Native 到如今的 Kotlin Multiplatform&#xff08;KMP&#xff09;和 Flutter&#xff0c;開發者們始終在代碼復用與原生體驗之間尋找平衡。本文我們從技術實現、性能…

Python Cookbook-5.10 選取序列中最小的第 n個元素

任務 需要根據排名順序從序列中獲得第n個元素(比如&#xff0c;中間的元素&#xff0c;也被稱為中值)。如果序列是已經排序的狀態&#xff0c;應該使用seq[n]&#xff0c;但如果序列還未被排序&#xff0c;那么除了先對整個序列進行排序之外&#xff0c;還有沒有更好的方法? …

列表之鏈表_C

數據結構&#xff08;鄧俊輝&#xff09;&#xff1a;列表及相關概念_listnodeposi-CSDN博客 #include <stdio.h> #include <stdlib.h>// 定義Rank類型為int typedef int Rank;// 定義ListNode結構體 typedef struct ListNode {int data;struct ListNode* pred;st…

0401react中使用css-react-css-仿低代碼平臺項目

文章目錄 1、普通方式-內聯使用css2、引入css文件2.1、示例2.2、classnames 3、內聯css與引入css文件對比3.1、內聯css3.2、 外部 CSS 文件&#xff08;External CSS&#xff09; 4、css module5、sass6、classnames組合scss modules7、css-in-js7.1、CSS-in-JS 的核心特性7.2、…

鴻蒙開發者高級認證編程題庫

題目一:跨設備分布式數據同步 需求描述 開發一個分布式待辦事項應用,要求: 手機與平板登錄同一華為賬號時,自動同步任務列表任一設備修改任務狀態(完成/刪除),另一設備實時更新任務數據在設備離線時能本地存儲,聯網后自動同步實現方案 // 1. 定義分布式數據模型 imp…

stream流Collectors.toMap(),key值重復問題

文章目錄 一、問題二、問題示例三、原因四、解決方法4.1、方案一 一、問題 發現Collectors.toMap的一個坑&#xff0c;若key值重復的時候會拋異常。如&#xff1a; IllegalStateException: Duplicate key 男 二、問題示例 報錯示例如下&#xff1a; import lombok.AllArgsC…

未來 AI 發展趨勢與挑戰(AGI、數據安全、監管政策)

從 ChatGPT 的火爆到國內 DeepSeek、通義千問、百川智能等模型的興起,AI 正以前所未有的速度走入各行各業。而下一階段,AI 是否會發展出真正的“通用智能”(AGI)?數據隱私、技術倫理又該如何應對?本文將帶你全面洞察未來 AI 的技術趨勢與落地挑戰。 一、AGI 的曙光:通用…

【微服務】SpringBoot整合LangChain4j 操作AI大模型實戰詳解

【微服務】SpringBoot整合LangChain4j 操作AI大模型實戰詳解 一、前言 隨著人工智能技術的飛速發展&#xff0c;AI大模型已經在眾多領域展現出強大的能力&#xff0c;為業務拓展和商業價值提升帶來了新的機遇。SpringBoot作為一款廣受歡迎的Java微服務框架&#xff0c;以其簡…

一種單脈沖雷達多通道解卷積前視成像方法【論文閱讀】

一種單脈沖雷達多通道解卷積前視成像方法-李悅麗-2007 1. 論文的研究目標與實際意義1.1 研究目標1.2 實際問題與產業意義2. 論文提出的思路、方法及模型2.1 多通道解卷積(MCD)技術的核心思想2.1.1 數學模型與公式推導2.1.2 針對單脈沖雷達的改進2.2 方法與傳統技術的對比3. 實…

Codeforces Round 1016 (Div. 3)題解

題目地址 https://codeforces.com/contest/2093 銳評 在所有題意都理解正確的情況下&#xff0c;整體難度不算太難。但是偏偏存在F這么惡心的題意&#xff0c;樣例都不帶解釋一下的&#xff0c;根本看不懂題。D題也惡心&#xff0c;在于遞歸過程的拆分&#xff0c;需要點數學…

【python讀取并顯示遙感影像】

在Python中讀取并顯示遙感影像&#xff0c;可以使用rasterio庫讀取影像數據&#xff0c;并結合matplotlib進行可視化。以下是一個完整的示例代碼&#xff1a; import rasterio import matplotlib.pyplot as plt import numpy as np# 打開遙感影像文件 with rasterio.open(path…

怎樣使用Python編寫的Telegram聊天機器人

怎樣使用Python編寫的Telegram聊天機器人 代碼直接運行可用 以下是對這段代碼的詳細解釋: 1. 導入必要的庫 import loggingfrom telegram import Update from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, filters, MessageHandler import log…