Linux 内核 DeviceTree API 完全指南:从节点查询到动态 Overlay(drivers/of 源码深度解析)
2026/9/17 9:03:18 网站建设 项目流程

Linux 内核 DeviceTree API 完全指南:从节点查询到动态 Overlay(drivers/of 源码深度解析)

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

导读

本文以 Documentation/devicetree/kernel-api.rst 为骨架,系统梳理 Linux 内核为设备驱动开发者提供的整套 DeviceTree(DT)内核 API。文章按"核心函数、驱动模型函数、Overlay 与动态 DT 函数"三大层次展开,逐一解读drivers/of/下各模块(base、property、graph、address、irq、fdt、device、platform、resolver、dynamic、overlay)的导出接口与典型用法。读完本文,你将掌握如何在驱动中查找设备节点、读取属性、解析中断与地址、创建平台设备,以及如何通过 changeset 与 overlay 机制在运行时动态修改设备树,并清楚每条 API 对应的源码位置与适用范围。

一、文档定位:一份由 kernel-doc 自动聚合的 API 索引

在 Linux 内核源码树中,Documentation/devicetree/kernel-api.rst是一份特殊的文档——它本身几乎不含手写文字,而是通过 Sphinx 的kernel-doc指令,把散落在drivers/of/include/linux/中的函数文档注释(/** ... */)自动抽取、编排成册。其结构分为三节,正好对应设备树子系统的三个层次:

文档小节聚合的源码对象覆盖的 API 类别
Core functions(核心函数)drivers/of/base.cinclude/linux/of.hdrivers/of/property.cinclude/linux/of_graph.hdrivers/of/address.cdrivers/of/irq.cdrivers/of/fdt.c树遍历、节点查找、属性读写、图形绑定、地址/中断解析、FDT 反扁平化
Driver model functions(驱动模型函数)include/linux/of_device.hdrivers/of/device.cinclude/linux/of_platform.hdrivers/of/platform.c设备匹配、平台设备创建与挂载
Overlay and Dynamic DT functions(Overlay 与动态 DT 函数)drivers/of/resolver.cdrivers/of/dynamic.cdrivers/of/overlay.cphandle 解析、变更集(changeset)、overlay 应用与移除

其中:export:表示只收集该文件中使用EXPORT_SYMBOL/EXPORT_SYMBOL_GPL导出的函数,:internal:表示收集头文件中以注释形式声明的内部接口与宏。因此这份文档事实上就是内核模块作者查阅"我能调用哪些 OF API"的权威清单。理解它的编排逻辑,等于拿到了一张drivers/of/源码地图。

二、Core Functions:设备树的核心操作集

2.1 全局根节点与遍历基础设施(drivers/of/base.c)

drivers/of/base.c是设备树子系统的心脏,负责树的建立、遍历与查找,并导出了几个驱动可直接引用的全局符号:

  • of_root:根设备节点指针(EXPORT_SYMBOL);
  • of_chosen/chosen节点指针,bootloader 与内核在此交换启动参数;
  • of_stdout:控制台 stdout 节点(EXPORT_SYMBOL_GPL)。

文件头还定义了两个保护设备树并发修改的锁:of_mutex(保护 alias 表与 sysfs 增删节点)和devtree_lockDEFINE_RAW_SPINLOCK,用于遍历 child/sibling/parent 指针时的原始自旋锁)。凡是遍历树并持有节点引用的代码,都必须配合of_node_get/of_node_put管理引用计数,这是使用所有 OF API 的前提。

节点名称与匹配判断

  • of_node_name_eq(np, name):比较节点名(忽略@unit-address后缀),如of_node_name_eq(np, "ethernet")
  • of_node_name_prefix(np, prefix):判断节点名是否以某前缀开头;
  • of_device_is_compatible(np, compat):判断节点的compatible属性是否包含给定字符串;
  • of_device_compatible_match/of_machine_compatible_match:整表匹配;
  • of_machine_read_compatible/of_machine_read_model:读取机器级compatible/model信息。

树遍历与查找(均返回带引用的节点,用毕需of_node_put):

  • 沿父子方向:of_get_parentof_get_next_parent
  • 沿兄弟方向:of_get_next_childof_get_next_child_with_prefixof_get_next_available_child(跳过status = "disabled"的节点)、of_get_next_reserved_childof_get_next_cpu_node
  • 按名称/类型/compatible 查找:of_get_child_by_nameof_find_node_by_nameof_find_node_by_typeof_find_compatible_nodeof_find_node_with_propertyof_find_node_opts_by_path(支持/path:options语法)、of_find_node_by_phandle
  • 匹配表查找:of_match_nodeof_find_matching_node_and_match

状态与属性查询

  • of_device_is_available(np):根据status属性判断设备是否可用(内核约定status = "okay"或缺失即视为可用);
  • of_device_is_big_endian(np):判断设备是否为大端(对应big-endian属性);
  • of_find_property/of_get_property:查找属性并返回其值指针与长度;
  • of_add_property/of_remove_property:运行时增删属性(GPL 导出)。

地址单元与 phandle 解析

  • of_n_addr_cells/of_n_size_cells:向上遍历取#address-cells/#size-cells,缺失时按平台约定回退(SPARC 与 coreboot 平台除外,会触发WARN_ONCE提示补全属性,见drivers/of/base.cEXCLUDED_DEFAULT_CELLS_PLATFORMS的定义);
  • of_phandle_iterator_init/of_phandle_iterator_next:phandle 迭代器,配合__of_parse_phandle_with_argsof_parse_phandle_with_args_map解析interrupts-extendedclocksdmas这类"引用 + 参数"型属性;of_count_phandle_with_args统计引用条目数;
  • of_alias_get_id/of_alias_get_highest_id:解析/aliases节点中的别名编号,驱动常用它把serial0mmc1这类别名映射为实例序号;
  • of_map_id/of_map_iommu_id/of_map_msi_id:按iommu-mapmsi-map属性执行 ID 重映射(GPL 导出)。

2.2 属性读取与 OF Graph 绑定(drivers/of/property.c)

drivers/of/property.c是驱动日常使用频率最高的文件,提供了一套类型安全、带边界检查的属性读取接口,全部为EXPORT_SYMBOL_GPL

  • 标量读取of_property_read_u8_index/u16_index/u32_index/u64_index(按索引取数组元素)、of_property_read_u64(兼容 32/64 位单元);
  • 数组读取of_property_read_variable_u8/u16/u32/u64_array(按需求大小读取,并校验单元数);
  • 元素计数of_property_count_elems_of_size
  • 字符串读取of_property_read_stringof_property_match_string(在一组字符串中匹配)、of_property_read_string_helper(通用 helper);
  • 迭代器of_prop_next_u32of_prop_next_string
  • 布尔判断of_property_read_bool(np, propname)——注意源码注释特别强调:布尔属性本不应携带值,若对带值属性误用该接口,会打印Read of boolean property '%s' with a value的告警;判断属性存在应优先用of_property_present()或直接读取返回值。

OF Graph(图形/多媒体绑定)of_graph_*系列函数(对应include/linux/of_graph.h中的struct of_endpoint与迭代宏)用于解析视频、显示、相机子系统常用的 ports/port/endpoint 层级结构:

  • of_graph_parse_endpoint:解析单个 endpoint 的 port 与 id;
  • of_graph_get_port_by_idof_graph_get_next_portof_graph_get_next_endpointof_graph_get_endpoint_by_regs:按 port/endpoint 定位;
  • of_graph_get_remote_endpointof_graph_get_remote_portof_graph_get_remote_port_parent:跨节点追踪"远端"(remote-endpointphandle);
  • of_graph_get_endpoint_countof_graph_get_port_count:统计数量;
  • of_graph_get_remote_node:一步取得远端设备的 device_node。

头文件还提供了for_each_endpoint_of_node(parent, child)for_each_of_graph_portfor_each_of_graph_port_endpoint等迭代宏,其中for_each_endpoint_of_node的注释明确要求:中途 break 时必须手动of_node_put(child)释放引用。此外,of_fwnode_opsEXPORT_SYMBOL_GPL)将 OF 接口桥接到通用 fwnode 框架,供 device property 子系统统一调用。

2.3 地址翻译与资源映射(drivers/of/address.c)

设备树中的reg属性描述的是总线本地地址,驱动需要的是 CPU 物理地址。drivers/of/address.c实现了沿总线层级逐级翻译的完整逻辑:

  • of_translate_address:按父子节点的ranges属性把地址翻译为 CPU 物理地址;
  • of_translate_dma_address/of_translate_dma_region:按dma-ranges翻译 DMA 地址(IOMMU 场景常用);
  • __of_get_address/of_property_read_reg:读取reg中的第 N 组地址/大小;
  • of_address_to_resource:把节点地址转为struct resource
  • of_iomapof_address_to_resource+ioremap一步到位,驱动中极为常用;
  • of_io_request_and_map:先request_mem_region再映射,用于声明占用 IO 资源;
  • of_dma_is_coherent:查询 DMA 一致性(dma-coherent属性);
  • PCI 范围解析:of_pci_range_parser_init/of_pci_range_parser_one/of_pci_range_to_resourceof_pci_dma_range_parser_initof_pci_address_to_resource——用于把 PCI 的ranges属性解析为 BAR 资源;
  • of_range_to_resourceEXPORT_SYMBOL_IF_KUNIT):通用 range 转资源。

一个典型驱动用法是void __iomem *base = of_iomap(np, 0);直接把节点第 0 组 reg 映射为内核虚拟地址。

2.4 中断解析(drivers/of/irq.c)

drivers/of/irq.c实现"DT 中断描述 → Linux irq 号"的完整解析链:

  • irq_of_parse_and_map(np, index):最常用的入口——解析interrupts/interrupts-extended属性并映射为 Linux IRQ 号;
  • of_irq_find_parent:沿interrupt-parent链向上找中断控制器;
  • of_irq_parse_raw/of_irq_parse_one:底层解析,生成struct of_phandle_args
  • of_irq_to_resource/of_irq_to_resource_table:把 IRQ 转成struct resource/ 批量填充资源表;
  • of_irq_get(np, index)/of_irq_get_byname(np, name):按索引或interrupt-names名字取 IRQ 号(带错误码返回,推荐使用);
  • of_irq_count:统计中断数量;
  • of_imap_parser_init/of_imap_parser_one:中断映射(interrupt-map)迭代器;
  • MSI 相关:of_msi_xlateof_msi_get_domainof_msi_configure(PCIe/MSI 控制器场景)。

2.5 FDT 扁平化设备树(drivers/of/fdt.c)

早期启动阶段,内核拿到的是 bootloader 传入的 Flattened Device Tree(FDT)二进制块(dtb)。drivers/of/fdt.c负责把它解析为内存中的struct device_node树:

  • of_fdt_unflatten_tree(fdt, parent, &nodes)EXPORT_SYMBOL_GPL):把 FDT blob 反扁平化为一棵可操作的 device_node 树,是 overlay 应用等场景的核心前置步骤(overlay 应用时即用它对 dtb 片段做 unflatten,详见下文)。

include/linux/of.h:internal:部分)则集中声明了struct device_nodestruct propertyof_node_get/put等基础数据结构的操作约定,是所有 OF API 的类型地基。

三、Driver Model Functions:把 DT 节点变成内核设备

3.1 设备匹配(include/linux/of_device.h 与 drivers/of/device.c)

  • of_match_device(matches, dev):用struct of_device_id匹配表匹配设备节点的compatible属性,是平台驱动 probe 的判定核心;
  • of_device_get_match_data(dev)EXPORT_SYMBOL):匹配成功后直接返回of_device_id.data指针,驱动借此拿到与 compatible 绑定的私有配置数据,是驱动中最常用的惯用法;
  • of_dma_configure_id(GPL):根据节点的dma-rangesiommu-map配置设备的 DMA 域(含对restricted-dma-pool内存区域的初始化,见of_dma_set_restricted_buffer辅助函数);
  • of_device_modalias/of_device_uevent/of_device_uevent_modalias:生成 modalias / uevent,供用户态 udev 自动加载模块;
  • of_device_make_bus_id:基于节点信息生成稳定的设备总线 ID。

3.2 平台设备实例化(include/linux/of_platform.h 与 drivers/of/platform.c)

设备树驱动模型的核心思想是"以数据驱动设备枚举"(见 Documentation/devicetree/usage-model.rst)。drivers/of/platform.c实现了把 DT 节点实例化为struct platform_device的整套流程:

  • of_platform_device_create(np, bus_id, parent):为单个节点创建设备;
  • of_platform_bus_probe:递归探测一条总线;
  • of_platform_populate(root, matches, lookup, parent):为根节点(通常是/soc等总线节点)下的匹配节点批量创建平台设备并注册驱动;
  • of_platform_default_populate:用默认匹配规则(default_match,覆盖simple-bussimple-mfd等总线 compatible)批量填充;
  • devm_of_platform_populate/devm_of_platform_depopulate:devres 托管版本,驱动卸载时自动清理;
  • 反向操作:of_platform_device_destroy/of_platform_depopulate移除设备树生成的设备;
  • 配套接口:of_find_device_by_node(由节点反查 platform_device)、of_device_allocof_device_register/of_device_unregister

这一层解释了"为什么在 DT 中声明一个compatible = "simple-bus"的节点,其子设备就能自动被枚举并绑定驱动"——正是of_platform_default_populate在启动流程中完成的。

四、Overlay 与 Dynamic DT:运行时的树修改

设备树不仅能在启动时被静态解析,还能在运行时被动态修改——这是 FPGA 重配置、可插拔子卡、CXL 设备热插拔等场景的基础设施。kernel-api.rst的第三节把相关 API 分为解析、变更集、overlay 三层。

4.1 phandle 解析(drivers/of/resolver.c)

  • of_resolve_phandles(overlay_fdt)EXPORT_SYMBOL_GPL):在应用 overlay 前,把 overlay dtb 片段中的__fixups__属性引用的标签,对照主树的/__symbols__表解析为真实的 phandle 值并回写。若主树缺少/__symbols__或标签不存在,会分别返回-EINVAL与错误码(见 drivers/of/resolver.c 中of_resolve_phandles的实现)。

4.2 变更集(drivers/of/dynamic.c)

变更集(changeset)是 overlay 机制的地基,它把一系列节点/属性操作打包成原子事务,可整体 apply 或 revert(详见 Documentation/devicetree/changesets.rst):

  • 引用计数与通知:of_node_get/of_node_putof_reconfig_notifier_register/of_reconfig_notifier_unregisterof_reconfig_get_state_change(监听树变更的 notifier 机制);
  • 节点操作:of_changeset_create_node(ocs, parent, full_name)(动态创建节点)、of_detach_node
  • 事务控制:of_changeset_init/of_changeset_destroyof_changeset_apply/of_changeset_revertof_changeset_action
  • 便捷操作宏:of_changeset_add_prop_stringof_changeset_add_prop_string_arrayof_changeset_add_prop_u32_arrayof_changeset_add_prop_boolof_changeset_update_prop_string

典型流程是:of_changeset_init→ 若干of_changeset_*操作入队 →of_changeset_apply一次性生效,失败时of_changeset_revert整体回滚,保证树的一致性。

4.3 Overlay 应用与移除(drivers/of/overlay.c)

  • of_overlay_fdt_apply(overlay_fdt, size, &ovcs_id)EXPORT_SYMBOL_GPL):核心入口。内部流程(见 drivers/of/overlay.c)为:复制 FDT 并做对齐 →of_fdt_unflatten_tree反扁平化 →of_resolve_phandles解析引用 → 把 overlay 树合并进主树并登记到全局ovcs_list;成功后返回 overlay 的 ID,失败时调用方可依据该 ID 调用of_overlay_remove()清理(源码注释特别警告:apply 部分成功时不能直接释放 changeset,否则会内存泄漏);
  • of_overlay_remove(ovcs_id)/of_overlay_remove_all():按 ID 移除单个 overlay 或移除全部;
  • of_overlay_notifier_register/of_overlay_notifier_unregister:注册 overlay 生命周期通知,供子系统在 overlay 应用/移除时做出反应。

这三组 API 的协作关系可用 Documentation/devicetree/overlay-notes.rst 中描述的模型概括:overlay 片段(含__overlay____fixups____local_fixups__)先经 resolver 解析,再通过 changeset 原子合并到活动树,整个过程可撤销、可通知。更底层的内存模型与解析细节可参考 Documentation/devicetree/dynamic-resolution-notes.rst。

五、API 的正确使用范式与验证手段

引用计数纪律:除明确标注外,of_*查找与遍历函数返回的都是持有引用的device_node *,调用方必须配对调用of_node_putof_graph.h的迭代宏、of_changeset_*事务、of_node_put等在注释与实现中反复强调这一规则,违反它会直接导致内存泄漏或悬垂指针。

错误处理:推荐优先使用带错误码返回的接口(如of_property_read_u32of_irq_get返回负 errno),而不是仅依赖空指针判断;of_iomap返回NULL表示失败。

验证与自测:内核为整个 OF 子系统提供了专门的单元测试框架,测试源码位于 drivers/of/unittest.c(含 overlay 测试 drivers/of/overlay_test.c 与测试数据目录 drivers/of/unittest-data),并配套文档 Documentation/devicetree/of_unittest.rst。开发者可以在CONFIG_OF_UNITTEST开启下运行这些测试,验证自身对 API 语义的理解;of_test.cof_kunit_helpers.c则提供了基于 KUnit 的辅助设施。修改驱动中 OF 相关代码后,建议先运行该套件再合入。

六、总结与延伸阅读

Documentation/devicetree/kernel-api.rst以三节二十余个源码对象,勾勒出 Linux 设备树子系统的完整 API 面:核心层(base/property/graph/address/irq/fdt)解决"如何读树",驱动模型层(device/platform)解决"如何把树变成设备",动态层(resolver/dynamic/overlay)解决"如何改树"。对驱动作者而言,日常 80% 的需求集中在of_property_read_*of_device_get_match_dataof_iomapirq_of_parse_and_mapof_platform_populate这几个接口上;而从事 FPGA/CXL/热插拔相关工作的开发者,则需要进一步掌握 changeset 与 overlay 的事务化编程模型。

如需继续深入,可依次阅读同目录下的 Documentation/devicetree/usage-model.rst(DT 使用模型与平台识别)、Documentation/devicetree/overlay-notes.rst(overlay 片段格式)、Documentation/devicetree/changesets.rst(变更集语义)、Documentation/devicetree/dynamic-resolution-notes.rst(动态解析细节),以及设备树绑定(binding)约定目录 Documentation/devicetree/bindings——所有的compatible字符串与属性约定都应以该目录下的文档为准。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询