在最近项目中涉及到了linux的驱动层与应用层之间的交互,在项目过程中发现对这两个层级之间应该怎么相互交互还不胜了解,因此本片笔记对这个驱动层与应用层之间的相互交互进行记录学习;在Linux内核开发中,设备树(Device Tree,DTS)与Platform驱动之间的匹配是一个关键机制,它实现了硬件描述与驱动代码的解耦。
该篇笔记手机通过学习下列文章进行学习的
1:《MX6U嵌入式Linux驱动开发指南V1.71》
在Linux内核中,总线(Bus)、设备(Device)和驱动(Driver) 是设备模型的核心概念,而设备树(Device Tree, DTS) 是一种硬件描述机制。
总线(Bus):
一种抽象层,负责管理设备和驱动的匹配逻辑。
例如:platform_bus_type
(虚拟总线)、PCI_bus_type
(物理总线)。
功能:
注册设备和驱动。
触发设备和驱动的匹配(通过match()
函数)。
管理设备和驱动的生命周期。
设备(Device):
表示硬件设备的内核对象(如struct platform_device
)。
包含硬件资源(地址、中断等)和元数据(名称、ID等)。
驱动(Driver):
实现设备操作的内核模块(如struct platform_driver
)。
定义设备匹配规则(如compatible
字符串)和操作接口(如probe()
/remove()
)。
匹配流程:
设备注册 → 总线发现设备 → 遍历驱动列表 → 调用驱动的match() → 匹配成功 → 调用驱动的probe()
驱动注册 → 总线发现驱动 → 遍历设备列表 → 调用驱动的match() → 匹配成功 → 调用驱动的probe()
虚拟总线:
用于管理无物理总线的设备(如SoC内部外设:UART、GPIO、定时器等)。
总线名称为platform
,对应的设备为platform_device
,驱动为platform_driver
。
设备来源:
设备树(主流方式):
内核将设备树中的节点(如compatible = "vendor,device"
)转换为platform_device
。
旧式硬编码:
通过platform_device_register()
手动注册(常见于未使用设备树的旧内核)。
设备树的作用:
静态描述硬件信息(如寄存器地址、中断号)。
被内核解析后生成platform_device
,并注册到platform
总线。
设备树本身不是总线,而是总线上设备的“数据源”。(一开始搞混了以为总线就是设备树)
设备树 ≠ 总线,但设备树为Platform总线提供了设备信息。
关键流程:
设备树节点 → 内核解析 → 生成platform_device → 注册到platform总线 → 与platform_driver匹配
硬件描述文件:
提供硬件资源的抽象描述(如reg
、interrupts
)。
支持动态配置(同一内核镜像适配不同硬件版本)。
与总线的独立性:
设备树可以描述多种总线的设备(如platform
、i2c
、spi
)。
例如:
// Platform设备
uart@1000 { compatible = "ns16550"; reg = <0x1000 0x100>; };
// I2C设备
i2c@2000 {
compatible = "vendor,i2c-controller";
eeprom@50 { compatible = "microchip,24c02"; reg = <0x50>; };
};
Platform设备:
设备树节点通过compatible
匹配platform_driver
。
其他总线设备:
设备树节点可能对应其他总线类型(如I2C、SPI),由各自总线控制器处理。
// 示例:mydevice节点
mydevice@12340000 {
compatible = "vendor,mydev-1.0";
reg = <0x12340000 0x1000>;
interrupts = <0 45 4>;
};
(1): 解析DTB文件生成device_node
结构体
(2):将compatible
匹配的节点转换为platform_device
static const struct of_device_id mydev_ids[] = {
{ .compatible = "vendor,mydev-1.0" },
{}
};
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.driver = {
.name = "mydev",
.of_match_table = mydev_ids,
},
};
module_platform_driver(mydev_driver);
(1):驱动加载时遍历所有未绑定的platform_device
(2):通过compatible
字符串精确匹配
(3):成功则调用驱动的probe()函数
整体流程可以梳理为:
DTS节点 → 内核解析 → Platform Device ↔ 匹配机制 ↔ Platform Driver
该实例使用的是官网linux-6.12.20的源码,路径\linux-6.12.20\arch\arm\boot\dts\xilinx\zynq-7000.dtsi下的dtsi文件;以其中的pmu驱动为例子:
pmu@f8891000 {
compatible = "arm,cortex-a9-pmu";
interrupts = <0 5 4>, <0 6 4>;//type=0(SPI), number=5(中断号), flags=4(触发方式)
interrupt-parent = <&intc>;
reg = <0xf8891000 0x1000>,
<0xf8893000 0x1000>;
};
intc: interrupt-controller@f8f01000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xF8F01000 0x1000>,
<0xF8F00100 0x100>;
};
首先dts中的compatible匹配对应
static const struct of_device_id armpmu_of_device_ids[] = {
{ .compatible = "arm,cortex-a9-pmu", .data = armv7_a9_pmu_init },
{}
};
然后就是interrupts参数对应的是使用的中断,在现在一般的arm架构中使用的中断都为gic中断,具体可以查询dtsi设备树文件中的interrupt-parent = <&intc> 的intc功能:
每个中断描述符为
:
type:0
表示SPI(Shared Peripheral Interrupt)
GIC中断分为三类:
类型 | 名称 | 设备树中的type 字段 |
中断号范围 | 用途 | 设备树示例 |
---|---|---|---|---|---|
SPI | Shared Peripheral | 0 |
32-1020 | 外设共享中断(如UART、PMU) | <0 116 4> |
PPI | Private Peripheral | 1 |
16-31 | CPU私有中断(如本地定时器) | <1 13 0xf01> |
SGI | Software Generated | 无(不通过设备树配置) | 0-15 | 核间软件触发中断(如IPI) | 无设备树定义 |
number:GIC中断号(需查表确认)
flags:4
表示高电平触发(ARM文档定义)
查阅TRM Table 7-3:
中断源 | GIC中断号 |
---|---|
PMU Interrupt (CPU0) | 5 |
PMU Interrupt (CPU1) | 6 |