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.c、include/linux/of.h、drivers/of/property.c、include/linux/of_graph.h、drivers/of/address.c、drivers/of/irq.c、drivers/of/fdt.c | 树遍历、节点查找、属性读写、图形绑定、地址/中断解析、FDT 反扁平化 |
| Driver model functions(驱动模型函数) | include/linux/of_device.h、drivers/of/device.c、include/linux/of_platform.h、drivers/of/platform.c | 设备匹配、平台设备创建与挂载 |
| Overlay and Dynamic DT functions(Overlay 与动态 DT 函数) | drivers/of/resolver.c、drivers/of/dynamic.c、drivers/of/overlay.c | phandle 解析、变更集(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_lock(DEFINE_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_parent、of_get_next_parent; - 沿兄弟方向:
of_get_next_child、of_get_next_child_with_prefix、of_get_next_available_child(跳过status = "disabled"的节点)、of_get_next_reserved_child、of_get_next_cpu_node; - 按名称/类型/compatible 查找:
of_get_child_by_name、of_find_node_by_name、of_find_node_by_type、of_find_compatible_node、of_find_node_with_property、of_find_node_opts_by_path(支持/path:options语法)、of_find_node_by_phandle; - 匹配表查找:
of_match_node、of_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.c中EXCLUDED_DEFAULT_CELLS_PLATFORMS的定义);of_phandle_iterator_init/of_phandle_iterator_next:phandle 迭代器,配合__of_parse_phandle_with_args、of_parse_phandle_with_args_map解析interrupts-extended、clocks、dmas这类"引用 + 参数"型属性;of_count_phandle_with_args统计引用条目数;of_alias_get_id/of_alias_get_highest_id:解析/aliases节点中的别名编号,驱动常用它把serial0、mmc1这类别名映射为实例序号;of_map_id/of_map_iommu_id/of_map_msi_id:按iommu-map、msi-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_string、of_property_match_string(在一组字符串中匹配)、of_property_read_string_helper(通用 helper); - 迭代器:
of_prop_next_u32、of_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_id、of_graph_get_next_port、of_graph_get_next_endpoint、of_graph_get_endpoint_by_regs:按 port/endpoint 定位;of_graph_get_remote_endpoint、of_graph_get_remote_port、of_graph_get_remote_port_parent:跨节点追踪"远端"(remote-endpointphandle);of_graph_get_endpoint_count、of_graph_get_port_count:统计数量;of_graph_get_remote_node:一步取得远端设备的 device_node。
头文件还提供了for_each_endpoint_of_node(parent, child)、for_each_of_graph_port、for_each_of_graph_port_endpoint等迭代宏,其中for_each_endpoint_of_node的注释明确要求:中途 break 时必须手动of_node_put(child)释放引用。此外,of_fwnode_ops(EXPORT_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_iomap:of_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_resource、of_pci_dma_range_parser_init、of_pci_address_to_resource——用于把 PCI 的ranges属性解析为 BAR 资源; of_range_to_resource(EXPORT_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_xlate、of_msi_get_domain、of_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_node、struct property、of_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-ranges、iommu-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-bus、simple-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_alloc、of_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_put、of_reconfig_notifier_register/of_reconfig_notifier_unregister、of_reconfig_get_state_change(监听树变更的 notifier 机制); - 节点操作:
of_changeset_create_node(ocs, parent, full_name)(动态创建节点)、of_detach_node; - 事务控制:
of_changeset_init/of_changeset_destroy、of_changeset_apply/of_changeset_revert、of_changeset_action; - 便捷操作宏:
of_changeset_add_prop_string、of_changeset_add_prop_string_array、of_changeset_add_prop_u32_array、of_changeset_add_prop_bool、of_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_put。of_graph.h的迭代宏、of_changeset_*事务、of_node_put等在注释与实现中反复强调这一规则,违反它会直接导致内存泄漏或悬垂指针。
错误处理:推荐优先使用带错误码返回的接口(如of_property_read_u32、of_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.c、of_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_data、of_iomap、irq_of_parse_and_map、of_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),仅供参考