关注+星标公众号,不容错过精彩

作者:HywelStar
每一个从事驱动开发的人都会有一个这样的疑问,当 Linux 系统启动时,内核是如何在众多的驱动中,精准找到并激活对应硬件的驱动程序的?这背后的核心,就是设备树与驱动的自动配对机制。这到底是如何做到的?none !important
更神奇的是,内核怎么知道硬件的存在?怎么找到对应的驱动?怎么确保驱动加载后马上去驱动那个硬件?none !important
这篇文章将以 Linux 6.1 + Orange Pi 5 Plus(RK3588) 为例,先查看驱动自动配对的完整机制,在内核层面,这主要归功于设备树与驱动的 compatible 匹配流程。none !important
在驱动自动加载的故事里,有三个主要角色:none !important
1. 设备树(Device Tree)none !important
.dts 文件编译成的 .dtb 二进制文件2. 驱动程序(Driver)none !important
compatible 字段)3. 内核中的设备驱动框架(Bus、Device、Driver)none !important
想象这样一个真实场景,Orange Pi 5 Plus 启动:none !important
【系统启动】 ↓【设备树被加载并解析】→ 内核扫描rk3588-orangepi-5-plus.dtb ↓ ├─ Device 1: gpio-leds, compatible = "gpio-leds" ├─ Device 2: pwm-fan, compatible = "pwm-fan" ├─ Device 3: sfc (SPI Flash), compatible = "jedec,spi-nor" ├─ Device 4: remotectl, compatible = "rockchip,remotectl-pwm" └─ Device 5: GPIO3, compatible = "rockchip,rk3588-gpio" ↓【内核驱动模块被加载】→ 驱动注册自己到内核 ↓ ├─ Driver A: compatible = "gpio-leds" ├─ Driver B: compatible = "pwm-fan" ├─ Driver C: compatible = "jedec,spi-nor" ├─ Driver D: compatible = "rockchip,remotectl-pwm" └─ Driver E: compatible = "rockchip,rk3588-gpio" ↓【内核进行配对】→ 匹配 compatible 字符串 ↓ ├─ gpio-leds device + gpio-leds driver ✓ 匹配!→ 调用 probe() ├─ pwm-fan device + pwm-fan driver ✓ 匹配!→ 调用 probe() ├─ spi-nor device + jedec-spi-nor driver ✓ 匹配!→ 调用 probe() └─ ... 其他设备 ↓【驱动 probe() 执行】→ 驱动被激活,硬件被初始化 ↓【驱动开始工作】整个过程的核心是字符串匹配!none !important
设备树里说:"我的 compatible 是 gpio-leds" 驱动里说:"我的 of_match_table 里包括 gpio-leds" 内核就说:"你俩配对成功,开始工作吧!"这就是 Linux 驱动框架的核心设计理念。none !important
从你的 Orange Pi 5 Plus 官方设备树 rk3588-orangepi-5-plus.dts 看:none !important
// arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dts/dts-v1/;#include "rk3588-orangepi-5-plus.dtsi"#include "rk3588-linux.dtsi"#include "rk3588-orangepi-5-plus-lcd.dtsi"#include "rk3588-orangepi-5-plus-camera1.dtsi"/ { model = "RK3588 OPi 5 Plus"; compatible = "rockchip,rk3588-orangepi-5-plus", "rockchip,rk3588"; // ← 关键!关键点解析:none !important
compatible = "rockchip,rk3588-orangepi-5-plus", "rockchip,rk3588"这告诉内核两件事(优先级从左到右):none !important
rk3588-orangepi-5-plus.dtsi - Orange Pi 5 Plus 特定的硬件定义rk3588-linux.dtsi - Linux 通用的 RK3588 配置rk3588-orangepi-5-plus-lcd.dtsi - LCD 显示屏配置rk3588-orangepi-5-plus-camera1.dtsi - 摄像头配置这是设备树中第一个自定义设备节点:none !important
./arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dtsleds: gpio-leds { compatible = "gpio-leds"; // ← 关键:告诉内核这是 GPIO LED pinctrl-names = "default"; pinctrl-0 = <&leds_rgb>; status = "okay"; // ← 启用这个设备 blue_led@1 { gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_HIGH>; // ← GPIO3 的第 6 号 pin (A 组) label = "blue_led"; linux,default-trigger = "heartbeat"; // ← LED 闪烁方式:心跳 linux,default-trigger-delay-ms = <0>; }; green_led@2 { gpios = <&gpio3 RK_PB1 GPIO_ACTIVE_HIGH>; // ← GPIO3 的第 9 号 pin (B 组) label = "green_led"; linux,default-trigger = "heartbeat"; linux,default-trigger-delay-ms = <0>; };};
解析:none !important
drivers/leds/leds-gpio.c 能驱动它&gpio3 - 引用 GPIO3 控制器RK_PA6 - A 组第 6 号(从 0 开始)RK_PB1 - B 组第 1 号pinctrl-0 = <&leds_rgb>;这告诉 pinctrl 子系统要配置哪些引脚为 GPIO 输出模式none !important
在文件的 pinctrl 部分:none !important
&pinctrl { leds_gpio { leds_rgb: leds-rgb { rockchip,pins = <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>, <3 RK_PB1 RK_FUNC_GPIO &pcfg_pull_up>; }; };};解析:none !important
rockchip,pins - 配置哪些物理引脚RK_FUNC_GPIO - 将这些引脚功能设置为 GPIO(不是其他功能如 I2C、UART 等)&pcfg_pull_up - 上拉配置你的设备树中还有一个风扇驱动,这也展示了驱动自动加载:none !important
fan: pwm-fan { compatible = "pwm-fan"; // ← 标准 PWM 风扇驱动 #cooling-cells = <2>; pwms = <&pwm3 0 50000 0>; // ← 使用 PWM3 cooling-levels = <0 50 100 150 200 255>; // ← 风扇速度等级 rockchip,temp-trips = < 50000 1 // 温度 50°C,风扇速度等级 1 55000 2 // 温度 55°C,风扇速度等级 2 60000 3 // 温度 60°C,风扇速度等级 3 65000 4 // 温度 65°C,风扇速度等级 4 70000 5 // 温度 70°C,风扇速度等级 5 >; status = "okay";};这个设备也会自动被 pwm-fan 驱动加载:none !important
compatible = "pwm-fan" 的设备drivers/hwmon/pwm-fan.c 驱动设备树中的红外遥控配置:none !important
&pwm15 { compatible = "rockchip,remotectl-pwm"; // ← Rockchip 特定驱动 pinctrl-names = "default"; pinctrl-0 = <&pwm15m1_pins>; remote_pwm_id = <3>; handle_cpu_id = <1>; remote_support_psci = <0>; status = "okay"; ir_key1 { rockchip,usercode = <0xfb04>; // ← 遥控器用户码 rockchip,key_table = < 0xa3 KEY_ENTER, 0xe4 388, 0xf5 KEY_BACK, 0xbb KEY_UP, // ... 更多按键映射 0xb2 KEY_POWER, // ... 等等 >; };};这是一个 Rockchip 特定的远程控制驱动(不是通用的 gpio-ir-receiver)。none !important
#include <linux/kernel.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/of_platform.h>#include <linux/leds.h>// 1. 驱动能驱动什么硬件 → of_match_table(设备树匹配)static conststruct of_device_id of_gpio_leds_match[] = { { .compatible = "gpio-leds" }, // ← 关键:能驱动 gpio-leds {}};MODULE_DEVICE_TABLE(of, of_gpio_leds_match);// 2. Probe 函数:匹配成功后被调用static int gpio_leds_probe(struct platform_device *pdev) {struct device *dev = &pdev->dev;struct device_node *np = dev->of_node; // ← 获取设备树节点struct device_node *child; int count = 0; dev_info(dev, "GPIO LED driver probe called!\n"); // 遍历设备树中的每个 LED 节点(blue_led@1, green_led@2) for_each_child_of_node(np, child) { const char *label; int ret; // 读取 label 属性:"blue_led" 或 "green_led" label = of_get_property(child, "label", NULL); // 获取 GPIO 引脚(Linux 6.1 推荐做法)struct gpio_desc *gpiod = fwnode_gpiod_get_index( of_fwnode_handle(child), "gpios", 0, GPIOD_OUT_LOW, label); if (IS_ERR(gpiod)) { dev_err(dev, "failed to get GPIO for %s\n", label); continue; } dev_info(dev, "Registered LED %s\n", label); count++; } dev_info(dev, "GPIO LED driver probed with %d LEDs\n", count); return 0;}// 3. Remove 函数static int gpio_leds_remove(struct platform_device *pdev) { dev_info(&pdev->dev, "GPIO LED driver removed\n"); return 0;}// 4. 驱动结构体:注册驱动staticstruct platform_driver gpio_led_driver = { .probe = gpio_leds_probe, .remove = gpio_leds_remove, .driver = { .name = "leds-gpio", .of_match_table = of_gpio_leds_match, // ← 设备树匹配表 .owner = THIS_MODULE, }};// 5. 驱动注册宏module_platform_driver(gpio_led_driver);MODULE_AUTHOR("Kernel LED Authors");MODULE_DESCRIPTION("GPIO LED driver for Linux");MODULE_LICENSE("GPL");当 Orange Pi 5 Plus 启动 Linux 6.1 时:none !important
第 1 步:U-Boot 加载设备树none !important
U-Boot 从 eMMC/SPI Flash 启动 ↓加载 Linux 内核 ↓加载 rk3588-orangepi-5-plus.dtb 设备树文件 ↓将 dtb 地址传给 Linux 内核第 2 步:Linux 解析设备树none !important
Linux 内核初始化 ↓设备树解析器读取 .dtb 文件 ↓创建设备树节点对象 ↓扫描所有节点,为有 compatible 属性的节点创建 platform_device ├─ gpio-leds 节点 → 创建 platform_device("leds") ├─ pwm-fan 节点 → 创建 platform_device("fan") ├─ pwm15 节点 → 创建 platform_device("remotectl") └─ ... 其他设备第 3 步:驱动注册和配对none !important
leds-gpio.ko 驱动模块初始化 ↓驱动调用 platform_driver_register(&gpio_led_driver) ↓驱动的 of_match_table 告诉内核:"我能驱动 compatible='gpio-leds' 的设备" ↓内核立即搜索是否有这样的设备 ↓找到了 platform_device("leds"),其 compatible = "gpio-leds" ↓配对成功!✓ ↓内核调用驱动的 probe() 函数: ├─ gpio_leds_probe(&platform_device("leds")) ├─ 遍历子节点:blue_led@1 和 green_led@2 ├─ 为每个 LED 获取 GPIO 引脚 ├─ 注册到 LED 子系统 └─ 创建 /sys/class/leds/blue_led 和 /sys/class/leds/green_led第 4 步:系统启动完毕none !important
所有驱动加载完毕 ↓蓝色 LED 和绿色 LED 开始按"心跳"方式闪烁 ↓你可以从用户空间控制 LED: $ echo 1 > /sys/class/leds/blue_led/brightness $ echo 0 > /sys/class/leds/green_led/brightness// 这是内核如何进行 compatible 匹配的简化代码const struct of_device_id *of_match_device( const struct of_device_id *matches, struct device *dev) { // 如果设备没有设备树节点,无法匹配 if (!dev->of_node) return NULL; // 获取设备树中的 compatible 属性字符串 const char *compatible = of_get_property(dev->of_node, "compatible", NULL); if (!compatible) return NULL; // 逐一检查驱动的 of_match_table while (matches->name[0] || matches->compatible[0]) { // 比较 compatible 字符串 if (!of_compat_cmp(compatible, matches->compatible, strlen(matches->compatible))) { return matches; // ✓ 匹配成功! } matches++; } return NULL; // ✗ 没有驱动能驱动这个设备}核心就是这一行:none !important
if (!of_compat_cmp(compatible, matches->compatible, ...))字符串比较!当设备树中的 compatible 和驱动的 of_match_table 中的字符串相同时,就匹配成功!none !important
在你的 Orange Pi 5 Plus 上运行这些命令:none !important
# 1. 查看 LED 设备是否被识别$ ls /sys/class/leds/blue_led green_led# 2. 查看 LED 的状态和亮度$ cat /sys/class/leds/blue_led/brightness0 # 当前亮度(0-255)$ cat /sys/class/leds/blue_led/triggernone [heartbeat] timer oneshot transient none# 3. 查看驱动是否加载$ lsmod | grep ledsleds_gpio 8192 2# 4. 查看内核加载日志$ dmesg | grep -i "gpio.*led\|leds.*gpio"[ 0.234567] leds-gpio: GPIO LED driver probe called![ 0.234890] leds-gpio: Registered LED blue_led[ 0.235123] leds-gpio: Registered LED green_led[ 0.235234] leds-gpio: GPIO LED driver probed with 2 LEDs# 5. 查看 LED 的设备树节点$ cat /proc/device-tree/leds/compatiblegpio-leds$ cat /proc/device-tree/leds/blue_led@1/labelblue_led# 6. 控制 LED(让蓝色 LED 亮起)$ echo 1 > /sys/class/leds/blue_led/brightness# 7. 关闭 LED$ echo 0 > /sys/class/leds/blue_led/brightness# 查看风扇设备$ ls /sys/class/hwmon/hwmon0 hwmon1 ...# 查看温度和风扇信息$ cat /sys/class/hwmon/hwmon*/namepwm-fan# 查看风扇速度$ cat /sys/class/hwmon/hwmon0/pwm1# 输出:0-255 的风速等级# 查看当前温度$ cat /sys/class/hwmon/hwmon0/temp1_input# 输出:温度(单位:毫度)# 查看驱动日志$ dmesg | grep -i pwm-fan[ 0.456789] pwm-fan: PWM fan initialized[ 0.457012] pwm-fan: Cooling device registered可能的原因:none !important
调试步骤:none !important
# 查看可用的 trigger$ cat /sys/class/leds/blue_led/triggernone [heartbeat] timer oneshot transient none# 手动设置 trigger$ echo timer > /sys/class/leds/blue_led/trigger # 改为定时器闪烁$ echo 1000 > /sys/class/leds/blue_led/delay_on # 闪烁间隔 1 秒$ echo 1000 > /sys/class/leds/blue_led/delay_off可能的原因:none !important
调试步骤:none !important
# 1. 检查 GPIO 状态$ gpioinfo | grep -A2 "gpio3\|PA6\|PB1"# 2. 手动测试 GPIO$ echo 102 > /sys/class/gpio/export # GPIO3_A6$ echo out > /sys/class/gpio/gpio102/direction$ echo 1 > /sys/class/gpio/gpio102/value # LED 应该亮$ echo 0 > /sys/class/gpio/gpio102/value # LED 应该灭# 3. 查看 pinctrl 配置$ cat /sys/kernel/debug/pinctrl/pinctrl-rockchip/pingroups | grep leds最常见的原因:compatible 不匹配none !important
# 检查设备树中的 compatible$ cat /proc/device-tree/leds/compatiblegpio-leds# 检查驱动的 of_match_table(从内核日志看)$ dmesg | grep "compatible"# 如果 compatible 不匹配,驱动和设备就无法配对# 例如:设备树中写的是 "gpio-led"(少了 s)# 但驱动中是 "gpio-leds"(有 s)# 就会配对失败!// 修改设备树中的 default-triggerblue_led@1 { gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_HIGH>; label = "blue_led"; // 改为常亮 linux,default-trigger = "default-on"; // 或者改为 timer 定时闪烁 // linux,default-trigger = "timer"; // linux,default-trigger-delay-ms = <1000>;};本章节讲述了Linux设备树与驱动的匹配机制。核心很简单:none !important
compatible 属性说明这是什么设备of_match_table 说明能驱动什么设备probe()如果想要看更加具体的,考虑跟随代码进行查看,这里还有一篇其他推荐:none !important
https://www.cnblogs.com/lyndonlu/articles/15723743.htmlnone !important
往期推荐
设备树:每一个 Linux 驱动工程师的起点
嵌入式软件开发求职指南
A/B 分区 OTA 升级机制与 U-Boot 实现
嵌入式系统 OTA 固件升级
Bootloader从启动到内核记录
2026 AI编程入门的基础概念
嵌入式Linux驱动开发常见坑
AI 编程工具发展太快了
嵌入式软件面试八股文(四) - Linux 内核驱动篇
嵌入式软件面试八股文(三) - 数据结构
嵌入式软件面试八股文(二) - 操作系统
嵌入式软件面试八股文(一)-C语言基础篇
戳“阅读原文”一起来充电吧!