1. 设备树简介
设备树(Device Tree, DT)是一种描述硬件信息的数据结构,主要用于 无固化 BIOS/ACPI 的嵌入式系统,如 ARM 体系结构。Linux 内核通过解析设备树,获取硬件拓扑信息,并在驱动程序中进行匹配,使驱动能够正确初始化设备。
设备树的主要作用:
- 描述 SoC(片上系统)的硬件资源,如 CPU、内存、总线、外设等。
- 使内核代码与硬件解耦,增强可移植性。
- 提供统一的硬件抽象层,支持不同的 SoC。
2. 设备树基本结构
设备树以 文本格式(.dts) 编写,最终会被编译成 二进制设备树 blob(.dtb),在内核启动时传递给内核。
示例:
/dts-v1/;
/ {
model = "i.MX8M Plus EVK";
compatible = "fsl,imx8mp-evk";
memory {
device_type = "memory";
reg = <0x40000000 0x8000000>; // 128MB 内存
};
soc {
uart0: serial@30860000 {
compatible = "fsl,imx-uart";
reg = <0x30860000 0x10000>;
interrupts = <0 110 4>;
clocks = <&clk IMX8MP_CLK_UART1>;
};
};
};
关键属性解析:
compatible
:用于匹配驱动程序。reg
:设备的寄存器地址和大小。interrupts
:中断号。clocks
:时钟信息。
3. Linux 内核解析设备树流程
内核解析设备树的主要流程如下:
- Bootloader 加载 .dtb 并传递给内核
- 例如 U-Boot 通过
fdt addr
命令设置设备树地址。
- 例如 U-Boot 通过
- 内核初始化设备树(
early_init_dt_scan_nodes
)- 解析
memory
、cpu
、chosen
等根节点信息。
- 解析
- 创建设备节点(
of_platform_populate
)- 遍历设备树,为每个节点创建
struct device_node
结构。
- 遍历设备树,为每个节点创建
- 驱动与设备树匹配(
of_match_device
)compatible
字段用于匹配对应的驱动。
- 设备初始化(
platform_driver_probe
)- 调用匹配的驱动
probe()
函数,初始化设备。
- 调用匹配的驱动
4. 设备树节点如何与驱动匹配
4.1 内核驱动匹配机制
设备树节点和驱动程序通过 compatible
字段匹配,内核的匹配机制如下:
- 设备树中的 compatible:
uart0: serial@30860000 { compatible = "fsl,imx-uart"; };
- 驱动程序中的匹配表(
of_match_table
):static const struct of_device_id imx_uart_dt_ids[] = { { .compatible = "fsl,imx-uart" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx_uart_dt_ids);
- platform_driver 结构体:
static struct platform_driver imx_uart_driver = { .driver = { .name = "imx-uart", .of_match_table = imx_uart_dt_ids, }, .probe = imx_uart_probe, };
- 驱动加载时匹配设备:
static int imx_uart_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENODEV; dev_info(dev, "IMX UART found at 0x%08x\n", res->start); return 0; }
4.2 设备树解析 API
Linux 内核提供一系列 API 来解析设备树:
API | 作用 |
---|---|
of_find_node_by_path() | 通过路径查找设备树节点 |
of_find_compatible_node() | 通过 compatible 查找节点 |
of_property_read_u32() | 读取单个 u32 属性值 |
of_property_read_string() | 读取字符串属性 |
示例:
struct device_node *np;
u32 val;
np = of_find_compatible_node(NULL, NULL, "fsl,imx-uart");
of_property_read_u32(np, "reg", &val);
5. 主要的内核代码解析
5.1 设备树解析代码
early_init_dt_scan_nodes()
内核启动时,arch/arm64/kernel/setup.c
调用 early_init_dt_scan_nodes()
解析设备树:
void __init early_init_dt_scan_nodes(void)
{
dt_root = unflatten_device_tree(initial_boot_params);
early_init_dt_check_for_initrd();
early_init_dt_check_for_chosen();
early_init_dt_scan_memory();
}
5.2 设备树到设备结构体转换
drivers/of/platform.c
的 of_platform_populate()
负责将设备树转换为 struct device
。
static int __init of_platform_populate(void)
{
struct device_node *np;
for_each_child_of_node(root, np) {
struct platform_device *pdev;
pdev = of_platform_device_create(np, "", NULL);
}
}
5.3 设备树与驱动匹配核心代码
drivers/of/device.c
中的 of_match_device()
负责 compatible
匹配:
const struct of_device_id *of_match_device(const struct of_device_id *matches, const struct device *dev)
{
while (matches->compatible[0]) {
if (of_device_is_compatible(dev->of_node, matches->compatible))
return matches;
matches++;
}
return NULL;
}
6. 结论
本文详细讲解了 设备树与 Linux 内核代码的匹配关系,分析了内核的解析过程、设备树 API、匹配机制,并通过示例代码解析驱动如何获取设备树信息。希望读者能够深入理解设备树在内核中的作用,并能在实际开发中灵活运用。