Skip to content

Latest commit

 

History

History
1814 lines (1424 loc) · 58.5 KB

File metadata and controls

1814 lines (1424 loc) · 58.5 KB

Linux 内核设备树(Device Tree)子系统深度分析

目录

  1. 概述与历史背景
  2. DTB 格式与 FDT 二进制结构
  3. 内核启动时 DT 的展开:unflatten_device_tree
  4. device_node 结构体与树形组织
  5. of_find_node_by_* 系列查询 API
  6. DT 与 platform_device 的绑定:of_platform_populate
  7. 中断映射:of_irq_parse_one 与 irq_domain
  8. DT Overlay 动态插拔
  9. DT 地址转换:of_translate_address
  10. compatible 字符串匹配与驱动绑定
  11. sysfs 暴露与调试接口
  12. 总结与设计哲学

1. 概述与历史背景

设备树(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 解析与 unflatten
  • drivers/of/platform.c — DT 节点到 platform_device 的映射
  • drivers/of/irq.c — 中断树解析
  • drivers/of/address.c — 地址转换
  • drivers/of/overlay.c — 动态 overlay 支持
  • include/linux/of.h — 公开 API 声明与核心结构体

2. DTB 格式与 FDT 二进制结构

2.1 DTB 文件格式

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):整棵树结束

2.2 早期 FDT 扫描

内核在 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 的合理性。

2.3 early_init_dt_scan_root:地址/大小单元格

在扫描内存节点之前,需要先确定地址/大小单元格数(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)。

2.4 CRC32 完整性保护

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));

2.5 of_fdt_is_compatible:FDT 层的兼容性检查

在展开之前,内核可以直接在 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_descdrivers/of/fdt.c:759)。


3. 内核启动时 DT 的展开:unflatten_device_tree

3.1 调用路径

以 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]

3.2 unflatten_device_tree 入口

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();
}

3.3 两遍扫描算法

__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 用于检测数组越界写入:若第二遍结束后哨兵被覆盖,说明内存大小计算有误。

3.4 unflatten_dt_nodes 节点迭代

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);

3.5 populate_node:分配并填充单个节点

每个 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)。

3.6 populate_properties:解析属性链表

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;

这是零拷贝设计——属性数据不会被复制,节省内存。

特殊处理:

  • phandlelinux,phandle:提取并填充 np->phandledrivers/of/fdt.c:138-141
  • name 属性缺失时,从节点名自动合成(drivers/of/fdt.c:161-188

3.7 reverse_nodes:修正子节点顺序

由于头插法导致顺序反转,展开完成后需要深度优先地翻转所有子节点链表(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;
    }
}

3.8 unflatten_dt_alloc:顺序分配器

所有 device_nodeproperty 从同一个连续内存块中顺序分配(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;
}

所有节点和属性共享一块连续内存,缓存友好,可一次性释放整块。


4. device_node 结构体与树形组织

4.1 核心结构体定义

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;  /* 单向链表 */
};

4.2 节点标志位

_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);
}

4.3 树形结构图

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"]

4.4 全局根节点与特殊节点

// 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 对应的设备节点 */

4.5 并发保护

设备树使用两种锁(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 是自旋锁,用于快速遍历路径,可在中断上下文使用。

4.6 phandle 哈希缓存

为加速 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)。

4.7 引用计数

设备节点使用引用计数管理生命周期(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 扩展,使节点引用可以在离开作用域时自动释放。


5. of_find_node_by_* 系列查询 API

5.1 API 总览

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" 的子节点

5.2 of_find_node_by_name

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 的引用计数会被减掉 */
}

5.3 of_find_compatible_node

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),无索引加速。

5.4 of_find_node_by_phandle 与缓存

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)。

5.5 全局深度优先遍历

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;
}

5.6 of_find_property 内部实现

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),无需索引。


6. DT 与 platform_device 的绑定:of_platform_populate

6.1 绑定机制概览

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)

6.2 of_platform_populate 实现

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 推迟)。

6.3 默认 match 表

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(工作点),不应被实例化为平台设备。

6.4 of_platform_bus_create:核心递归函数

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;
}

6.5 of_platform_device_create_pdata:创建单个 platform_device

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 节点不会被实例化两次。

6.6 of_device_alloc:提取硬件资源

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 属性第一个地址生成设备名称,确保唯一性。


7. 中断映射:of_irq_parse_one 与 irq_domain

7.1 DT 中的中断描述方式

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
            >;
        };
    };
};

7.2 irq_of_parse_and_map 入口

最常用的驱动 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;
}

7.3 of_irq_parse_one:解析中断描述符

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);

7.4 of_irq_find_parent:查找中断父节点

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 属性可以跨层级指向任意节点。

7.5 of_irq_parse_raw:interrupt-map 转换

对于带有 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 属性,内核维护黑名单跳过标准解析。

7.6 irq_domain:Hardware IRQ 到 Linux virq 的映射

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_DEFERdrivers/of/irq.c:538-540),触发设备探测延迟机制,待中断控制器驱动加载后重试。

7.7 of_irq_to_resource:中断转换为 resource

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;
}

8. DT Overlay 动态插拔

8.1 Overlay 的应用场景

DT Overlay 允许在系统运行时动态修改设备树,典型用途包括:

  • 树莓派 HAT 扩展板的热插拔配置
  • FPGA 部分重配置后添加新功能节点
  • 通过 configfs 在用户空间加载 overlay

8.2 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 */
};

8.3 Overlay 应用流程

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)

8.4 互斥保护

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 导致冲突。

8.5 通知链

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 触发通知,只要有一个回调返回错误就中止。

8.6 符号路径修正

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 中可被正确引用。

8.7 Overlay 回滚

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)。


9. DT 地址转换:of_translate_address

9.1 地址转换的必要性

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 映射)。

9.2 of_bus 结构与多总线支持

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-cellsof_bus_default_mapdrivers/of/address.c:53
  • PCI 总线:3 cell 地址格式(含空间标识、总线号、设备功能号)
  • ISA 总线:带 I/O 空间标识

9.3 of_translate_address 实现

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;
}

9.4 __of_translate_address 核心算法

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;
    }
}

9.5 of_address_to_resource:完整转换为 resource

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):

  1. 调用 of_get_address() 获取 reg 属性的第 index 个条目
  2. 调用 of_translate_address() 转换为 CPU 物理地址
  3. 填充 struct resource(包含起始地址、结束地址、标志位)

9.6 DMA 地址转换

对于 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-memdrivers/of/address.c:602-618),以支持系统互联总线(如 IOMMU)的 DMA 地址映射。


10. compatible 字符串匹配与驱动绑定

10.1 compatible 属性格式

compatible 属性是驱动与设备树节点绑定的核心机制,通常为从具体到通用的字符串列表:

/* 从最具体到最通用 */
compatible = "fsl,imx8mm-uart", "fsl,imx21-uart";

这意味着优先使用 imx8mm 特定驱动,若不存在则回退到 imx21 通用驱动。

10.2 __of_device_is_compatible 评分算法

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                               (最低优先级)

10.3 of_device_is_compatible

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 的时间仅为字符串比较,驱动探测热路径中不会造成性能瓶颈。

10.4 of_machine_compatible_match:根节点匹配

对于平台级别的板卡识别,使用根节点的 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。

10.5 of_match_node 与驱动 probe 流程

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 字段(驱动私有配置)

10.6 典型驱动 of_device_id 表

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 将匹配表导出到模块信息中,modprobeudev 可据此自动加载驱动。

10.7 of_device_compatible_match:多 compatible 并行匹配

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;
}

11. sysfs 暴露与调试接口

11.1 of_core_init:建立 sysfs 表示

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");
}

11.2 sysfs 目录结构

/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),原始值以大端序存储。

11.3 /sys/firmware/fdt 原始 blob

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);

11.4 动态调试

启用 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

11.5 %pOF 格式化说明符

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(节点名不含路径)。


12. 总结与设计哲学

12.1 分层设计

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 传递)      |
+------------------------------------------------------+

12.2 引用计数的正确使用模式

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");

12.3 性能考量

操作 复杂度 优化手段
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

12.4 关键设计决策解析

1. 两遍扫描 unflattendrivers/of/fdt.c:382-401) 避免了内存重分配和碎片,一次分配精确大小的连续内存块,所有节点和属性共享同一块内存,便于缓存预取。

2. 头插法 + reversedrivers/of/fdt.c:219-258) 头插法的 O(1) 插入特性换来了额外的 reverse_nodes() 开销,但后者仅在启动时执行一次,是合理的权衡。

3. 零拷贝属性数据drivers/of/fdt.c:151-153property->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:52struct fwnode_handlefwnode_ops 使驱动代码可以统一处理 DT 和 ACPI 来源的设备描述,无需关心底层固件格式。of_fwnode_ops 实现于 drivers/of/property.c,通过虚表分发到具体的 OF 实现。

7. OF_POPULATED 原子标志include/linux/of.h:152of_node_test_and_set_flag(np, OF_POPULATED) 是原子操作,防止在并发初始化场景(SMP)下同一节点被实例化两次,无需外部锁。

12.5 相关源文件索引

文件 主要内容 关键行
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 分析生成