- 概述与历史背景
- DTB 格式与 FDT 二进制结构
- 内核启动时 DT 的展开:unflatten_device_tree
- device_node 结构体与树形组织
- of_find_node_by_* 系列查询 API
- DT 与 platform_device 的绑定:of_platform_populate
- 中断映射:of_irq_parse_one 与 irq_domain
- DT Overlay 动态插拔
- DT 地址转换:of_translate_address
- compatible 字符串匹配与驱动绑定
- sysfs 暴露与调试接口
- 总结与设计哲学
设备树(Device Tree,DT)起源于 Open Firmware 规范,最初用于 PowerPC 架构(Paul Mackerras,1996 年)。它的核心思想是将硬件描述从内核代码中剥离,以数据驱动的方式描述系统拓扑,从而让同一份内核二进制能够在不同硬件板卡上运行。
drivers/of/base.c:1 文件头注释记录了这段历史:
* Paul Mackerras August 1996.
* Copyright (C) 1996-2005 Paul Mackerras.
* Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
* Adapted for sparc and sparc64 by David S. Miller
* Reconsolidated from arch/x/kernel/prom.c by Stephen Rothwell and Grant Likely.
在 ARM 生态系统中,2011 年前内核为每块开发板维护大量特定代码(board files),导致 Linus Torvalds 在邮件列表中将 ARM 平台代码称为 "a fucking pain in the ass"。Device Tree 的引入解决了这一问题,使 ARM 内核真正实现了硬件无关化。
+----------------------------------------------------------+
| Bootloader (U-Boot / UEFI) |
| DTB 二进制 ---> 通过寄存器 (x0/r2) 传递给内核 |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| 内核启动阶段 |
| early_init_dt_scan() <- 早期扫描:内存/命令行 |
| unflatten_device_tree() <- 将 FDT 展开为 device_node 树 |
| of_alias_scan() <- 扫描 /aliases 节点 |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| device_node 树(内存中) |
| |
| of_root ("/") |
| +-- cpus/ |
| +-- memory@80000000 |
| +-- soc/ |
| +-- uart@ff000000 <- compatible = "ns16550a" |
| +-- i2c@ff010000 |
| +-- gpio@ff020000 |
+----------------------------------------------------------+
|
of_platform_populate()
|
v
+----------------------------------------------------------+
| platform_device 实例 |
| uart.ff000000 i2c.ff010000 gpio.ff020000 |
| | | | |
| platform_driver platform_driver platform_driver |
| (compatible 匹配) |
+----------------------------------------------------------+
Device Tree 子系统在内核中的源文件主要集中在:
drivers/of/base.c— 核心 API:节点查找、属性读取、compatible 匹配drivers/of/fdt.c— FDT 解析与 unflattendrivers/of/platform.c— DT 节点到 platform_device 的映射drivers/of/irq.c— 中断树解析drivers/of/address.c— 地址转换drivers/of/overlay.c— 动态 overlay 支持include/linux/of.h— 公开 API 声明与核心结构体
Device Tree Blob(DTB)是设备树源文件(.dts)经 dtc 编译器编译后得到的二进制形式,也称为 Flattened Device Tree(FDT)。其内存布局如下:
+----------------------------------+ <-- blob 起始地址
| fdt_header (48 字节) |
| magic: 0xD00DFEED | 大端序魔数
| totalsize: 整个 blob 大小 |
| off_dt_struct: structure block 偏移 |
| off_dt_strings: strings block 偏移 |
| off_mem_rsvmap: memory reserve block 偏移 |
| version: 17 |
| last_comp_version: 16 |
| boot_cpuid_phys: 启动 CPU ID |
| size_dt_strings: strings block 大小 |
| size_dt_struct: structure block 大小 |
+----------------------------------+
| memory reservation block |
| (物理地址保留区域列表) |
+----------------------------------+
| structure block |
| FDT_BEGIN_NODE "/" |
| property "compatible" = ... |
| property "#address-cells" = 2 |
| FDT_BEGIN_NODE "cpus" |
| FDT_BEGIN_NODE "cpu@0" |
| FDT_END_NODE |
| FDT_END_NODE |
| FDT_BEGIN_NODE "soc" |
| ... |
| FDT_END_NODE |
| FDT_END_NODE |
| FDT_END |
+----------------------------------+
| strings block |
| 属性名字符串池(以 NUL 分隔) |
+----------------------------------+
Structure block 中的 token 类型:
FDT_BEGIN_NODE(0x00000001):节点开始FDT_END_NODE(0x00000002):节点结束FDT_PROP(0x00000003):属性(长度 + strings block 偏移 + 值)FDT_NOP(0x00000004):填充FDT_END(0x00000009):整棵树结束
内核在 MMU 建立之前就必须从 FDT 中提取内存布局。drivers/of/fdt.c 提供了两阶段扫描机制:
阶段一:早期扫描(flat blob 直接操作)
early_init_dt_verify() <- 验证 FDT 头部,初始化 initial_boot_params
|
early_init_dt_scan_nodes() <- 扫描关键节点(drivers/of/fdt.c:1218)
|-- early_init_dt_scan_chosen() <- /chosen: cmdline, initrd
|-- early_init_dt_scan_memory() <- /memory: 物理内存布局
`-- early_init_dt_check_for_usable_mem_range()
initial_boot_params 是指向原始 FDT blob 的全局指针(drivers/of/fdt.c:459):
// drivers/of/fdt.c:459
void *initial_boot_params __ro_after_init;
phys_addr_t initial_boot_params_pa __ro_after_init;__ro_after_init 标注意味着该指针在 init 阶段完成后将变为只读,防止后续误修改。
FDT 有效性检查(drivers/of/fdt.c:376):
// drivers/of/fdt.c:376
if (fdt_check_header(blob)) {
pr_err("Invalid device tree blob header\n");
return NULL;
}fdt_check_header() 来自 lib/libfdt,会校验魔数、版本号和 totalsize 的合理性。
在扫描内存节点之前,需要先确定地址/大小单元格数(drivers/of/fdt.c:1002-1013):
// drivers/of/fdt.c:1002-1013
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (!WARN(!prop, "No '#size-cells' in root node\n"))
dt_root_size_cells = be32_to_cpup(prop);
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (!WARN(!prop, "No '#address-cells' in root node\n"))
dt_root_addr_cells = be32_to_cpup(prop);这两个值决定了后续 reg 属性的解析宽度(32 位平台通常为 1,64 位平台通常为 2)。
FDT blob 在加载时会计算 CRC32 校验值(drivers/of/fdt.c:1208),用于后续在 sysfs 暴露 /sys/firmware/fdt 时的完整性验证:
// drivers/of/fdt.c:1208
of_fdt_crc32 = crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params));在展开之前,内核可以直接在 flat blob 上做兼容性检查(drivers/of/fdt.c:679-698):
// drivers/of/fdt.c:679-698
static int of_fdt_is_compatible(const void *blob,
unsigned long node, const char *compat)
{
const char *cp;
int cplen;
unsigned long l, score = 0;
cp = fdt_getprop(blob, node, "compatible", &cplen);
if (cp == NULL)
return 0;
while (cplen > 0) {
score++; /* 越靠后分越低 */
if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
return score;
l = strlen(cp) + 1;
cp += l;
cplen -= l;
}
return 0;
}返回值是位置索引(score),越小表示越具体的匹配,of_flat_dt_match_machine() 用此找到最佳匹配的 machine_desc(drivers/of/fdt.c:759)。
以 ARM64 为例,unflatten_device_tree() 在内存子系统初始化后、驱动注册之前被调用(arch/arm64/kernel/setup.c:342):
start_kernel()
└── setup_arch()
└── unflatten_device_tree() <- arch/arm64/kernel/setup.c:342
└── __unflatten_device_tree() <- drivers/of/fdt.c:351
|-- unflatten_dt_nodes() [第一遍:计算内存需求]
|-- early_init_dt_alloc_memory_arch() [分配内存]
`-- unflatten_dt_nodes() [第二遍:填充 device_node]
drivers/of/fdt.c:1272-1299:
// drivers/of/fdt.c:1272
void __init unflatten_device_tree(void)
{
void *fdt = initial_boot_params;
fdt_scan_reserved_mem_reg_nodes();
/* 若 bootloader 未提供 DTB,使用编译进内核的空根节点 */
if (!fdt) {
fdt = (void *) __dtb_empty_root_begin;
of_fdt_crc32 = crc32_be(~0, fdt, fdt_totalsize(fdt));
fdt = copy_device_tree(fdt);
}
/* 展开 FDT,结果写入全局变量 of_root */
__unflatten_device_tree(fdt, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
/* 建立 /chosen 和 /aliases 索引 */
of_alias_scan(early_init_dt_alloc_memory_arch);
unittest_unflatten_overlay_base();
}__unflatten_device_tree() 采用经典的两遍扫描策略(drivers/of/fdt.c:351-417):
// drivers/of/fdt.c:382
/* 第一遍:dryrun=true,仅计算所需内存大小 */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
...
size = ALIGN(size, 4);
/* 分配精确大小的内存,末尾放哨兵值 0xdeadbeef */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
memset(mem, 0, size);
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
// drivers/of/fdt.c:401
/* 第二遍:实际填充 device_node 和 property 结构 */
ret = unflatten_dt_nodes(blob, mem, dad, mynodes);
// drivers/of/fdt.c:403-405
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warn("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));哨兵值 0xdeadbeef 用于检测数组越界写入:若第二遍结束后哨兵被覆盖,说明内存大小计算有误。
unflatten_dt_nodes() 使用 fdt_next_node() 遍历 FDT structure block,维护一个深度为 64 的节点栈(drivers/of/fdt.c:270-333):
// drivers/of/fdt.c:277-278
#define FDT_MAX_DEPTH 64
struct device_node *nps[FDT_MAX_DEPTH];深度限制为 64 层,超出时通过 WARN_ON_ONCE 告警并跳过。
每轮迭代调用 populate_node(),结果存入 nps[depth+1]:
// drivers/of/fdt.c:299-317
for (offset = 0;
offset >= 0 && depth >= initial_depth;
offset = fdt_next_node(blob, offset, &depth)) {
if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))
continue;
if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
!of_fdt_device_is_available(blob, offset))
continue;
ret = populate_node(blob, offset, &mem, nps[depth],
&nps[depth+1], dryrun);每个 FDT 节点对应一次 populate_node() 调用(drivers/of/fdt.c:191-235):
// drivers/of/fdt.c:210-223
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + len,
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
of_node_init(np);
/* full_name 紧跟在 device_node 结构体之后,零拷贝设计 */
np->full_name = fn = ((char *)np) + sizeof(*np);
memcpy(fn, pathp, len);
if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child; /* 头插法建立兄弟链表 */
dad->child = np;
}
}device_node 和其 full_name 字符串被分配在连续的内存块中,减少碎片。兄弟节点通过头插法形成链表,因此遍历顺序与 DTS 相反,需要 reverse_nodes() 修正(drivers/of/fdt.c:237-259)。
populate_properties() 解析节点的所有属性(drivers/of/fdt.c:94-189):
// drivers/of/fdt.c:106-108
for (cur = fdt_first_property_offset(blob, offset);
cur >= 0;
cur = fdt_next_property_offset(blob, cur)) {解析完成后,属性的 name 指针直接指向 FDT strings block,value 直接指向 FDT structure block(drivers/of/fdt.c:151-153):
// drivers/of/fdt.c:151-153
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;这是零拷贝设计——属性数据不会被复制,节省内存。
特殊处理:
phandle和linux,phandle:提取并填充np->phandle(drivers/of/fdt.c:138-141)name属性缺失时,从节点名自动合成(drivers/of/fdt.c:161-188)
由于头插法导致顺序反转,展开完成后需要深度优先地翻转所有子节点链表(drivers/of/fdt.c:237-259):
// drivers/of/fdt.c:237-259
static void reverse_nodes(struct device_node *parent)
{
struct device_node *child, *next;
/* 递归处理所有子树 */
child = parent->child;
while (child) {
reverse_nodes(child);
child = child->sibling;
}
/* 翻转当前层的子节点链表 */
child = parent->child;
parent->child = NULL;
while (child) {
next = child->sibling;
child->sibling = parent->child;
parent->child = child;
child = next;
}
}所有 device_node 和 property 从同一个连续内存块中顺序分配(drivers/of/fdt.c:82-92):
// drivers/of/fdt.c:82-92
static void *unflatten_dt_alloc(void **mem, unsigned long size,
unsigned long align)
{
void *res;
*mem = PTR_ALIGN(*mem, align);
res = *mem;
*mem += size;
return res;
}所有节点和属性共享一块连续内存,缓存友好,可一次性释放整块。
struct device_node 定义于 include/linux/of.h:48-68:
// include/linux/of.h:48-68
struct device_node {
const char *name; /* 节点名(不含地址部分) */
phandle phandle; /* 唯一句柄,用于跨节点引用 */
const char *full_name; /* 完整路径名,如 "uart@ff000000" */
struct fwnode_handle fwnode; /* 统一固件节点句柄,连接驱动模型 */
struct property *properties; /* 属性链表头 */
struct property *deadprops; /* 已删除的属性(用于 overlay 回滚) */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 第一个子节点 */
struct device_node *sibling; /* 下一个兄弟节点 */
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj; /* sysfs 集成 */
#endif
unsigned long _flags; /* 位标志集合 */
void *data; /* 平台私有数据 */
};struct property 定义于 include/linux/of.h:28-42:
// include/linux/of.h:28-42
struct property {
char *name; /* 属性名 */
int length; /* 属性值字节数 */
void *value; /* 属性值(大端序原始数据) */
struct property *next; /* 单向链表 */
};_flags 字段的各位含义(include/linux/of.h:150-155):
| 标志位 | 值 | 含义 |
|---|---|---|
OF_DYNAMIC |
1 | 节点和属性通过 kmalloc 动态分配(overlay 用) |
OF_DETACHED |
2 | 已从主设备树脱离 |
OF_POPULATED |
3 | 已为此节点创建 platform_device |
OF_POPULATED_BUS |
4 | 已为子节点创建 platform_bus |
OF_OVERLAY |
5 | 为 overlay 分配的节点 |
OF_OVERLAY_FREE_CSET |
6 | 在 overlay cset 释放中 |
操作标志位的内联函数(include/linux/of.h:190-209):
// include/linux/of.h:190-209
static inline int of_node_check_flag(const struct device_node *n, unsigned long flag)
{
return test_bit(flag, &n->_flags);
}
static inline int of_node_test_and_set_flag(struct device_node *n, unsigned long flag)
{
return test_and_set_bit(flag, &n->_flags);
}
static inline void of_node_set_flag(struct device_node *n, unsigned long flag)
{
set_bit(flag, &n->_flags);
}of_root ("/")
|
+--[child]--> cpus
| |
| +--[child]--> cpu@0 --[sibling]--> cpu@1
|
+--[sibling]--> memory@80000000
|
+--[sibling]--> soc
| |
| +--[child]--> interrupt-controller@a0000000
| |
| +--[sibling]--> uart@ff000000
| | |
| | +--[properties]
| | compatible = "ns16550a"
| | reg = <0xff000000 0x1000>
| | interrupts = <0 1 4>
| |
| +--[sibling]--> i2c@ff010000
|
+--[sibling]--> chosen
| properties: [bootargs, stdout-path]
|
+--[sibling]--> aliases
properties: [serial0="/soc/uart@ff000000"]
// drivers/of/base.c:36-41
struct device_node *of_root; /* 设备树根节点 "/" */
struct device_node *of_chosen; /* /chosen 节点(cmdline、initrd 等) */
struct device_node *of_aliases; /* /aliases 节点 */
struct device_node *of_stdout; /* stdout 对应的设备节点 */设备树使用两种锁(drivers/of/base.c:52-57):
// drivers/of/base.c:52
DEFINE_MUTEX(of_mutex); /* 保护 of_aliases,序列化 sysfs 节点添加 */
// drivers/of/base.c:57
DEFINE_RAW_SPINLOCK(devtree_lock); /* 保护树结构遍历(child/sibling/parent) */of_mutex 是睡眠锁,用于修改树结构的慢路径;devtree_lock 是自旋锁,用于快速遍历路径,可在中断上下文使用。
为加速 of_find_node_by_phandle() 查找,内核维护一个 128 槽的哈希缓存(drivers/of/base.c:156-163):
// drivers/of/base.c:156-163
#define OF_PHANDLE_CACHE_BITS 7
#define OF_PHANDLE_CACHE_SZ BIT(OF_PHANDLE_CACHE_BITS) /* 128 */
static struct device_node *phandle_cache[OF_PHANDLE_CACHE_SZ];
static u32 of_phandle_cache_hash(phandle handle)
{
return hash_32(handle, OF_PHANDLE_CACHE_BITS);
}缓存在 of_core_init() 中预热(drivers/of/base.c:200-201),每次 overlay 修改时通过 __of_phandle_cache_inv_entry() 失效相关条目(drivers/of/base.c:169-181)。
设备节点使用引用计数管理生命周期(include/linux/of.h:128-138):
// include/linux/of.h:128-138
#ifdef CONFIG_OF_DYNAMIC
extern struct device_node *of_node_get(struct device_node *node);
extern void of_node_put(struct device_node *node);
#else
static inline struct device_node *of_node_get(struct device_node *node)
{
return node; /* 非动态配置:空操作,节点永不释放 */
}
static inline void of_node_put(struct device_node *node) { }
#endif
DEFINE_FREE(device_node, struct device_node *, if (_T) of_node_put(_T))DEFINE_FREE 宏(include/linux/of.h:138)支持 gcc/clang cleanup 扩展,使节点引用可以在离开作用域时自动释放。
include/linux/of.h:269-311 中声明的节点查找函数形成一个完整体系:
按名称查找
of_find_node_by_name() <- 匹配 "name" 属性(去除 @ 后缀)
of_find_node_by_type() <- 匹配 "device_type" 属性
按路径查找
of_find_node_by_path() <- 绝对路径,如 "/soc/uart@ff000000"
of_find_node_opts_by_path() <- 路径 + 可选参数(分号后部分)
按 compatible 查找
of_find_compatible_node() <- 匹配 compatible 字符串
of_find_matching_node_and_match() <- 匹配 of_device_id 表
按句柄查找
of_find_node_by_phandle() <- 使用 phandle 数值,带哈希缓存
遍历辅助
of_find_all_nodes() <- 全局深度优先遍历
of_find_node_with_property() <- 查找拥有指定属性名的节点
of_get_next_child() <- 迭代子节点
of_get_next_available_child() <- 跳过 status = "disabled" 的子节点
drivers/of/base.c:1013-1027:
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name)
{
struct device_node *np;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
for_each_of_allnodes_from(from, np)
if (of_node_name_eq(np, name) && of_node_get(np))
break;
of_node_put(from); /* 消费调用者传入的 from 节点引用 */
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}of_node_name_eq() 在比较时会剥除节点名中的地址部分(@ 之后)(drivers/of/base.c:59-71):
// drivers/of/base.c:67-70
node_name = kbasename(np->full_name);
len = strchrnul(node_name, '@') - node_name;
return (strlen(name) == len) && (strncmp(node_name, name, len) == 0);这意味着 uart@ff000000 节点的节点名是 uart(不含地址部分)。
调用模式:from 参数在函数内部会被 of_node_put(),因此迭代模式如下:
struct device_node *np = NULL;
while ((np = of_find_node_by_name(np, "uart"))) {
/* 处理 np,下次迭代时 np 的引用计数会被减掉 */
}drivers/of/base.c:1071-1086:
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compatible)
{
struct device_node *np;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
for_each_of_allnodes_from(from, np)
if (__of_device_is_compatible(np, compatible, type, NULL) &&
of_node_get(np))
break;
of_node_put(from);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}type 参数为 NULL 时忽略 device_type 匹配,仅匹配 compatible 字符串。of_root 根节点附近的 compatible 全树扫描性能为 O(N),无索引加速。
drivers/of/base.c:1238-1268:
struct device_node *of_find_node_by_phandle(phandle handle)
{
struct device_node *np = NULL;
unsigned long flags;
u32 handle_hash;
if (!handle)
return NULL;
handle_hash = of_phandle_cache_hash(handle);
raw_spin_lock_irqsave(&devtree_lock, flags);
/* 先查 128 槽哈希缓存 */
if (phandle_cache[handle_hash] &&
handle == phandle_cache[handle_hash]->phandle)
np = phandle_cache[handle_hash];
/* 缓存未命中,全树扫描,并更新缓存 */
if (!np) {
for_each_of_allnodes(np)
if (np->phandle == handle &&
!of_node_check_flag(np, OF_DETACHED)) {
phandle_cache[handle_hash] = np;
break;
}
}
of_node_get(np);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}phandle 是 DT 中节点间引用的核心机制,例如 interrupt-parent = <&gic> 中的 &gic 就是对某个节点 phandle 的引用。中断映射路径会频繁调用此函数,哈希缓存将最坏情况下的 O(N) 全树扫描降为 O(1)。
include/linux/of.h:266-268:
#define for_each_of_allnodes_from(from, dn) \
for (dn = __of_find_all_nodes(from); dn; dn = __of_find_all_nodes(dn))
#define for_each_of_allnodes(dn) for_each_of_allnodes_from(NULL, dn)__of_find_all_nodes() 实现深度优先遍历(drivers/of/base.c:244-258):
// drivers/of/base.c:244-258
struct device_node *__of_find_all_nodes(struct device_node *prev)
{
struct device_node *np;
if (!prev) {
np = of_root;
} else if (prev->child) {
np = prev->child; /* 有子节点,向下深入 */
} else {
np = prev;
while (np->parent && !np->sibling)
np = np->parent; /* 回溯到有兄弟的祖先节点 */
np = np->sibling; /* 走向兄弟节点 */
}
return np;
}drivers/of/base.c:210-227:
// drivers/of/base.c:210-227
static struct property *__of_find_property(const struct device_node *np,
const char *name, int *lenp)
{
struct property *pp;
if (!np)
return NULL;
for (pp = np->properties; pp; pp = pp->next) {
if (of_prop_cmp(pp->name, name) == 0) {
if (lenp)
*lenp = pp->length;
break;
}
}
return pp;
}属性查找是 O(P) 线性扫描(P 为节点属性数),通常 P 很小(< 20),无需索引。
DT 节点并不会自动变成设备,而是通过 of_platform_populate() 这一"设备实例化"过程,将 DT 中描述的硬件资源转换为内核的 platform_device 对象:
of_platform_populate() <- drivers/of/platform.c:443
└── for each child of root
└── of_platform_bus_create() <- drivers/of/platform.c:325
|-- 检查 compatible 属性
|-- of_platform_device_create_pdata() <- 创建 platform_device
|-- 若匹配 simple-bus 等,递归处理子节点
`-- of_node_set_flag(OF_POPULATED_BUS)
drivers/of/platform.c:443-470:
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
device_links_supplier_sync_state_pause();
for_each_child_of_node_scoped(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc)
break;
}
device_links_supplier_sync_state_resume();
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}device_links_supplier_sync_state_pause/resume() 配对使用,防止在批量设备创建过程中过早触发 supplier 同步状态检查(可能导致 probe 推迟)。
of_platform_default_populate() 使用内置 match 表(drivers/of/platform.c:476-487):
// drivers/of/platform.c:476-484
static const struct of_device_id match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif
{}
};只有 compatible 匹配 simple-bus(或类似总线类型)的节点,其子节点才会被递归实例化为设备。这就是为什么 SoC 通常会有一个 compatible = "simple-bus" 的 soc 节点包裹所有外设节点。
跳过列表(drivers/of/platform.c:78-81):
// drivers/of/platform.c:78-81
static const struct of_device_id of_skipped_node_table[] = {
{ .compatible = "operating-points-v2", },
{}
};operating-points-v2 节点描述 CPU OPP(工作点),不应被实例化为平台设备。
drivers/of/platform.c:325-382:
// drivers/of/platform.c:337-380
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
/* strict 模式要求有 compatible 属性 */
if (strict && (!of_property_present(bus, "compatible"))) {
pr_debug("%s() - skipping %pOF, no compatible prop\n", __func__, bus);
return 0;
}
/* 跳过黑名单节点 */
if (unlikely(of_match_node(of_skipped_node_table, bus)))
return 0;
/* 防止重复创建 */
if (of_node_check_flag(bus, OF_POPULATED_BUS))
return 0;
/* ARM PrimeCell(AMBA)设备走独立路径 */
if (of_device_is_compatible(bus, "arm,primecell")) {
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
/* 创建 platform_device */
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
/* 若是总线节点,递归处理子节点 */
for_each_child_of_node_scoped(bus, child) {
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc)
break;
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}drivers/of/platform.c:151-186:
// drivers/of/platform.c:161-163
/* 检查 status 属性,跳过 disabled 节点 */
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED)) /* 原子:防止重复创建 */
return NULL;
dev = of_device_alloc(np, bus_id, parent);
...
dev->dev.bus = &platform_bus_type;
of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) { ... }
return dev;of_node_test_and_set_flag(np, OF_POPULATED) 是原子的测试-置位操作,确保同一 DT 节点不会被实例化两次。
drivers/of/platform.c:97-138:
// drivers/of/platform.c:110-126
num_reg = of_address_count(np); /* 统计 reg 属性条目数 */
if (num_reg) {
res = kzalloc_objs(*res, num_reg);
dev->num_resources = num_reg;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res); /* reg -> struct resource */
WARN_ON(rc);
}
}
/* 将 device_node 关联到 platform_device(通过 fwnode) */
device_set_node(&dev->dev, of_fwnode_handle(of_node_get(np)));
dev->dev.parent = parent ? : &platform_bus;
of_device_make_bus_id(&dev->dev); /* 自动生成设备 ID,如 "ff000000.uart" */of_device_make_bus_id() 使用节点的 reg 属性第一个地址生成设备名称,确保唯一性。
DT 中断系统基于"中断树"概念,与设备树的父子关系独立:
/ {
gic: interrupt-controller@a0000000 {
compatible = "arm,gic-400";
interrupt-controller; /* 声明为中断控制器 */
#interrupt-cells = <3>; /* 描述一个中断需要 3 个 cell */
reg = <0xa0000000 0x10000 0xa0010000 0x10000>;
};
soc {
uart@ff000000 {
interrupts = <0 37 4>; /* GIC 格式:type SPI 37, 电平触发 */
interrupt-parent = <&gic>; /* phandle 引用中断控制器 */
};
/* 级联中断控制器 */
gpio@ff020000 {
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gic>;
interrupts = <0 50 4>;
interrupt-map = <
0 0 &gic 0 51 4
1 0 &gic 0 52 4
>;
};
};
};
最常用的驱动 API(drivers/of/irq.c:39-51):
// drivers/of/irq.c:39-51
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
unsigned int ret;
if (of_irq_parse_one(dev, index, &oirq)) /* 解析 DT 中断描述 */
return 0;
ret = irq_create_of_mapping(&oirq); /* 映射到 Linux virq */
of_node_put(oirq.np);
return ret;
}drivers/of/irq.c:428-483:
of_irq_parse_one(device, index, out_irq)
|
|-- 尝试 "interrupts-extended" 属性(新格式,drivers/of/irq.c:453)
| of_parse_phandle_with_args(device, "interrupts-extended",
| "#interrupt-cells", index, out_irq)
|
`-- 若无 interrupts-extended,使用传统格式(drivers/of/irq.c:458):
1. of_irq_find_parent() 查找 interrupt-parent
2. 读取父控制器的 #interrupt-cells
3. 从 "interrupts" 属性按 index*intsize 偏移读取 intsize 个 cell
4. of_irq_parse_raw() 处理 interrupt-map 转换
关键代码(drivers/of/irq.c:458-481):
// drivers/of/irq.c:458-481
p = of_irq_find_parent(device);
if (!p || of_property_read_u32(p, "#interrupt-cells", &intsize))
return -EINVAL;
out_irq->np = p;
out_irq->args_count = intsize;
for (i = 0; i < intsize; i++) {
res = of_property_read_u32_index(device, "interrupts",
(index * intsize) + i,
out_irq->args + i);
if (res)
return res;
}
/* 处理 interrupt-map 转换 */
return of_irq_parse_raw(addr_buf, out_irq);drivers/of/irq.c:61-83:
// drivers/of/irq.c:61-83
struct device_node *of_irq_find_parent(struct device_node *child)
{
struct device_node *p;
phandle parent;
do {
/* 读 interrupt-parent 属性(phandle) */
if (of_property_read_u32(child, "interrupt-parent", &parent)) {
p = of_get_parent(child); /* 无属性,走设备树结构父节点 */
} else {
p = of_find_node_by_phandle(parent);
}
of_node_put(child);
child = p;
/* 向上遍历直到找到有 #interrupt-cells 属性的节点 */
} while (p && of_get_property(p, "#interrupt-cells", NULL) == NULL);
return p;
}中断父节点不必是设备树中的直接父节点,interrupt-parent 属性可以跨层级指向任意节点。
对于带有 interrupt-map 属性的中断控制器,需要做 specifier 转换(drivers/of/irq.c:246-413):
of_irq_parse_raw(addr, out_irq) 工作流:
while ipar != NULL:
1. 检查 ipar 是否有 interrupt-controller 属性(drivers/of/irq.c:322)
若有且无 interrupt-map -> 找到终点,返回 0
2. 读取 ipar 的 interrupt-map 属性(drivers/of/irq.c:324)
3. 读取 ipar 的 interrupt-map-mask 属性(可选)(drivers/of/irq.c:349)
4. 遍历 interrupt-map 表(drivers/of/irq.c:355-377):
每条表项 = [child addr | child irq | parent phandle
| parent addr | parent irq]
5. 用 mask 对 (addr, specifier) 做 AND 后与表项比较
6. 匹配成功 -> 提取新的 parent 和新的 specifier
7. 更新 ipar 为新父节点,继续循环
interrupt-map-abusers 黑名单(drivers/of/irq.c:96-106):部分旧平台中断控制器滥用 interrupt-map 属性,内核维护黑名单跳过标准解析。
irq_create_of_mapping() 将 of_phandle_args 转换为 Linux 虚拟中断号:
of_phandle_args {
.np = <中断控制器 device_node>
.args = {0, 37, 4} /* GIC SPI 37, 电平触发 */
.args_count = 3
}
|
v
irq_find_host(np) <- 根据 device_node 找到已注册的 irq_domain
|
v
irq_domain->ops->xlate() <- 将 args 翻译为 hwirq + trigger type
|
v
irq_create_mapping() <- 分配 Linux virq,建立 virq <-> hwirq 双向映射
|
v
返回 Linux virq 号(如 65)
当对应的中断控制器驱动尚未探测时,of_irq_get() 返回 -EPROBE_DEFER(drivers/of/irq.c:538-540),触发设备探测延迟机制,待中断控制器驱动加载后重试。
drivers/of/irq.c:491-516:
// drivers/of/irq.c:491-516
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
{
int irq = of_irq_get(dev, index);
if (irq < 0) return irq;
if (r && irq) {
/* 从 "interrupt-names" 属性获取可选名称 */
of_property_read_string_index(dev, "interrupt-names", index, &name);
*r = DEFINE_RES_IRQ_NAMED(irq, name ?: of_node_full_name(dev));
r->flags |= irq_get_trigger_type(irq);
}
return irq;
}DT Overlay 允许在系统运行时动态修改设备树,典型用途包括:
- 树莓派 HAT 扩展板的热插拔配置
- FPGA 部分重配置后添加新功能节点
- 通过 configfs 在用户空间加载 overlay
drivers/of/overlay.c:41-80:
// drivers/of/overlay.c:41-44
struct fragment {
struct device_node *overlay; /* __overlay__ 子节点(待应用的变更内容) */
struct device_node *target; /* live tree 中的目标节点 */
};
// drivers/of/overlay.c:56-80
struct overlay_changeset {
int id; /* IDR 分配的唯一标识符 */
struct list_head ovcs_list; /* 全局 ovcs_list 链表节点 */
const void *new_fdt; /* unflattened 对齐后的 FDT 副本 */
const void *overlay_mem; /* 展开后的 overlay 树内存块 */
struct device_node *overlay_root; /* 展开后的 overlay 树根 */
enum of_overlay_notify_action notify_state;
int count; /* fragments[] 数组长度 */
struct fragment *fragments; /* 片段数组 */
bool symbols_fragment; /* 是否包含 __symbols__ 节点 */
struct of_changeset cset; /* 记录所有变更操作的 changeset */
};of_overlay_fdt_apply(overlay_fdt, overlay_fdt_size, &ovcs_id, base)
|
|-- 1. __unflatten_device_tree():展开 overlay FDT(detached 模式)
|
|-- 2. of_resolve_phandles():解析 phandle 冲突
| overlay 中的 phandle 可能与 live tree 冲突
| 扫描 live tree 找最大 phandle,为 overlay 中所有 phandle 加偏移
|
|-- 3. init_overlay_changeset():初始化 changeset
| 解析 fragment@N 节点
| 找到每个 fragment 的 target-path 或 target phandle
|
|-- 4. build_changeset():构建变更集(drivers/of/overlay.c:630)
| for each fragment:
| build_changeset_next_level()
| 递归遍历 __overlay__ 子树
| 生成 OF_RECONFIG_ADD_NODE / ADD_PROPERTY 等操作
|
|-- 5. overlay_notify(OF_OVERLAY_PRE_APPLY):通知监听者
|
|-- 6. of_changeset_apply(&ovcs->cset):原子应用变更到 live tree
|
`-- 7. overlay_notify(OF_OVERLAY_POST_APPLY)
drivers/of/overlay.c:116-126:
// drivers/of/overlay.c:116-126
static DEFINE_MUTEX(of_overlay_phandle_mutex);
void of_overlay_mutex_lock(void)
{
mutex_lock(&of_overlay_phandle_mutex);
}
void of_overlay_mutex_unlock(void)
{
mutex_unlock(&of_overlay_phandle_mutex);
}of_overlay_phandle_mutex 确保 of_resolve_phandles() 和 of_overlay_apply() 之间的原子性(见注释 drivers/of/overlay.c:108-115),防止两个 overlay 同时申请 phandle 导致冲突。
drivers/of/overlay.c:131:
// drivers/of/overlay.c:131
static BLOCKING_NOTIFIER_HEAD(overlay_notify_chain);通知动作类型:
OF_OVERLAY_PRE_APPLY:应用前(回调可返回错误拒绝)OF_OVERLAY_POST_APPLY:应用后OF_OVERLAY_PRE_REMOVE:移除前(回调可返回错误拒绝)OF_OVERLAY_POST_REMOVE:移除后
overlay_notify() 函数(drivers/of/overlay.c:161-186)对每个 fragment 触发通知,只要有一个回调返回错误就中止。
Overlay 的 __symbols__ 节点记录了 overlay 内的标签路径,应用时 dup_and_fixup_symbol_prop() 将其修正为目标节点的真实路径(drivers/of/overlay.c:201-249):
原始路径:"/fragment@0/__overlay__/uart1"
修正后: "/soc/uart@ff010000"
将 overlay 树中形如 /fragment@0/__overlay__/tail 的前缀替换为 fragment 目标节点的真实路径,确保 overlay 中的标签在 live tree 中可被正确引用。
of_overlay_remove(&ovcs_id) <- drivers/of/overlay.c:1188
|
|-- overlay_notify(OF_OVERLAY_PRE_REMOVE)
|
|-- of_changeset_revert(&ovcs->cset)
| 逆序回放 changeset 中的操作:
| ADD_NODE -> 删除节点
| ADD_PROP -> 删除属性
| UPDATE_PROP -> 恢复旧属性值
|
|-- overlay_notify(OF_OVERLAY_POST_REMOVE)
|
`-- free_overlay_changeset()
若 of_changeset_revert() 失败,设置 DTSF_REVERT_FAIL 标志,后续所有 overlay 操作将被 devicetree_corrupt() 检查拒绝(drivers/of/overlay.c:98-102)。
DT 中 reg 属性描述的是相对于父总线的地址,而非 CPU 视角的物理地址。地址转换沿树向上,通过每一层的 ranges 属性完成从局部地址空间到父地址空间的映射,直到到达根节点(CPU 物理地址)。
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x00000000 0x80000000 0x10000000>;
/* 子地址0 父(CPU)地址 大小 */
uart@1000 {
reg = <0x1000 0x100>;
/* CPU 物理地址 = 0x80000000 + 0x1000 = 0x80001000 */
};
};
若节点无 ranges 属性,意味着地址空间与父节点完全一致(1:1 映射)。
drivers/of/address.c:27-38:
// drivers/of/address.c:27-38
struct of_bus {
const char *name;
const char *addresses;
int (*match)(struct device_node *parent);
void (*count_cells)(struct device_node *child, int *addrc, int *sizec);
u64 (*map)(__be32 *addr, const __be32 *range,
int na, int ns, int pna, int fna);
int (*translate)(__be32 *addr, u64 offset, int na);
int flag_cells;
unsigned int (*get_flags)(const __be32 *addr);
};内核支持多种总线地址格式:
- 默认总线:通用,使用
#address-cells/#size-cells(of_bus_default_map,drivers/of/address.c:53) - PCI 总线:3 cell 地址格式(含空间标识、总线号、设备功能号)
- ISA 总线:带 I/O 空间标识
drivers/of/address.c:585-599:
// drivers/of/address.c:585-599
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
struct device_node *host;
u64 ret;
ret = __of_translate_address(dev, of_get_parent,
in_addr, "ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR; /* 遇到逻辑 I/O 映射节点,无法转换 */
}
return ret;
}drivers/of/address.c:496-583:
// drivers/of/address.c:496-583
static u64 __of_translate_address(struct device_node *node,
struct device_node *(*get_parent)(const struct device_node *),
const __be32 *in_addr, const char *rprop,
struct device_node **host)
{
/* 初始化:获取当前节点总线信息 */
bus = of_match_bus(parent); /* 识别总线类型 */
bus->count_cells(dev, &na, &ns); /* 获取地址/大小 cell 数 */
memcpy(addr, in_addr, na * 4);
/* 循环:每轮上升一层 */
for (;;) {
dev = parent;
parent = get_parent(dev);
/* 到达根节点,当前 addr 即为 CPU 物理地址 */
if (parent == NULL)
return of_read_number(addr, na);
/* 获取父总线信息 */
pbus = of_match_bus(parent);
pbus->count_cells(dev, &pna, &pns);
/* 用 dev 节点的 "ranges" 属性将 addr 从当前总线地址空间
转换到父总线地址空间 */
if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
return OF_BAD_ADDR;
na = pna; ns = pns; bus = pbus;
}
}drivers/of/address.c:1091-1095:
// drivers/of/address.c:1091-1095
int of_address_to_resource(struct device_node *dev, int index,
struct resource *r)
{
return __of_address_to_resource(dev, index, -1, r);
}内部 __of_address_to_resource()(drivers/of/address.c:1041):
- 调用
of_get_address()获取reg属性的第index个条目 - 调用
of_translate_address()转换为 CPU 物理地址 - 填充
struct resource(包含起始地址、结束地址、标志位)
对于 DMA 操作,地址转换使用 dma-ranges 属性而非 ranges,通过 of_translate_dma_address() 完成(drivers/of/address.c:636):
// drivers/of/address.c:636
ret = __of_translate_address(dev, __of_get_dma_parent,
in_addr, "dma-ranges", &host);__of_get_dma_parent() 优先查找 interconnects 属性中的 dma-mem(drivers/of/address.c:602-618),以支持系统互联总线(如 IOMMU)的 DMA 地址映射。
compatible 属性是驱动与设备树节点绑定的核心机制,通常为从具体到通用的字符串列表:
/* 从最具体到最通用 */
compatible = "fsl,imx8mm-uart", "fsl,imx21-uart";
这意味着优先使用 imx8mm 特定驱动,若不存在则回退到 imx21 通用驱动。
drivers/of/base.c:338-374 实现了带权重的匹配算法:
// drivers/of/base.c:346-372
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
/* 越靠前(越具体)的 compatible 得分越高 */
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score) return 0;
}
/* device_type 匹配加 2 分 */
if (type && type[0]) {
if (!__of_node_is_type(device, type)) return 0;
score += 2;
}
/* name 匹配加 1 分 */
if (name && name[0]) {
if (!of_node_name_eq(device, name)) return 0;
score++;
}
return score;优先级排序(drivers/of/base.c:320-336):
1. specific compatible + type + name (最高优先级)
2. specific compatible + type
3. specific compatible + name
4. specific compatible
5. general compatible + type + name
...
11. name (最低优先级)
drivers/of/base.c:379-390:
// drivers/of/base.c:379-390
int of_device_is_compatible(const struct device_node *device,
const char *compat)
{
unsigned long flags;
int res;
raw_spin_lock_irqsave(&devtree_lock, flags);
res = __of_device_is_compatible(device, compat, NULL, NULL);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return res;
}持 devtree_lock 的时间仅为字符串比较,驱动探测热路径中不会造成性能瓶颈。
对于平台级别的板卡识别,使用根节点的 compatible(drivers/of/base.c:422-435):
// drivers/of/base.c:422-435
bool of_machine_compatible_match(const char *const *compats)
{
struct device_node *root;
int rc = 0;
root = of_find_node_by_path("/");
if (root) {
rc = of_device_compatible_match(root, compats);
of_node_put(root);
}
return rc != 0;
}这是 ARM 平台检测当前板卡类型的标准方式,例如通过根节点 compatible = "raspberrypi,4-model-b" 识别 Raspberry Pi 4。
platform_bus_match()
└── of_match_node(drv->of_match_table, dev->of_node)
|-- 遍历 of_match_table 中每个条目
|-- 调用 __of_device_is_compatible() 计算匹配分数
`-- 返回得分最高的匹配项
|
v
platform_driver->probe(pdev)
|
`-- of_device_get_match_data(&pdev->dev)
获取匹配项的 .data 字段(驱动私有配置)
static const struct of_device_id uart_of_match[] = {
{ .compatible = "ns16550a", .data = &ns16550a_ops },
{ .compatible = "nvidia,tegra20-uart", .data = &tegra_ops },
{ .compatible = "fsl,imx21-uart", .data = &imx_ops },
{} /* sentinel */
};
MODULE_DEVICE_TABLE(of, uart_of_match);MODULE_DEVICE_TABLE 将匹配表导出到模块信息中,modprobe 和 udev 可据此自动加载驱动。
drivers/of/base.c:396-413:
// drivers/of/base.c:396-413
int of_device_compatible_match(const struct device_node *device,
const char *const *compat)
{
unsigned int tmp, score = 0;
if (!compat)
return 0;
while (*compat) {
tmp = of_device_is_compatible(device, *compat);
if (tmp > score)
score = tmp; /* 取最高分 */
compat++;
}
return score;
}drivers/of/base.c:184-208:
// drivers/of/base.c:184-208
void __init of_core_init(void)
{
struct device_node *np;
of_platform_register_reconfig_notifier();
/* 创建 /sys/firmware/devicetree/ 的 kset */
mutex_lock(&of_mutex);
of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
/* 为所有已存在节点创建 sysfs 条目,同时预热 phandle 缓存 */
for_each_of_allnodes(np) {
__of_attach_node_sysfs(np);
if (np->phandle && !phandle_cache[of_phandle_cache_hash(np->phandle)])
phandle_cache[of_phandle_cache_hash(np->phandle)] = np;
}
mutex_unlock(&of_mutex);
/* /proc/device-tree -> /sys/firmware/devicetree/base 符号链接 */
if (of_root)
proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");
}/sys/firmware/devicetree/base/
|-- compatible <- 属性文件(二进制,大端序)
|-- model
|-- #address-cells
|-- cpus/
| |-- cpu@0/
| | |-- compatible
| | |-- reg
| `-- cpu@1/
`-- soc/
|-- uart@ff000000/
| |-- compatible
| |-- reg
| `-- interrupts
`-- i2c@ff010000/
每个节点对应一个目录,每个属性对应一个二进制文件(通过 bin_attribute),原始值以大端序存储。
drivers/of/fdt.c:1321-1337:
// drivers/of/fdt.c:1321-1337
static int __init of_fdt_raw_init(void)
{
static __ro_after_init BIN_ATTR_SIMPLE_ADMIN_RO(fdt);
/* 仅在 CRC32 校验通过时暴露,防止 bootloader 修改后的信息泄漏 */
if (of_fdt_crc32 != crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params))) {
pr_warn("not creating '/sys/firmware/fdt': CRC check failed\n");
return 0;
}
bin_attr_fdt.private = initial_boot_params;
bin_attr_fdt.size = fdt_totalsize(initial_boot_params);
return sysfs_create_bin_file(firmware_kobj, &bin_attr_fdt);
}
late_initcall(of_fdt_raw_init);启用 OF 子系统的调试输出:
# 启用 drivers/of/ 下所有文件的 pr_debug 输出
echo "file drivers/of/*.c +p" > /sys/kernel/debug/dynamic_debug/control
# 导出当前 FDT 并反编译
cp /sys/firmware/fdt /tmp/current.dtb
dtc -I dtb -O dts /tmp/current.dtb | less
# 查看某节点的原始 reg 属性
hexdump -C /sys/firmware/devicetree/base/soc/uart@ff000000/reg在 pr_* / dev_* 系列函数中,%pOF 打印节点的完整路径(drivers/of/platform.c:159):
pr_debug("create platform device: %pOF\n", np);
/* 输出:create platform device: /soc/uart@ff000000 */变体:%pOFf(完整路径)、%pOFp(phandle 值)、%pOFP(节点名不含路径)。
DT 子系统体现了清晰的分层架构:
+------------------------------------------------------+
| 用户空间 (sysfs /sys/firmware/devicetree, /proc/dt) |
+------------------------------------------------------+
| 驱动框架层 (platform_device/platform_driver 匹配) |
+------------------------------------------------------+
| OF API 层 (of_find_node_*, of_property_read_*, ...) |
+------------------------------------------------------+
| 树模型层 (device_node/property, devtree_lock) |
+------------------------------------------------------+
| FDT 解析层 (libfdt, unflatten, populate_node) |
+------------------------------------------------------+
| 固件接口层 (initial_boot_params, bootloader 传递) |
+------------------------------------------------------+
DT API 遵严格的引用计数规则:
/* 模式 1:单次查找 */
struct device_node *np = of_find_node_by_path("/soc/uart@ff000000");
if (np) {
/* 使用 np */
of_node_put(np); /* 使用完毕后释放 */
}
/* 模式 2:迭代查找(of_find_node_by_* 消费 from 的引用) */
struct device_node *np = NULL;
while ((np = of_find_compatible_node(np, NULL, "ns16550a"))) {
/* 使用 np,下次迭代自动释放 */
}
/* 模式 3:cleanup 宏(离开作用域自动调用 of_node_put) */
/* include/linux/of.h:138:DEFINE_FREE(device_node, ...) */
struct device_node *np __free(device_node) = of_find_node_by_path("/cpus");| 操作 | 复杂度 | 优化手段 |
|---|---|---|
of_find_node_by_phandle |
O(1) 常见情况 | 128 槽哈希缓存(drivers/of/base.c:156) |
of_find_node_by_path |
O(depth) | 路径分量逐级查找 |
of_find_compatible_node |
O(N) | 全树遍历,无索引 |
of_find_property |
O(P) | 属性链表线性扫描(P 通常很小) |
of_translate_address |
O(depth) | 沿树向上逐层转换 |
of_device_is_compatible |
O(C) | C 为 compatible 条目数,通常 < 5 |
1. 两遍扫描 unflatten(drivers/of/fdt.c:382-401)
避免了内存重分配和碎片,一次分配精确大小的连续内存块,所有节点和属性共享同一块内存,便于缓存预取。
2. 头插法 + reverse(drivers/of/fdt.c:219-258)
头插法的 O(1) 插入特性换来了额外的 reverse_nodes() 开销,但后者仅在启动时执行一次,是合理的权衡。
3. 零拷贝属性数据(drivers/of/fdt.c:151-153)
property->name 直接指向 FDT strings block,property->value 直接指向 FDT structure block,不复制数据,节省启动时间和内存。
4. phandle 哈希缓存(drivers/of/base.c:156-163)
中断映射(of_irq_parse_raw)需要频繁通过 phandle 查找中断控制器节点,缓存将最坏情况下的 O(N) 全树扫描降为 O(1)。
5. Overlay changeset 事务性(drivers/of/overlay.c)
将一批 DT 修改记录为可回滚的事务,确保 overlay apply/remove 的原子性,避免出现半途失败的一致性问题。若回滚也失败(DTSF_REVERT_FAIL),后续所有 overlay 操作被拒绝(drivers/of/overlay.c:98-102)。
6. fwnode 抽象层(include/linux/of.h:52)
struct fwnode_handle 和 fwnode_ops 使驱动代码可以统一处理 DT 和 ACPI 来源的设备描述,无需关心底层固件格式。of_fwnode_ops 实现于 drivers/of/property.c,通过虚表分发到具体的 OF 实现。
7. OF_POPULATED 原子标志(include/linux/of.h:152)
of_node_test_and_set_flag(np, OF_POPULATED) 是原子操作,防止在并发初始化场景(SMP)下同一节点被实例化两次,无需外部锁。
| 文件 | 主要内容 | 关键行 |
|---|---|---|
drivers/of/base.c |
核心 API:节点查找、属性读取、compatible 匹配、sysfs 初始化 | of_core_init:184, of_find_node_by_phandle:1238 |
drivers/of/fdt.c |
FDT 解析、unflatten_device_tree、早期内存扫描 | unflatten_device_tree:1272, populate_node:191 |
drivers/of/platform.c |
DT 节点到 platform_device/amba_device 的绑定 | of_platform_populate:443, of_device_alloc:97 |
drivers/of/irq.c |
中断树解析、irq_domain 映射 | of_irq_parse_one:428, of_irq_find_parent:61 |
drivers/of/address.c |
地址转换、of_bus 抽象、DMA 地址处理 | of_translate_address:585, __of_translate_address:496 |
drivers/of/overlay.c |
动态 overlay 应用与回滚 | of_overlay_fdt_apply, build_changeset:630 |
drivers/of/of_private.h |
子系统内部接口 | — |
include/linux/of.h |
公开 API 声明、device_node/property 定义 | struct device_node:48, struct property:28 |
include/linux/of_fdt.h |
FDT 相关 API 声明 | unflatten_device_tree:92 |
include/linux/of_irq.h |
中断相关 API 声明 | — |
include/linux/of_address.h |
地址转换 API 声明 | — |
include/linux/of_platform.h |
platform 绑定 API 声明 | — |
由 Claude Code 分析生成