目錄
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 設備樹
因每個板子的硬件資源都不同,因此會存在大量的硬件資源 .c 文件存在于Linux內核中,這就會導致內核十分龐大臃腫
由此引出設備樹
- 對于每個單板都會有其對應的 dts 文件包含它的所有硬件資源
- 需要使用時,將該單板的 dts 文件編譯成 dtb 文件并傳入內核
- 內核會解析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. name 和 platform_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.name 和 platform_driver.driver.name
platform_driver.id_table 可能為空, 這時可以根據 platform_driver.driver.name 來尋找同名的 platform_device。
2.LED 模板驅動程序的改造:總線設備驅動模型
board_A_led.c:
- 定義?platform_device 結構體中的內容:name、num_resources、resource等成員
- 在入口函數和出口函數中分別注冊和注銷?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:
- 老規矩,配置 led_operations 結構體,定義實現結構體成員的函數 board_demo_led_init 和?board_demo_led_ctl(硬件操作)
- 配置平臺驅動 platform_driver 結構體,實現成員函數:
chip_demo_gpio_probe:用于初始化設備,只要有 platform_device 與 platform_driver 配對,就會執行這個函數,可以看出,把之前在 leddrv.c 中的創建設備節點的命令放到了這里
chip_demo_gpio_remove:用于清除設備,就是和上面相反- 最后也是老規矩,實現該驅動的入口函數和出口函數
注意:入口函數中的 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:
- 使用 EXPORT_SYMBOL() 函數將創建、刪除設備號和讀取 led_operations 結構體函數導出給 chip_demo_gpio.c 使用
EXPORT_SYMBOL():使其對所有內核代碼可見,從而可以在其他內核模塊中直接調用。若只包含在頭文件中,則只有在該模塊中可以使用- 其他就是正常的 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