原理与源码级实现解析)
Linux 内核 Devicetree 动态解析器Dynamic Resolver原理与源码级实现解析【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读本文围绕 Linux 内核文档 dynamic-resolution-notes.rst 展开深入剖析内核中 Devicetree Overlay 的解析器resolver模块——它位于drivers/of/resolver.c是设备树覆盖overlay技术落地前的关键一步将 dtc 编译生成的、带有__fixups__与__local_fixups__节点的 overlay 二进制树修正其内部 phandle 冲突并把对外部节点的引用替换为 live tree 中的真实 phandle 值。读完本文你将掌握 resolver 的六步工作流程、phandle 重定位的数值规律、__fixups__属性中path:property:offset三元组格式的解析机制以及如何在内核源码与 unittest 数据中验证这一过程。背景overlay 与 resolver 的分工在 Linux 内核中设备树覆盖Device Tree Overlay允许运行时向 live tree当前已展开的设备树中增删节点、修改属性从而实现外设热插拔、可编程硬件配置等场景。整套机制由两个核心文件支撑drivers/of/resolver.c负责解析resolve即修正 phandle 引用关系使 overlay 能与 live tree 无缝衔接drivers/of/overlay.c负责应用apply即把解析完成的 overlay 以 changeset 的形式合入 live tree并触发设备注册/注销。本文的关联文档是 Documentation/devicetree/dynamic-resolution-notes.rst它精确描述了 resolver 的输入、输出与六步执行流程其姊妹文档 overlay-notes.rst 则从应用层面对 overlay 整体机制做了补充。从调用链上看resolver 是 overlay 应用流程的第一个环节。在 drivers/of/overlay.c 的of_overlay_apply()中static int of_overlay_apply(struct overlay_changeset *ovcs, const struct device_node *base) { int ret 0, ret_revert, ret_tmp; ret of_resolve_phandles(ovcs-overlay_root); if (ret) goto out; ...也就是说任何一次 overlay 应用都必须先经过of_resolve_phandles()完成 phandle 解析随后才会进入 changeset 构建与OF_OVERLAY_PRE_APPLY/POST_APPLY通知回调。Resolver 的输入/plugin/ 与 dtc 生成的固定节点resolver 的输入是一棵用正确的 dtc 选项编译、带有/plugin/标签的任意树。在设备树源文件DTS中声明 overlay 的固定写法是/dts-v1/; /plugin/;/plugin/告诉 dtc 编译器该源文件是一个可加载的 overlay而不是独立的完整设备树。编译时 dtc 会为 overlay 自动生成两个关键节点__fixups__记录 overlay 中对外部节点live tree 中带 label 的节点的引用__local_fixups__记录 overlay内部节点之间的 phandle 引用。这两个节点正是 resolver 后续工作的目录。内核自带的测试数据可以佐证这一点。在 drivers/of/unittest-data/overlay_common.dtsi 的注释中明确写到* Do not add anything that would result in dtc creating node /__fixups__. * dtc will create nodes /__symbols__ and /__local_fixups__.这说明即使只是被 overlay 引用的基础树base tree只要用-选项编译dtc 也会自动生成/__symbols__节点而 overlay 自身的/__local_fixups__也由 dtc 自动创建。/__symbols__中保存的是label - 节点路径的映射表它是 resolver 把外部引用解析到具体节点的桥梁。Resolver 六步工作流程根据文档of_resolve_phandles()按以下顺序执行编号与文档一致获取 live tree 的最大 phandle 值并加 1作为 phandle 偏移基准按该偏移量调整 overlay 中所有局部 phandle利用__local_fixups__节点信息将所有局部引用同步增加同样的偏移量遍历__fixups__节点中的每个属性在 live tree 中找到属性名对应的 label 所标记的节点取出目标节点的 phandle对属性中记录的每个 fixup 位置节点:属性:偏移写入该 phandle 值。下面结合drivers/of/resolver.c的源码逐条展开。第一步获取 live tree 最大 phandle对应的源码是 drivers/of/resolver.c 中的live_tree_max_phandle()static phandle live_tree_max_phandle(void) { struct device_node *node; phandle phandle; unsigned long flags; raw_spin_lock_irqsave(devtree_lock, flags); phandle 0; for_each_of_allnodes(node) { if (node-phandle ! OF_PHANDLE_ILLEGAL node-phandle phandle) phandle node-phandle; } raw_spin_unlock_irqrestore(devtree_lock, flags); return phandle; }要点遍历 live tree 的全部节点for_each_of_allnodes在devtree_lock自旋锁保护下取最大值OF_PHANDLE_ILLEGAL即 0在include/linux/of.h中定义被视为无 phandle不参与比较返回值是 live tree 中 phandle 的最大值在 of_resolve_phandles() 中phandle_delta live_tree_max_phandle() 1得到偏移基准。第二步调整 overlay 局部 phandleadjust_overlay_phandles()drivers/of/resolver.c递归遍历 overlay 的每个节点static void adjust_overlay_phandles(struct device_node *overlay, int phandle_delta) { struct device_node *child; const struct property *prop; phandle phandle; /* adjust nodes phandle in node */ if (overlay-phandle ! 0 overlay-phandle ! OF_PHANDLE_ILLEGAL) overlay-phandle phandle_delta; /* copy adjusted phandle into *phandle properties */ for_each_property_of_node(overlay, prop) { if (of_prop_cmp(prop-name, phandle) of_prop_cmp(prop-name, linux,phandle)) continue; if (prop-length 4) continue; phandle be32_to_cpup(prop-value); if (phandle OF_PHANDLE_ILLEGAL) continue; *(__be32 *)prop-value cpu_to_be32(overlay-phandle); } for_each_child_of_node(overlay, child) adjust_overlay_phandles(child, phandle_delta); }要点每个节点内存中的phandle字段增加phandle_delta同时把节点属性phandle或linux,phandle大端序存储__be32同步更新为新值保证树结构中的 phandle与属性中的 phandle一致递归处理所有子节点。这里体现了一个重要的数值规律live tree 的 phandle 取值范围是 1 到live_tree_max_phandle()overlay 中的 phandle 也从 1 开始编号。两者直接冲突所以必须把 overlay 的全部局部 phandle 抬升到max 1起的新区间避免与 live tree 撞号。第三步调整局部引用local_fixupsadjust_local_phandle_references()drivers/of/resolver.c实现文档中的第 3 步。__local_fixups__子树的结构与 overlay 根节点下的 fragment 结构一一镜像对于 fragment 中每个包含 phandle 引用的属性__local_fixups__里有一个同名属性其值是该引用在属性值中的字节偏移列表。if ((prop_fix-length % 4) ! 0 || prop_fix-length 0) return -EINVAL; count prop_fix-length / sizeof(__be32); ... for (i 0; i count; i) { off be32_to_cpu(((__be32 *)prop_fix-value)[i]); if ((off 4) prop-length) return -EINVAL; be32_add_cpu(prop-value off, phandle_delta); }要点每个偏移必须是 4 字节对齐的__be32长度校验保证不会越界对被引用属性中偏移处的那 4 个字节直接执行be32_add_cpu()即加上 phandle_delta而不是替换为某个具体值——因为局部引用的目标在 overlay 内部目标 phandle 已经统一加了 delta引用侧加同样的 delta 即可继续保持指向关系随后递归遍历__local_fixups__与 overlay 的子树按节点名逐层匹配后继续处理node_name_cmp()比较时忽略unit-address部分。第四至六步解析外部引用fixups核心函数是update_usages_of_a_phandle_reference()drivers/of/resolver.c与of_resolve_phandles()主体drivers/of/resolver.c。__fixups__中每个属性的名字就是 live tree 中某个 label符号名属性值则是一串节点路径:属性名:偏移的三元组列表。resolver 的处理流程在 overlay 的__fixups__下逐属性遍历跳过自动添加的name伪属性从 live tree 的/__symbols__节点读取该 label 对应的节点路径of_property_read_string(tree_symbols, prop-name, refpath)通过of_find_node_by_path(refpath)定位目标节点取其phandle字段调用update_usages_of_a_phandle_reference()把 overlay 中所有记录位置的值替换为该 phandle。update_usages_of_a_phandle_reference()对属性值做了解析/* prop_fixup contains a list of tuples of path:property_name:offset */ end value prop_fixup-length; for (cur value; cur end; cur len 1) { len strlen(cur); node_path cur; s strchr(cur, :); if (!s) return -EINVAL; *s \0; prop_name s; s strchr(s, :); if (!s) return -EINVAL; *s \0; err kstrtoint(s, 10, offset); ... refnode __of_find_node_by_full_path(of_node_get(overlay), node_path); ... for_each_property_of_node(refnode, prop) { if (!of_prop_cmp(prop-name, prop_name)) break; } ... *(__be32 *)(prop-value offset) cpu_to_be32(phandle); }要点多个三元组之间以\0分隔字符串列表用strlen逐个推进解析每个三元组形如path:property_name:offset例如/fragment0/__overlay__/hvac-provider:0offset为十进制注意这里的path是相对于 overlay 根节点的完整路径因此用__of_find_node_by_full_path(of_node_get(overlay), node_path)查找最终把phandle以cpu_to_be32的字节序写入指定属性偏移处覆盖原来由 dtc 留下的 0 占位值。of_resolve_phandles()对错误路径也做了完整处理overlay 为 NULL返回-EINVALoverlay 未脱离 live tree未置OF_DETACHED标志返回-EINVAL并打印overlay not detached——因为 resolver 会原地改写overlay 的 phandle 与引用绝不允许操作还挂在 live tree 上的节点live tree 根节点没有/__symbols__打印no symbols in root of device tree.并返回-EINVAL典型场景是基础树编译时未加-选项label 在符号表中不存在打印node label %s not found in live devicetree symbols table路径无法定位节点返回-ENOENT。此外函数头注释drivers/of/resolver.c明确指出一个并发约束解析与应用多个 overlay 必须通过某种机制保证单线程顺序执行否则多个 overlay 会把 phandle 重定位到重叠区间该强制机制目前尚未实现属于已知的开放问题。为什么需要 phandle 重定位一个直观的数值示例假设 live tree 中当前最大 phandle 是 42phandle_delta 42 1 43overlay 中原本编号为 1、2、3 的局部 phandle 被改为 44、45、46overlay 内部所有引用这些 phandle 的属性由__local_fixups__记录位置同样各加 43overlay 中引用 live tree 节点如hvac_1、spin_ctrl_2的属性则由__fixups__记录的path:property:offset位置直接写入live tree 中对应节点的真实 phandle 值。这样最终得到的 overlay 树中所有 phandle 值既不与 live tree 冲突又精确指向正确的目标节点随后才能安全地合入 live tree。在内核源码与测试数据中验证unittest 测试数据内核自带完整的 overlay 单元测试数据位于 drivers/of/unittest-dataoverlay_base.dtso基础树编译时通过DTC_FLAGS_overlay_base -开启符号生成overlay.dtso一个典型 overlay用electric_1、rides_1、lights_2等 label 指向基础树节点并在内部通过hvac_2、spin_ctrl_1等 label 建立局部 phandle 引用——恰好同时覆盖了__fixups__外部引用与__local_fixups__内部引用两条路径overlay_common.dtsi被所有 overlay 引用的公共基础树其中ride100的hvac-provider hvac_1、spin-controller spin_ctrl_2 5 spin_ctrl_2 7都是会被解析器改写的位置。编译规则见 drivers/of/unittest-data/MakefileCONFIG_OF_OVERLAY开启时各.dtso被编译为.dtbo并链接进内核镜像其中overlay_bad_phandle.dtbo、overlay_bad_symbol.dtbo等坏 overlay专门用于测试 resolver 的错误路径而overlay_bad_unresolved.dtbo用于测试无法解析的场景。单元测试执行drivers/of/unittest.c约 4569 行是 OF 子系统的自测试框架。以unittest()宏drivers/of/unittest.c断言结果失败的用例会打印FAIL并计入unittest_results.failed。该框架通过of_overlay_fdt_apply()将测试 overlay 应用到 live tree从而在真实运行环境中验证 resolver 与 overlay 机制配套的scripts/dtc/of_unittest_expect脚本用于过滤预期告警、高亮异常。运行时如何触发 resolver普通用户与驱动开发者通常不直接调用 resolver而是经由 overlay API 间接触发int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size, u32 *ret_ovcs_id, const struct device_node *base);该接口在 drivers/of/overlay.c 中定义并EXPORT_SYMBOL_GPL导出内核模块可调用它将一段 FDTflattened devicetree即.dtbo的二进制内容注册为 overlay changesetof_overlay_apply()内部第一步调用of_resolve_phandles()随后依次构建 changeset、触发OF_OVERLAY_PRE_APPLY/POST_APPLY通知最终完成设备节点的增删。对应的移除接口是of_overlay_remove()全部移除可用of_overlay_remove_all()。对编译期的要求要想让 resolver 正常工作基础树base tree在编译时必须开启符号表生成dtc - -I dts -O dtb -o base.dtb base.dts否则 live tree 根节点下没有/__symbols__of_resolve_phandles()会直接报no symbols in root of device tree.并失败。overlay 源文件则必须以/plugin/声明且包含对外部节点的引用dtc 才会生成__fixups__与__local_fixups__节点。这与文档 overlay-notes.rst 中基础 DT 未用-编译则ocp标签不可用的说明相互印证该文档同时给出了改用显式路径{/ocp}的替代写法。总结Linux 内核的 Devicetree 动态解析器drivers/of/resolver.c通过最大 phandle 1的偏移策略把 overlay 内部 phandle 重定位到不与 live tree 冲突的区间再借助 dtc 生成的__local_fixups__与__fixups__两个目录节点分别完成内部引用的统一抬升与外部引用的精确替换。整个流程在 of_resolve_phandles() 中闭环并被 drivers/of/overlay.c 的of_overlay_apply()作为应用 overlay 的第一步调用。结合 drivers/of/unittest-data 中的真实 overlay 测试数据开发者可以在源码层面完整复现并验证文档所描述的六步解析过程为编写自己的可加载设备树 overlay 提供坚实的理论与实践依据。进一步阅读overlay-notes.rstoverlay 应用机制与 notifier 说明of_unittest.rstOF 单元测试框架与测试数据挂载方式drivers/of/overlay.coverlay changeset 的构建、应用与移除drivers/of/unittest-data/overlay.dtso同时覆盖内部/外部引用的典型 overlay 示例。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考