跳到主要内容PetaLinux 设备树配置深度剖析与驱动集成 | 极客日志C
PetaLinux 设备树配置深度剖析与驱动集成
PetaLinux 设备树通过 .dts 文件描述硬件拓扑结构,由内核编译为 .dtb 并加载。内核依据 compatible 字段匹配驱动,实现即插即用。管理上应使用 system-user.dtsi 进行增量修改而非直接编辑自动生成文件。驱动加载依赖 of_match_table 机制,调试时可利用 dmesg 和 status 属性排查问题。掌握分层设计与模块化开发能有效提升外设集成效率。
PetaLinux 设备树配置深度剖析与驱动集成
在现代嵌入式开发中,面对 Xilinx Zynq 系列、Zynq UltraScale+ MPSoC 乃至 Versal ACAP 这类高度异构的平台,软硬件协同设计是工程落地的核心能力。PetaLinux 作为 Xilinx 官方推荐的嵌入式 Linux 构建工具链,承担着将 FPGA 逻辑与 ARM 处理器无缝融合的关键角色。而在这整套流程中,设备树(Device Tree)则是连接硬件定义与操作系统之间的桥梁——它决定了 Linux 是否能正确识别你的 AXI IP、I²C 传感器、GPIO 外设,甚至是你自己定制的加速模块。
设备树的本质:让 Linux'看见'你的硬件
传统嵌入式系统会把外设信息硬编码进内核源码,比如直接写死某个 UART 的基地址。这种方式对于固定板卡尚可接受,但在 Zynq 这种 PS+PL 动态重构的架构下,显然行不通。于是,设备树应运而生。
它到底是什么?
简单说,设备树就是一个描述硬件拓扑结构的数据文件,用文本格式(.dts)编写,最终被编译成二进制(.dtb),由 U-Boot 传给 Linux 内核。内核根据这份'地图',自动创建对应的设备实例,并尝试加载匹配的驱动。
在 Xilinx 平台上,这个文件通常叫 system-top.dts 或 zynq-7000.dtsi,由 PetaLinux 基于 HDF/XSA 自动生成初始版本,开发者在此基础上进行扩展或修改。
设备树是如何工作的?三步走通启动全流程
要真正掌握设备树,必须清楚它在整个启动过程中的生命周期。
第一步:硬件建模 —— .dts 文件结构解析
/ {
model = "Xilinx Zynq"
compatible = "xlnx,zynq-7000"
cpus { ... }
memory { ... }
amba { // AMBA 总线容器,对应 PS 端外设
uart0: serial@e0001000 {
compatible = "xlnx,xuartps", "cdns,uart-r1p8"
reg = <0xe0001000 0x1000>
interrupts = <0 31 4>
clocks = <&clkc 14>
status = "okay"
}
}
amba_pl: amba_pl { // 可编程逻辑区域
ranges
custom_ip@43c00000 {
compatible = "mycompany,accel-ip-1.0"
reg = <0x43c00000 0x10000>
interrupts = <0 61 4>
}
}
}
| 字段 | 含义 |
|---|
compatible | 驱动匹配标识,最重要! |
reg | 寄存器物理地址范围 |
interrupts | 中断号(GIC 编号)及触发类型 |
clocks | 所依赖的时钟源引用 |
status | okay 表示启用,disabled 表示关闭 |
其中 compatible 是灵魂字段——它告诉内核:'我是一个什么样的设备',从而决定加载哪个驱动。
第二步:编译与加载 —— .dts → .dtb 的转化之旅
设备树源文件不能直接运行,需要通过 Device Tree Compiler (dtc) 编译为二进制 Blob:
dtc -I dts -O dtb -o system.dtb system-top.dts
在 PetaLinux 中,这一步是全自动的。当你执行 petalinux-build 时,系统会:
- 解析
project-spec/dts/system-user.dtsi
- 合并自动生成的
system-conf.dtsi
- 输出最终的
image/linux/system.dtb
该文件会被打包进 BOOT.BIN 或放在 boot 分区,由 U-Boot 加载至内存并传递给内核。
第三步:内核解析 —— platform_device 的诞生
Linux 启动后,内核开始遍历 .dtb 中的每一个节点:
- 对于 AMBA 总线下的设备(如 UART、SPI),生成
amba_device;
- 对于普通总线设备(如 GPIO、I²C 设备),生成
platform_device;
- 然后查找所有注册过的驱动,检查其
of_match_table 是否与当前节点的 compatible 匹配;
- 若匹配成功,调用驱动的
.probe() 函数完成初始化。
整个过程无需任何硬编码,实现了真正的'即插即用'。
PetaLinux 怎么管理设备树?别再手动改 system-top 了!
很多初学者习惯直接编辑 system-top.dts,结果下次 petalinux-config --get-hw-description 一运行,所有改动全没了。
PetaLinux 有一套清晰的分层机制来避免这个问题。
推荐做法:使用 system-user.dtsi 做增量覆盖
正确的姿势是在 project-spec/dts/ 目录下维护两个文件:
system-top.dts:自动生成,不要动!
system-user.dtsi:用户自定义补丁,永远保留!
示例:添加一个 AXI I²C 控制器及其挂载的温度传感器 TMP102
/include/ "system-conf.dtsi"
/
amba_pl: amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges;
i2c_axi@41600000 {
compatible = "xlnx,xps-iic-2.00.a";
reg = <0x41600000 0x10000>;
interrupts = <0 29 4>;
clocks = <&clkc 15>;
#address-cells = <1>;
#size-cells = <0>;
tmp102@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
};
这样做的好处是:
✅ 修改独立于自动生成内容
✅ 易于版本控制和迁移
✅ 支持多人协作开发
每次重建项目时,只需重新导入 HDF/XSA + 复制 system-user.dtsi 即可快速恢复配置。
驱动是怎么被加载的?深入匹配机制
光有设备树还不够,还得有对应的驱动程序才行。那么,Linux 是怎么知道该用哪个驱动呢?
内核驱动的标准模板
以标准的 TMP102 温度传感器驱动为例(位于 drivers/hwmon/tmp102.c):
static const struct of_device_id tmp102_of_match[] = {
{ .compatible = "ti,tmp102", },
{ .compatible = "nxp,nct75", },
{ }
};
MODULE_DEVICE_TABLE(of, tmp102_of_match);
static struct i2c_driver tmp102_driver = {
.driver = {
.name = "tmp102",
.of_match_table = of_match_ptr(tmp102_of_match),
},
.probe = tmp102_probe,
.remove = tmp102_remove,
.id_table = tmp102_ids,
};
module_i2c_driver(tmp102_driver);
注意这里的 .of_match_table 和 MODULE_DEVICE_TABLE(of, ...),它们共同构成了设备树匹配的基础。
tmp102@48 { compatible = "ti,tmp102"
就会触发匹配流程,最终调用 tmp102_probe() 初始化设备。
如果没有现成驱动怎么办?
方案一:启用模块化支持,在运行时加载 ko
petalinux-config -c kernel
选择 <M> 编译为模块,则生成 tmp102.ko,可通过 insmod tmp102.ko 动态加载。
方案二:自己写 platform 驱动(适用于自定义 IP)
比如你有个 AXI GPIO LED 控制器,可以写一个简单的驱动:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
static int led_probe(struct platform_device *pdev)
{
struct gpio_desc *desc;
const char *label = "user-led";
desc = devm_fwnode_gpiod_get(&pdev->dev, &pdev->dev.fwnode, "default", GPIOD_OUT_LOW, label);
if (IS_ERR(desc)) {
dev_err(&pdev->dev, "Failed to get GPIO\n");
return PTR_ERR(desc);
}
gpiod_set_value_cansleep(desc, 1);
platform_set_drvdata(pdev, desc);
dev_info(&pdev->dev, "Custom LED driver initialized\n");
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "gpio-leds", },
{ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
.probe = led_probe,
.driver = {
.name = "led-simple-gpio",
.of_match_table = of_match_ptr(led_of_match),
},
};
module_platform_driver(led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple GPIO LED Driver for PetaLinux");
leds {
compatible = "gpio-leds"
user_led {
label = "user-led"
gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>
default-state = "on"
}
}
然后在 petalinux-config -c kernel 中确保选中:
Device Drivers ---> GPIO Support ---> <*> GPIO Class [*] /sys/class/gpio/... (sysfs interface)
实战技巧:那些年踩过的坑与应对策略
理论讲完,来点实战经验。以下是我在多个 Zynq 项目中总结出的高频问题和解决方案。
问题 1:设备树节点写了,但驱动没加载
-
查看内核日志:
dmesg | grep -i of_parse
输出类似:
of_parse_phandle_with_args: could not find phandle
说明 clocks 或 interrupts 引用无效。
-
检查 compatible 是否拼错:
dts compatible = "ti,tmp102"
dts compatible = "ti-tmp102"
-
确认驱动已编译进内核或模块存在:
find . -name "*tmp102*"
秘籍 1:用 status = "disabled" 快速调试
不想永久删除某个接口?可以用 status 控制启停:
&spi0 { status = "disabled"
&i2c1 { status = "okay"
秘籍 2:合理利用 .dtsi 分离公共配置
project-spec/dts/
├── system-top.dts
├── system-user.dtsi
└── boards/
├── board_a.dtsi
└── board_b.dtsi
在 system-user.dtsi 中 include 不同板型配置:
#include "boards/board_a.dtsi"
秘籍 3:保留原始 HDF/XSA,便于重建
.hdf 或 .xsa 文件包含了完整的硬件连接信息,务必纳入版本管理(Git/LFS)。一旦需要升级 PetaLinux 版本或迁移到新环境,可以直接:
petalinux-create -t project -n new_proj --template zynq
petalinux-config --get-hw-description=../old_hw/design_1_wrapper.hdf
总结:设备树不是负担,而是生产力工具
🔧 设备树的核心价值在于解耦
同一内核镜像,换一个 .dtb 就能在不同硬件上运行。这对产品多型号迭代意义重大。
⚡ PetaLinux 极大简化了设备树管理
自动化生成 + 用户覆盖机制,让我们既能享受灵活性,又不必陷入底层细节。
🎯 驱动匹配靠的是 compatible 字符串
只要名字对得上,内核就能自动加载标准驱动;否则就得自己写。
- 用
system-user.dtsi 做增量修改
- 非关键驱动尽量编为模块
- 保留 HDF/XSA 用于重建
掌握这套方法论后,你会发现,无论是接入一个新的 ADC 芯片,还是集成你自己写的 AXI DMA IP,都不再是令人头疼的任务。
更重要的是,这种基于设备树的开发模式,正是向 Versal ACAP、AI Engine 等更复杂平台演进的基础。今天的每一步实践,都在为未来的系统级创新铺路。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online