LINUX驱动开发: 设备和驱动是怎么匹配的?

在没有设备树的"上古时代",我们需要为每一个单板(Board)编写一个专属的C文件(如 arch/arm/mach-xxx/board-yyy.c),在里面用代码定义所有的硬件设备和资源。这种方式导致内核代码与特定硬件高度耦合,可移植性极差。

设备树的出现,就是为了解决这个问题。它的核心思想是:

  • 硬件描述 (What) :由设备树(.dts 文件)负责,说明"这里有什么硬件,它在哪,用什么资源"。

  • 硬件驱动 (How) :由驱动程序(.c 文件)负责,说明"我能驱动什么样的硬件,以及如何驱动它"。

用设备树描述硬件的方式下,设备和驱动的匹配过程:

**第一步:**内核启动后,OF(Open Firmware)子系统会率先被初始化。

  1. 解析DTB:它会读取Bootloader传入内存中的设备树二进制文件(DTB)。

  2. 创建platform_device :当它遍历到一个代表SoC片上外设的节点时(比如一个UART控制器),它会在内核中动态创建一个 struct platform_device 实例。这可以看作是硬件设备在内核中的"数字孪生"。

  3. 信息填充 :这个 platform_device 结构体会被填充从设备树节点中解析出的信息,例如寄存器地址(reg属性)、中断号(interrupts属性)等资源。

  4. 注册到总线 :最后,内核调用 platform_device_register(),将这个代表具体硬件的 platform_device 注册到一条名为"平台总线"的虚拟总线上,静静地等待着能驾驭它的驱动出现。

设备树示例

复制代码
i2c0: i2c@fe7d0000 {
    compatible = "rockchip,rk3588-i2c"; // 关键的"身份标识"
    reg = <0x0 0xfe7d0000 0x0 0x1000>;
    interrupts = <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru PCLK_I2C0>, <&cru SCLK_I2C0>;
    status = "disabled";
};
第二步:驱动"报到" - 驱动程序的注册

驱动程序可以被静态编译进内核,也可以作为模块(.ko)动态加载。

  1. 驱动初始化 :当驱动被加载时,它的 module_init() 函数会被调用。

  2. 定义platform_driver :在驱动代码中,开发者会定义一个 struct platform_driver 实例,它代表了整个驱动的功能集合。

  3. 提供"认领清单" :在这个结构体中,一个名为 .driver.of_match_table 的成员是关键。它指向一个 of_device_id 数组,这个数组里清晰地列出了本驱动支持的所有设备的 compatible 字符串。

  4. 注册到总线 :驱动调用 platform_driver_register(),将自己注册到平台总线上,表明"我已经准备就绪,可以开始工作了"。

驱动代码示例 (.c)

复制代码
// 定义本驱动能处理的设备列表(认领清单)
static const struct of_device_id rockchip_i2c_of_match[] = {
    { .compatible = "rockchip,rk3588-i2c" }, // 声明可以驱动这个设备
    { .compatible = "rockchip,rk3399-i2c" },
    { /* 数组结束符 */ }
};

static struct platform_driver rockchip_i2c_driver = {
    .probe = rockchip_i2c_probe, // 匹配成功后调用的函数
    .remove = rockchip_i2c_remove,
    .driver = {
        .name = "i2c-rockchip",
        .of_match_table = rockchip_i2c_of_match, // 关联"认领清单"
    },
};

// 通过 module_init 宏在加载时注册这个 platform_driver
module_init(platform_driver_register(&rockchip_i2c_driver));
第三步:内核"牵线" - 匹配逻辑核心

当一个新的驱动被注册到平台总线时,总线会自动为它触发一次匹配扫描。

  • 遍历设备 :平台总线会遍历总线上所有尚未绑定驱动的 platform_device

  • 比较compatible :对于每一个设备,总线会取出其compatible属性字符串,然后与新注册驱动的 of_match_table 列表中的每一项进行对比。

  • 匹配成功:一旦发现完全相同的字符串,匹配即告成功!内核的驱动核心会将设备与驱动进行"绑定",在彼此的结构体中记录对方的指针。

第四步:大功告成 - 调用 probe 函数

绑定成功是结果,但驱动的真正工作才刚刚开始。

  • 调用probe :匹配成功后,内核会立刻调用 platform_driver 中定义的 .probe 函数。

  • 传递设备信息 :调用时,会将匹配上的 platform_device 指针作为参数传给 probe 函数。

  • 硬件初始化probe 函数通过这个参数,就能获取到设备的所有硬件资源(寄存器地址、中断号等),然后执行内存映射(ioremap)、请求中断(request_irq)等一系列硬件初始化操作。

probe 函数成功返回后,一个设备就真正地"活"了起来,准备好为上层提供服务。

总结

整个流程清晰而高效,形成了一个完美的闭环:

设备树 通过compatible属性提供硬件的"身份ID" -> 内核 解析并为之创建platform_device -> 驱动 通过of_match_table声明自己能处理的"身份ID" -> 平台总线 对比两者ID,若匹配成功 -> 调用驱动的probe函数完成最终的初始化。

相关推荐
国际云,接待13 分钟前
出海东南亚无忧:腾讯云如何凭借本地合作与全球节点,保障游戏和电商业务合规流畅?
大数据·服务器·网络·云计算·腾讯云
ejinxian17 分钟前
Linux 虚拟化技术 KVM/ESXI/Docker
linux·运维·docker·qemu·openvz
z2023050839 分钟前
linux之arm SMMUv3 故障和错误(4)
linux·运维·arm开发
攒钱植发42 分钟前
嵌入式Linux——解密 ARM 性能优化:LDR 未命中时,为何 STR 还能“插队”?
linux·arm开发·c++·性能优化
是孑然呀1 小时前
【钉钉多元表格(自动化)】钉钉群根据表格 自动推送当天值日生信息
运维·自动化·钉钉
天外飞雨1 小时前
各传感器消息解析
linux
一匹电信狗1 小时前
【C++】哈希表详解(开放定址法+哈希桶)
服务器·c++·leetcode·小程序·stl·哈希算法·散列表
逐风&者1 小时前
CentsOS 7 “Could not resolve host: mirrorlist.centos.org; 未知的错误”问题解决
linux·运维·centos
路由侠内网穿透.2 小时前
本地部署网站流量分析工具 Matomo 并实现外部访问
运维·服务器·远程工作
dnpao2 小时前
在服务器已有目录中部署 Git 仓库
运维·服务器·git