
04-1从 DEVICE_DT_DEFINE 到 struct device验证方式Zephyr 源码、ELF/map、native_sim/native/64验证结论源码确认 编译确认 模拟确认1. 本节目标本节建立 Zephyr Device Model 的第一条完整链路Devicetree node - DT_INST_FOREACH_STATUS_OKAY - DEVICE_DT_INST_DEFINE - struct device device_state init entry - kernel 在 main 前调用 init - DEVICE_DT_GET 取得 device pointer - device_is_ready 检查初始化状态本节重点理解设备对象的组成和生命周期不深入初始化优先级、依赖排序和 API table 调度。第四章计划分为五节04-1 DEVICE_DT_DEFINE 与 struct device 04-2 初始化 level、priority 与启动顺序 04-3 初始化失败、依赖与 device_is_ready 04-4 driver API table 与子系统调用 04-5 多实例驱动、常见问题与第四章总结2. Device Model 解决什么问题不同驱动的硬件和业务不同但 Zephyr 希望用统一对象表达这个设备叫什么 它的只读硬件配置在哪里 它的可变运行状态在哪里 它提供什么 API 初始化是否已经执行并成功 它依赖哪些其他设备这就是struct device的职责。应用通常拿到conststructdevice*dev;然后检查设备是否 ready通过子系统 API 使用设备不直接了解驱动私有 data/config 的结构。3. 本地 Zephyr 的 struct device源码位置zephyr/include/zephyr/device.h:453核心字段可简化为structdevice{constchar*name;constvoid*config;constvoid*api;structdevice_state*state;void*data;/* 根据 Kconfig 还可能有 deps、PM、DT metadata 等字段 */};需要先记住五个核心指针字段含义常见存储性质name设备实例名称只读字符串config设备实例的固定配置static const通常 ROM/rodataapi驱动对外函数表static const通常 ROM/rodatastateZephyr 维护的公共初始化状态可变状态data驱动实例私有运行数据可变 RAM/BSS源码确认4. config 与 data 必须分开实验驱动定义structlearning_device_config{intinitial_value;};structlearning_device_data{intcurrent_value;bool init_was_called;};区别config 构建时由 Devicetree 决定 初始化后通常不改变 多数驱动声明为 static const data 运行过程中会改变 保存状态、buffer、callback、锁、计数等 不能声明为 const本节的具体值config.initial_value 42 data.current_value 0BSS 初值init 执行后data.current_value 42 data.init_was_called true5. Devicetree 实例overlay/ { learning_device0: learning-device-0 { compatible zephyr,learning-device; status okay; initial-value 42; }; };bindingcompatible:zephyr,learning-deviceinclude:base.yamlproperties:initial-value:type:intrequired:true生成结果#defineDT_N_INST_0_zephyr_learning_deviceDT_N_S_learning_device_0#defineDT_N_S_learning_device_0_P_initial_value42编译确认6. 每实例 config 和 data驱动使用#defineDT_DRV_COMPATzephyr_learning_device每实例展开宏#defineLEARNING_DEVICE_DEFINE(inst)\staticstructlearning_device_data\learning_device_data_##inst;\staticconststructlearning_device_config\learning_device_config_##inst{\.initial_value\DT_INST_PROP(inst,initial_value),\};\DEVICE_DT_INST_DEFINE(inst,learning_device_init,NULL,\learning_device_data_##inst,\learning_device_config_##inst,\POST_KERNEL,50,NULL);DT_INST_FOREACH_STATUS_OKAY(LEARNING_DEVICE_DEFINE)因为只有一个 status-okay 实例简化展开后会产生learning_device_data_0 learning_device_config_0 一个 struct device 一个 device_state 一个 init entry7. DEVICE_DT_INST_DEFINE 做了什么本地定义zephyr/include/zephyr/device.h:246#defineDEVICE_DT_INST_DEFINE(inst,...)\DEVICE_DT_DEFINE(DT_DRV_INST(inst),__VA_ARGS__)它只是先把inst转成 node identifier再调用DEVICE_DT_DEFINE(node_id,...)所以DEVICE_DT_DEFINE 接收明确 node identifier DEVICE_DT_INST_DEFINE 接收当前 DT_DRV_COMPAT 的 instance number驱动多实例模式通常使用后者。8. DEVICE_DT_DEFINE 做了什么本地定义zephyr/include/zephyr/device.h:229核心工作可以概括为1. 为 node 创建 device_state 2. 创建全局 struct device 3. 把 name/config/api/state/data 写入 device object 4. 注册 init function、level 和 priority 5. 把对象放进 Zephyr 特定 iterable/linker section它不是普通的局部变量声明也不是运行时malloc()。设备对象及初始化记录在链接阶段就已经存在于镜像中。9. 本节 DEVICE_DT_INST_DEFINE 参数DEVICE_DT_INST_DEFINE(inst,learning_device_init,NULL,learning_device_data_##inst,learning_device_config_##inst,POST_KERNEL,50,NULL);参数含义参数本实验值含义inst0当前 compatible instanceinit_fnlearning_device_init启动初始化函数pmNULL暂不使用设备电源管理datalearning_device_data_0私有可变数据configlearning_device_config_0私有只读配置levelPOST_KERNELkernel 可用后初始化prio50同 level 内的初始化优先级apiNULL本节暂不建立 API tablelevel 和 priority 将在 04-2 单独验证。10. init function 的输入staticintlearning_device_init(conststructdevice*dev){conststructlearning_device_config*configdev-config;structlearning_device_data*datadev-data;data-current_valueconfig-initial_value;data-init_was_calledtrue;return0;}初始化函数已经拿到了完整struct device因此能从中取得对应实例的 config 和 data。返回值0 初始化成功 非 0 初始化失败device 不应被认为 ready失败路径将在 04-3 用故障注入验证。11. init 为什么在 main 之前运行DEVICE_DT_DEFINE()不只是生成 device object还创建 init entry。Zephyr 启动代码按初始化 level 和 priority 遍历这些 entry在进入应用main()前调用相应 init function。本节选择POST_KERNEL, priority 50实际输出顺序[init] devicelearning-device-0 initial42 current_before0 *** Booting Zephyr OS build v4.1.0-rc1 *** [main] devicelearning-device-0 ready1 init_called1 initial42 current42结论learning_device_init() 先执行 main() 后执行并观察到 init 已修改 data模拟确认12. DEVICE_DT_GET 不是运行时查找main 中#defineLEARNING_DEVICE_NODEDT_NODELABEL(learning_device0)staticconststructdevice*constlearning_devDEVICE_DT_GET(LEARNING_DEVICE_NODE);本地定义#defineDEVICE_DT_GET(node_id)(DEVICE_DT_NAME_GET(node_id))这表示DEVICE_DT_GET 在编译/链接期引用 node 对应的全局 device object 不是按字符串遍历设备表 不会自动检查 init 是否成功如果 node 存在但没有驱动为它执行DEVICE_DT_DEFINE()通常会在链接时报错undefined reference to __device_dts_ord_N这是很重要的排查信号。13. node identifier 与 device pointer不要混淆DT_NODELABEL(learning_device0)这是预处理阶段使用的 node identifier。DEVICE_DT_GET(DT_NODELABEL(learning_device0))这是指向运行时struct device对象的 C pointer。转换关系node identifier - DEVICE_DT_GET - const struct device *node identifier 自己不是指针也不能传给运行时 device API。14. device_is_ready 的真实判断本地实现zephyr/kernel/device.c:132boolz_impl_device_is_ready(conststructdevice*dev){if(devNULL){returnfalse;}returndev-state-initialized(dev-state-init_res0U);}所以 ready 至少要求init function 已经被调用 init result 表示成功device_is_ready()不是Devicetree status 的别名真实总线通信测试传感器数据有效性测试永久保证设备以后不会出错。本节结果ready1因为 init 已执行并返回 0。模拟确认15. device name 从哪里来DEVICE_DT_DEFINE()使用DEVICE_DT_NAME(node_id)本地规则如果 node 有 label property - 使用 label property 字符串 否则 - 使用 node full name实验节点没有labelproperty因此dev-name learning-device-0不要把这里的dev-name再与 node labellearning_device0混淆。16. ELF 与 map 证据检查nm-n/mnt/c/study/1-zephyr/work/ch04_device_object/zephyr/zephyr.elf\|rglearning_device|__device_dts_ord|__init_实际关键 symbollearning_device_config_0 learning_device_data_0 learning_device_init __device_dts_ord_12 __init___device_dts_ord_12生成头文件说明#defineDT_N_S_learning_device_0_ORD12因此 ordinal 12 对应/learning-device-0。map 中还能看到.rodata.learning_device_config_0 .bss.learning_device_data_0 __device_dts_ord_12这与设计相符config - rodata data - BSS/RAM device init entry - 静态链接对象编译确认17. 本节最小构建cd~/project/exportZEPHYR_SDK_INSTALL_DIR/home/yff/zephyr-sdk/zephyr-sdk-0.17.1sourcezephyr/zephyr-env.sh west build\-bnative_sim/native/64\/mnt/c/study/1-zephyr/labs/ch04_device_model\--build-dir /mnt/c/study/1-zephyr/work/ch04_device_object\-palways /mnt/c/study/1-zephyr/work/ch04_device_object/zephyr/zephyr.exe实验不需要 GPIO、SPI、interrupt 或真实传感器验证的是 Zephyr Device Model 本身。19. 常见错误19.1 config 没有 const固定硬件配置通常应放在只读区。误放入可变 data 会增加 RAM 使用并模糊职责。19.2 在 config 中保存运行状态config 可能位于只读存储不能用于计数、callback 状态或实时数据。19.3 DEVICE_DT_GET 后不检查 ready取得 pointer 不表示 init 成功。conststructdevice*devDEVICE_DT_GET(node_id);if(!device_is_ready(dev)){/* handle error */}19.4 把 status okay 当 readystatus okay 只让实例参与构建ready 是 init 后的运行时状态。19.5 看到 __device_dts_ord_N undefined 就怀疑编译器通常应检查对应 driver Kconfig 是否为 ydriver.c是否进入编译node compatible/status 是否正确driver 是否执行DEVICE_DT_DEFINE/INST_DEFINE。19.6 在应用中直接访问 dev-data本实验为了教学展示内部结构。正常应用应通过驱动/子系统 API 使用设备避免依赖私有 data/config 类型。20. 可迁移到其他驱动的结论struct device是配置、数据、API 和公共状态的统一连接点config 通常static constdata 通常位于可变 RAMDEVICE_DT_INST_DEFINE()为 Devicetree instance 创建静态 device object 和 init entryDEVICE_DT_GET()取得全局对象地址不执行字符串查找也不检查 readydevice_is_ready()检查 init 已执行且返回成功status okay、device object 存在和 runtime ready 是不同阶段。下一节04-2-初始化level、priority与启动顺序。