ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zephyr BSP: 18-Zephyr Devicetree 宏展开全解析

Zephyr BSP: 18-Zephyr Devicetree 宏展开全解析 摘要:本文源码级拆解DT_NODELABEL→devicetree_generated.h→ dependency ordinal →__device_dts_ord_xx→struct device的完整编译期映射链路,并手把手教你用grep/nm定位undefined reference to __device_dts_ord_xx链接错误。适合 Zephyr BSP 开发者。18 — 手动拆解 devicetree_generated.h:从 DT_NODELABEL 到 struct device这一篇非常关键。前面你已经把 Zephyr 的链条一路追到了:Devicetree ↓ DT compiler ↓ devicetree_generated.h ↓ DT_NODELABEL(...)↓ dependency ordinal ↓ device handle ↓ struct device这一篇我们不再停留在宏 API 层面,而是直接"手撕"生成出来的 devicetree_generated.h,看看一个:DT_NODELABEL(uart1)最后到底是怎么找到:struct device的。一、先建立目标:我们到底要追什么?假设 Board 的 DTS 中有:usart1{status="okay";current-speed=115200;};或者 SoC DTS 中:usart1:usart@40013800{compatible="st,stm32-usart";reg=0x400138000x400;status="okay";};应用程序可能写:#defineUART_NODEDT_NODELABEL(usart1)conststructdevice*uart=DEVICE_DT_GET(UART_NODE);然后:if(!device_is_ready(uart)){return;}我们真正要回答的问题是:DT_NODELABEL(usart1) 明明只是一个宏,它怎么最终变成一个真实的 struct device *?二、第一层:DT_NODELABEL(usart1) 到底是什么?先看:DT_NODELABEL(usart1)表面上你可能认为:usart1 ↓ 某个节点但实际上它最终会映射到一个类似这样的内部 token:DT_N_NODELABEL_usart1也就是说:DT_NODELABEL(usart1)本质上是在生成一个:DT_N_NODELABEL_usart1这样的Devicetree node identifier。三、注意:这个东西不是 struct device这是理解 Zephyr Devicetree 的第一个关键点。DT_NODELABEL(usart1)得到的不是:struct device *甚至不是:struct device它只是一个:Devicetree Node Identifier也就是:"Devicetree 中的这个节点是谁?"例如:DT_NODELABEL(usart1)│ ▼ Devicetreenode│ │ ▼ /soc/usart@40013800所以:DT_NODELABEL(usart1)解决的是:Node identification而:DEVICE_DT_GET(...)解决的是:Node → Device这两个阶段不要混在一起。四、第二层:打开 devicetree_generated.hZephyr build 完以后,可以找到类似:build/zephyr/include/generated/zephyr/devicetree_generated.h不同 Zephyr 版本/build system 路径可能略有区别。你可以直接:findbuild-namedevicetree_generated.h然后:grep-n"NODELABEL_usart1"\build/zephyr/include/generated/zephyr/devicetree_generated.h你会看到大量类似:# define DT_N_NODELABEL_usart1 ...以及各种:DT_N_S_soc_S_usart_40013800之类的定义。五、为什么生成文件里面名字这么奇怪?例如:/soc/usart@40013800可能被编码成:DT_N_S_soc_S_usart_40013800这里:DT_N表示 Devicetree node。S_soc表示:socS_usart_40013800表示:usart@40013800因此你可以把它理解成:DTS path ↓ /soc/usart@40013800 ↓ C identifier ↓ DT_N_S_soc_S_usart_40013800这就是 Zephyr 为什么能够把 DTS 节点变成 C 预处理器可以操作的东西。六、DT_NODELABEL() 实际上做了什么?Zephyr 的 Devicetree macro system 会把:DT_NODELABEL(usart1)映射到:DT_N_NODELABEL_usart1然后:DT_N_NODELABEL_usart1又会对应到真正的 node identifier。概念上可以理解成:DT_NODELABEL(usart1)│ ▼ DT_N_NODELABEL_usart1 │ ▼ DT_N_S_soc_S_usart_40013800于是:# define UART_NODE DT_NODELABEL(usart1)实际上已经把:UART_NODE绑定到了:/soc/usart@40013800这个 Devicetree node。七、第三层:Node Identifier 怎么产生 dependency ordinal?这里就是你上一章:phandle → dependency ordinal → device handle开始真正发挥作用的地方。Zephyr 会给 Devicetree nodes 建立 dependency graph。例如:┌──────────────┐ │ interrupt │ │ controller │ └──────▲───────┘ │ │ interrupt-parent │ │ ┌─────────┐ ┌─────┴─────┐ │ clock │─────▶│ USART1 │ └─────────┘ └───────────┘ │ ▼ pinctrlDevicetree compiler / Zephyr 的生成过程会根据这些 dependency 建立 ordinal。例如概念上:ordinal0interrupt controller ordinal5clock controller ordinal12pinctrl ordinal20USART1所以 USART1 node 最终会拥有一个:dependency ordinal=20注意:这个 ordinal 不是 UART 编号。不是:UART1=ordinal1完全不是。它是:整个 Devicetree dependency graph 中这个 node 的唯一排序标识。八、为什么需要 ordinal?因为 Zephyr 最终需要解决一个问题:Devicetree Node ↓ 对应哪个 struct device?如果直接拿:DT_N_S_soc_S_usart_40013800作为 C symbol,当然也可以。但 Zephyr 的 device model 还需要表达:这个 device 依赖哪些 device?于是 ordinal 就成为一个非常重要的桥梁。概念上:Devicetree Node │ ▼ dependency ordinal │ ▼ device object九、第四层:看 DEVICE_DT_GET()应用程序:#defineUART_NODEDT_NODELABEL(usart1)conststructdevice*uart=DEVICE_DT_GET(UART_NODE);这里是整个链条最重要的地方之一。不要把:DEVICE_DT_GET(UART_NODE)理解成:device_find("usart1")它不是运行时查找。没有:hashtable lookup search string compare这些东西。它本质上是:编译期把 Devicetree node 映射成一个 C symbol。十、把宏展开成概念模型你可以先不要管 Zephyr 真实宏定义里的各种:DT_CAT(...)DT_DEP_ORD(...)DT_INVALID_NODE先建立这个简化模型:DEVICE_DT_GET(node)≈DEVICE_OBJECT_FOR(node)然后:DEVICE_DT_GET(DT_NODELABEL(usart1))≈device objectforUSART1最终类似:\__device_dts_ord_20十·补充、真实宏定义拆解:DT_NODELABEL 与 DEVICE_DT_GET 的源码级展开上面我们建立了概念模型,现在直接打开 Zephyr 源码,看看这些宏在include/zephyr/devicetree.h和include/zephyr/device.h里到底是怎么定义的。这样你以后读源码时,就不会被层层DT_CAT绕晕。1)DT_NODELABEL的源码定义(include/zephyr/devicetree.h)/** * @brief Get a node identifier for a node label */#defineDT_NODELABEL(label)DT_CAT(DT_N_NODELABEL_,label)逐行拆解:label:宏参数,就是你在 DTS 里写的节点 label,例如usart1。DT_CAT(a, b):Zephyr 的拼接宏,本质是a ## b,把两个 token 粘合成一个。DT_N_NODELABEL_:固定前缀,表示「这是一个通过 node label 索引的 Devicetree node identifier」。所以:DT_NODELABEL(usart1)展开为:DT_CAT(DT_N_NODELABEL_,usart1)再展开为:DT_N_NODELABEL_usart1而DT_N_NODELABEL_usart1这个宏,是在devicetree_generated.h里由构建系统生成的,它指向真正的 node identifier:#defineDT_N_NODELABEL_usart1DT_N_S_soc_S_usart_400138002)DT_DEP_ORD的源码定义(include/zephyr/devicetree.h)/** * @brief Get the dependency ordinal of a node */#defineDT_DEP_ORD(node_id)DT_CAT(node_id,_ORD)逐行拆解:node_id:宏参数,一个 Devicetree node identifier,例如DT_N_S_soc_S_usart_40013800。_ORD:后缀,表示「dependency ordinal」。DT_CAT(node_id, _ORD):把 node identifier 和_ORD拼起来,得到该节点的 ordinal 宏名。所以:DT_DEP_ORD(DT_N_S_soc_S_usart_40013800)展开为:DT_N_S_soc_S_usart_40013800_ORD而devicetree_generated.h里会生成:#defineDT_N_S_soc_S_usart_40013800_ORD20于是DT_DEP_ORD(...)最终得到整数20。3)DEVICE_DT_GET的源码定义(include/zephyr/device.h)/** * @brief Get a device reference from a devicetree node */#defineDEVICE_DT_GET(node_id)\DEVICE_DT_GET_ONE(node_id)再往下追,DEVICE_DT_GET_ONE最终会落到:#defineDEVICE_DT_GET_ONE(node_id)\(
返回列表