摘要:本文源码级拆解
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.h
Zephyr 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)\(&