1. 项目背景与核心概念
在嵌入式Linux开发领域,设备树(Device Tree)已经成为描述硬件资源的标准化方式。而将device_node转换为platform_device这一过程,正是设备树机制与Linux设备模型衔接的关键环节。这个转换过程决定了内核如何识别、管理和驱动设备树中描述的硬件资源。
我曾在多个ARM架构的嵌入式项目中处理过这类问题,比如在i.MX6平台上调试自定义外设时,就遇到过device_node未能正确生成platform_device的情况。当时花了整整两天时间才定位到问题根源——设备树中的compatible属性拼写错误。这种看似简单的错误往往会导致整个设备驱动无法加载。
2. 设备树到平台设备转换机制
2.1 设备树基础结构
设备树源文件(.dts)经过编译后会生成二进制格式的设备树blob(.dtb)。内核在启动时解析这个二进制文件,将其转换为device_node结构体组成的树形结构。每个device_node包含以下关键信息:
- node name:节点名称,如"serial@2020000"
- compatible:驱动匹配字符串,如"fsl,imx6q-uart"
- reg:寄存器地址范围
- interrupts:中断号信息
- 其他设备特定属性
2.2 转换触发时机
内核在以下两个主要阶段会进行device_node到platform_device的转换:
- 早期初始化阶段:对于标有"simple-bus"、"simple-mfd"等兼容性的节点,内核会在of_platform_default_populate_init()函数中自动转换
- 延迟探测阶段:通过device-tree的"defer-probe"机制处理有依赖关系的设备
在实际项目中,我曾遇到过一个I2C设备必须在时钟控制器初始化完成后才能转换的情况。这时就需要使用defer-probe机制,否则会导致驱动探测失败。
2.3 转换过程详解
转换的核心函数是of_platform_device_create_pdata(),其主要流程如下:
- 分配platform_device结构体
- 设置设备名称(取自device_node的name属性)
- 填充resource数组(包括内存区域、中断号等)
- 设置platform_device的dev.of_node指针指向原始device_node
- 将设备添加到全局设备列表
关键数据结构关系如下:
code复制struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name;
// ...
} → 转换为 ↓
struct platform_device {
const char *name;
int id;
struct device dev;
struct resource *resource;
// ...
}
3. 关键实现细节与调试技巧
3.1 设备树编写规范
要让device_node正确转换为platform_device,设备树编写必须符合以下规范:
-
compatible属性必须存在且格式正确:
c复制compatible = "厂商缩写,设备型号", "通用兼容性"; -
地址映射必须正确:
c复制reg = <0x02000000 0x4000>; // 起始地址 0x02000000,长度 0x4000 -
中断定义要完整:
c复制interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
我曾遇到过一个案例:工程师在设备树中漏写了interrupts属性,导致platform_device创建成功但驱动无法正常工作。这种问题往往很难通过常规日志发现,需要仔细检查/sys/firmware/devicetree/base下的节点信息。
3.2 转换条件判断
并非所有device_node都会转换为platform_device,内核通过以下条件判断:
- 节点必须有compatible属性
- 节点不能有status = "disabled"属性
- 父节点必须已转换为platform_device(除非是根节点)
- 不是特殊节点(如cpus、memory等)
可以通过在设备树中添加以下调试节点来验证转换是否成功:
c复制test-node {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
dummy@0 {
compatible = "dummy-device";
reg = <0x0 0x1000>;
};
};
3.3 转换失败排查方法
当发现device_node没有按预期转换为platform_device时,可以按以下步骤排查:
-
检查dmesg日志:
bash复制
dmesg | grep of_platform -
查看sysfs中的设备树信息:
bash复制ls /sys/firmware/devicetree/base/ cat /sys/firmware/devicetree/base/soc/serial@2020000/compatible -
检查platform设备列表:
bash复制ls /sys/bus/platform/devices/ -
使用of_*系列API手动检查:
c复制struct device_node *np = of_find_node_by_path("/soc/serial@2020000"); if (np) { printk("Node found, compatible: %s\n", of_get_property(np, "compatible", NULL)); }
4. 高级应用场景
4.1 动态加载设备树节点
在调试阶段,可以通过configfs动态加载设备树节点:
bash复制mount -t configfs none /config
mkdir /config/device-tree/overlays/my_overlay
cat my_overlay.dtbo > /config/device-tree/overlays/my_overlay/dtbo
这种方法特别适合需要频繁修改设备树配置的场景,比如调试各种传感器接口时。
4.2 自定义转换规则
通过实现of_platform_populate()函数,可以自定义转换规则。例如,只转换特定前缀的节点:
c复制int custom_of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
struct device *parent)
{
struct device_node *child;
for_each_child_of_node(root, child) {
if (of_node_name_prefix(child, "custom-")) {
of_platform_device_create(child, NULL, parent);
}
}
return 0;
}
4.3 平台数据传递
有时需要向platform_device传递额外的初始化数据,可以通过设备树的特殊属性实现:
设备树:
c复制my-device {
compatible = "custom,device";
custom-param = <0x12345678>;
};
驱动中获取:
c复制u32 param;
of_property_read_u32(pdev->dev.of_node, "custom-param", ¶m);
5. 性能优化与注意事项
5.1 转换过程优化
在大规模设备树(如包含数百个节点)中,转换过程可能影响启动时间。可以通过以下方式优化:
- 合理组织设备树结构,将同类设备放在同一总线节点下
- 对非关键设备使用模块化加载
- 在不需要的节点上添加status = "disabled"
5.2 内存管理要点
platform_device创建时会分配多个资源结构体,需要注意:
-
及时释放未使用的设备:
c复制
platform_device_unregister(pdev); -
正确管理resource数组:
c复制struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -
避免重复转换同一节点
5.3 实际项目经验
在最近的一个工业控制器项目中,我们遇到了这样的问题:系统启动后,部分设备节点未能正确生成platform_device。经过排查发现是设备树中的节点命名冲突导致的。解决方案是:
- 确保每个节点名称在兄弟节点中唯一
- 使用完整路径访问节点
- 在驱动中添加更详细的错误检查
调试这类问题时,我通常会使用以下命令组合:
bash复制# 查看设备树原始信息
dtc -I fs /sys/firmware/devicetree/base
# 检查平台设备
ls /sys/bus/platform/devices/
# 查看特定设备的资源信息
cat /sys/bus/platform/devices/serial@2020000/resource
6. 常见问题解决方案
6.1 设备未按预期创建
现象:设备树中有节点但/sys/bus/platform/devices/下没有对应设备
排查步骤:
- 确认节点有compatible属性
- 检查节点及其父节点没有status = "disabled"
- 查看dmesg是否有相关错误信息
- 确认内核配置开启了CONFIG_OF和CONFIG_OF_ADDRESS
6.2 资源映射失败
现象:驱动无法访问设备的��存器空间
解决方案:
- 检查设备树中的reg属性是否正确
- 确认ranges属性在父节点中正确定义
- 使用devm_ioremap_resource()而非直接ioremap
6.3 中断无法正常工作
现象:设备注册了中断但从未触发
排查方法:
- 确认设备树中的interrupts属性格式正确
- 检查中断控制器是否已初始化
- 使用/proc/interrupts查看中断状态
6.4 兼容性匹配失败
现象:设备已创建但驱动未绑定
解决方案:
- 确认驱动中的compatible表与设备树一致
- 检查驱动模块是否已加载
- 查看/sys/bus/platform/drivers/下的绑定状态
7. 工具链支持
7.1 设备树编译器
bash复制# 编译dts为dtb
dtc -O dtb -o my_board.dtb my_board.dts
# 反编译dtb为dts
dtc -I dtb -O dts my_board.dtb > my_board.dts
7.2 内核配置选项
确保以下配置已开启:
code复制CONFIG_OF=y
CONFIG_OF_ADDRESS=y
CONFIG_OF_IRQ=y
CONFIG_OF_DEVICE=y
CONFIG_OF_DYNAMIC=y
7.3 调试工具
- dtc:设备树编译器,可用于验证dts语法
- fdtdump:查看dtb文件内容
- of_node_get()/of_node_put():引用计数调试
- /sys/firmware/devicetree:运行时查看设备树
在调试一个复杂的多核处理器项目时,我创建了一个简单的脚本来验证设备树转换:
bash复制#!/bin/bash
for node in /sys/firmware/devicetree/base/*; do
if [ -f "$node/compatible" ]; then
dev_name=$(basename $node)
compatible=$(tr -d '\0' < "$node/compatible")
if ! ls /sys/bus/platform/devices/$dev_name >/dev/null 2>&1; then
echo "WARNING: $dev_name ($compatible) not created"
fi
fi
done
这个脚本可以帮助快速定位哪些设备节点没有被正确转换。
