Zephyr RTOS 设备树机制解析

代码版本zephyr/VERSION = 4.4.99
重点源码cmake/modules/dts.cmakescripts/dts/{gen_edt.py,gen_defines.py}include/zephyr/devicetree.hinclude/zephyr/device.hinclude/zephyr/init.hkernel/init.ckernel/device.cinclude/zephyr/pm/device.h
友情提示:这篇文章主要是为了能更好的分析后续蓝牙相关代码写的,并未深入分析dts是怎么生成的,自从有了AI,工具脚本相关代码就不怎么仔细研究了。如果想深入研究的,可以参考该链接:https://www.cnblogs.com/jayant97/articles/17209392.html


先给你一个“看懂设备树”的最短闭环

如果你是第一次接触 Zephyr 设备树,建议不要一上来就死磕所有宏定义。先把“它到底在做什么”这个问题抓住:

  • 设备树负责“描述硬件”;
  • DT_* / DEVICE_DT_* 宏负责“把描述变成 C 语言可用的对象”;
  • 系统启动时,再把这些对象按优先级和初始化阶段执行起来。

最小闭环可以这样理解:

设备树节点(.dts / .dtsi / overlay)
  └─ DT_DRV_COMPAT / DT_DRV_INST(inst)
       └─ DT_N_INST_0_... -> DT_N_S_...
            └─ DEVICE_DT_INST_DEFINE(...)
                 └─ 生成 struct device + init entry
                      └─ 启动时由 z_sys_init_run_level() 调用
                           └─ dev->ops.init(...)

这条链路里,最值得你记住的 5 个点是:

  1. DT_DRV_COMPAT:当前驱动支持哪种 compatible。
  2. DT_DRV_INST(inst):当前驱动的第几个实例。
  3. DEVICE_DT_INST_DEFINE(...):把设备树节点和驱动数据、配置、API 绑定成一个 struct device
  4. Z_DEVICE_INIT_ENTRY_DEFINE():生成 init entry,供系统启动时调用。
  5. z_sys_init_run_level():启动阶段按 PRE_KERNEL / POST_KERNEL / APPLICATION 依次执行。

把它们串起来看,你就会明白:设备树不是“只给你看配置”,而是“把硬件描述和驱动初始化机制真正连接起来”。

一个最贴近真实代码的例子

本文的贯穿实例是 nRF54L 系列 SoC 的 Zephyr 原生软件蓝牙控制器(compatible = zephyr,bt-hci-ll-sw-split)。

它涉及两个文件:

  • zephyr/subsys/bluetooth/controller/hci/hci_driver.c(驱动侧,负责“生成设备对象”);
  • zephyr/subsys/bluetooth/host/hci_core.c(host 侧,负责“拿到并使用设备对象”)。
/* hci_core.c */
#define BT_HCI_NODE DT_CHOSEN(zephyr_bt_hci)
#define BT_HCI_DEV  DEVICE_DT_GET(BT_HCI_NODE)

BT_HCI_DEV 从 chosen node zephyr_bt_hci 取出的设备对象,正是 hci_driver.cDEVICE_DT_INST_DEFINE(...) 生成的那一个 —— 两边通过同一个设备树节点“对上了号”。

具体怎么定义、怎么匹配、何时初始化、如何使用,见第二~五章。

建议的阅读顺序(初学者)

如果你想快速入门,建议按下面顺序看:

  1. 先看 include/zephyr/devicetree.h:理解 DT_* 宏是什么。
  2. 再看 include/zephyr/device.h:理解 DEVICE_DT_* 宏如何生成设备对象。
  3. 然后看 kernel/init.c:理解启动时如何调用这些设备。
  4. 最后再回头看 cmake/modules/dts.cmakescripts/dts/*.py:理解设备树是如何生成这些宏的。

这样看,知识点会从“宏名看起来非常抽象”变成“从设备树到系统初始化的一条清晰链路”。


一、总体架构

Zephyr 的设备树用于把硬件描述从驱动代码中剥离出来:

硬件事实(SoC/board/shield/app overlay)
  ├─ 地址、中断、时钟、GPIO、bus 拓扑
  ├─ compatible 与 binding
  └─ status/属性覆盖

构建期转换
  ├─ 合并 DTS
  ├─ edtlib 解析 binding
  ├─ 生成 C 预处理宏
  └─ 生成设备对象/初始化入口

驱动代码
  ├─ 通过 DT_* 宏取属性
  ├─ 通过 DEVICE_DT_* 宏实例化设备
  └─ 通过 DEVICE_DT_GET()/device_is_ready() 使用设备

核心价值:同一驱动适配多个 SoC/板级硬件;硬件配置在构建期检查;驱动不需要硬编码寄存器地址、中断号、GPIO 编号。

本文以 zephyr,bt-hci-ll-sw-split 为贯穿实例,从驱动中最常见的宏 DEVICE_DT_INST_DEFINE() 入手,沿“定义 → 匹配 → 初始化 → 使用”的调用链逐层分析。


二、怎么定义的:从 DTS 节点到驱动代码

zephyr,bt-hci-ll-sw-split 为例,一个设备要能存在,需要三处“对齐”:设备树节点(硬件在哪)、binding(属性怎么解释)、驱动代码(谁来实例化)。

2.1 设备树节点:SoC 声明,board 使能

节点首先在 SoC 级 dtsi 中声明,例如 zephyr/dts/vendor/nordic/nrf54l_05_10_15.dtsi

bt_hci_controller: bt_hci_controller {
    compatible = "zephyr,bt-hci-ll-sw-split";
    status = "disabled";
};
  • bt_hci_controller:node label,后面可用 DT_NODELABEL(bt_hci_controller) 引用;

  • SoC 级 nrf54l_05_10_15.dtsi 默认 disabled,由 SoC 应用核的 zephyr/dts/arm/nordic/nrf54l_05_10_15_cpuapp.dtsi:40 使能:

    &bt_hci_controller {
        status = "okay";
    };
    

    在合并后的 build/zephyr/zephyr.dts 中可见使能来源标注:status = "okay"; /* in zephyr/dts/arm/nordic/nrf54l_05_10_15_cpuapp.dtsi:40 */

  • 本实例最终路径为 /soc/peripheral@50000000/radio@8a000/bt_hci_controllerzephyr.dts:467 注释可确认)。

2.2 Binding:compatible 的属性定义

zephyr/dts/bindings/bluetooth/zephyr,bt-hci-ll-sw-split.yaml

description: Bluetooth HCI provided by the native Zephyr Bluetooth Controller

compatible: "zephyr,bt-hci-ll-sw-split"

include: bt-hci.yaml

properties:
  bt-hci-name:
    default: "Controller"
  bt-hci-bus:
    default: "virtual"
  bt-hci-quirks:
    default: ["no-auto-dle"]
  • include: bt-hci.yaml 引入公共 HCI 属性;
  • 构建期 gen_defines.py 会据此为节点生成 DT_N_..._P_bt_hci_quirks 等宏。

2.3 驱动侧:DT_DRV_COMPAT + DEVICE_DT_INST_DEFINE

zephyr/subsys/bluetooth/controller/hci/hci_driver.c

#define DT_DRV_COMPAT zephyr_bt_hci_ll_sw_split
...
static DEVICE_API(bt_hci, hci_driver_api) = {
    .open  = hci_driver_open,
    .close = hci_driver_close,
    .send  = hci_driver_send,
};

#define BT_HCI_CONTROLLER_INIT(inst) \
    static struct bt_hci_driver_data data_##inst; \
    static const struct bt_hci_driver_config config_##inst = \
                        BT_DT_HCI_DRIVER_CONFIG_INST_GET(inst); \
    DEVICE_DT_INST_DEFINE(inst, NULL, NULL, &data_##inst, &config_##inst, \
                          POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
                          &hci_driver_api)

/* Only a single instance is supported */
BT_HCI_CONTROLLER_INIT(0)

要点:

  • DT_DRV_COMPAT 与 binding 的 compatible 一一对应(zephyr,bt-hci-ll-sw-splitzephyr_bt_hci_ll_sw_split);
  • data_0:驱动运行时数据,struct bt_hci_driver_data 第一个成员是 host 注册进来的 recv 回调;
  • config_0BT_DT_HCI_DRIVER_CONFIG_INST_GET(0) 从设备树生成 { .quirks = ... }
  • hci_driver_api.open / .close / .send 三个回调;
  • 只实例化 inst = 0(单实例支持,注释已说明)。

三、怎么一步一步匹配上的(宏展开链)

DEVICE_DT_INST_DEFINE(0, ...) 是怎么“认识”到 /soc/.../bt_hci_controller 的?靠的是构建期生成的 devicetree_generated.h

3.1 第一步:DEVICE_DT_INST_DEFINE → DEVICE_DT_DEFINE(DT_DRV_INST(0), …)

include/zephyr/device.h

#define DEVICE_DT_INST_DEFINE(inst, ...) \
    DEVICE_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__)

3.2 第二步:DT_DRV_INST(0) → DT_INST(0, zephyr_bt_hci_ll_sw_split) → DT_N_INST_0_zephyr_bt_hci_ll_sw_split

include/zephyr/devicetree.h

#define DT_DRV_INST(inst) DT_INST(inst, DT_DRV_COMPAT)
#define DT_INST(inst, compat) UTIL_CAT(DT_N_INST, DT_DASH(inst, compat))
#define DT_DASH(...) MACRO_MAP_CAT(DT_DASH_PREFIX, __VA_ARGS__)
#define DT_DASH_PREFIX(name) _##name

DT_INST 本质只是“拼宏名”:0zephyr_bt_hci_ll_sw_split 被拼成 DT_N_INST_0_zephyr_bt_hci_ll_sw_split。它自己并不知道节点在哪,真正的映射在下一步。

3.3 第三步:生成头文件把宏名映射到真实节点

build/zephyr/include/generated/zephyr/devicetree_generated.h

#define DT_N_INST_0_zephyr_bt_hci_ll_sw_split \
    DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller

DT_N_S_... 是按路径生成的节点标识符,对应:

/soc/peripheral@50000000/radio@8a000/bt_hci_controller

至此,DT_DRV_INST(0) 才真正指向了那个设备树节点。

3.4 第四步:DEVICE_DT_DEFINE(node_id, …) 生成设备对象与 init entry

#define DEVICE_DT_DEFINE(node_id, init_fn, pm, data, config, level, prio, api, ...) \
    DEVICE_DT_DEINIT_DEFINE(node_id, init_fn, NULL, pm, data, config,              \
                            level, prio, api, __VA_ARGS__)

内部展开链:

DEVICE_DT_DEFINE(node_id, NULL, NULL, &data_0, &config_0, POST_KERNEL, ...)
  └─ DEVICE_DT_DEINIT_DEFINE(...)
       ├─ Z_DEVICE_STATE_DEFINE(...)        ← struct device_state(RAM)
       └─ Z_DEVICE_DEFINE(...)
            ├─ 可选 Z_DEVICE_DEPS_DEFINE()
            ├─ 可选 Z_DEVICE_DT_METADATA_DEFINE()
            ├─ Z_DEVICE_BASE_DEFINE(...)    ← struct device(放 iterable section)
            │    └─ STRUCT_SECTION_ITERABLE_NAMED_ALTERNATE(device, ...)
            └─ Z_DEVICE_INIT_ENTRY_DEFINE() ← struct init_entry(放 init section)

Z_DEVICE_BASE_DEFINE() 生成的设备对象形如:

static const struct device __device_dts_ord_<N> = {
    .name   = "bt_hci_controller",   /* DEVICE_DT_NAME(node_id) */
    .config = &config_0,
    .api    = &hci_driver_api,
    .state  = &__devstate_dts_ord_<N>,
    .data   = &data_0,
    .ops    = { .init = NULL, ... },
    .flags  = 0,
};

小知识:__device_dts_ord_<N> 里的 <N> 是节点在依赖图中的 ordinal(DT_DEP_ORD)。所以当 DEVICE_DT_GET()undefined reference to __device_dts_ord_<N> 时,说明该节点没有对应的驱动实例化设备。

四、什么时候初始化

4.1 init entry:设备和 SYS_INIT 的统一入口

Z_DEVICE_INIT_ENTRY_DEFINE() 创建初始化入口:

static const Z_DECL_ALIGN(struct init_entry) __used __noasan
Z_INIT_ENTRY_SECTION(level, prio, Z_DEVICE_INIT_SUB_PRIO(node_id))
Z_INIT_ENTRY_NAME(DEVICE_NAME_GET(dev_id)) = {
    .init_fn = NULL,                                          /* 设备 init entry 的 init_fn 为 NULL */
    .dev = (const struct device *)&DEVICE_NAME_GET(dev_id),   /* dev 指向设备对象 */
};

设备 init entry 的 init_fnNULLdev 指向设备对象;普通 SYS_INIT() 的 entry 则相反(init_fn != NULL, dev = NULL)。

include/zephyr/init.h 当前 struct init_entry

struct init_entry {
    int (*init_fn)(void);
    const struct device *dev;
};

4.2 启动时遍历 init entry

本实例注册在 POST_KERNEL 级、CONFIG_KERNEL_INIT_PRIORITY_DEVICE 优先级。kernel/init.cz_sys_init_run_level()

for (entry = levels[level]; entry < levels[level + 1]; entry++) {
    const struct device *dev = entry->dev;
    int result = 0;

    if (dev != NULL) {
        /* 设备 init entry:调用 dev->ops.init */
        if ((dev->flags & DEVICE_FLAG_INIT_DEFERRED) == 0U) {
            result = do_device_init(dev);
        }
    } else {
        /* 普通 SYS_INIT entry */
        result = entry->init_fn();
    }
}

启动阶段顺序:

z_cstart()
  ├─ z_sys_init_run_level(EARLY)
  ├─ arch_kernel_init()
  ├─ z_device_state_init()
  ├─ soc_early_init_hook() / board_early_init_hook()
  ├─ z_sys_init_run_level(PRE_KERNEL_1)
  ├─ arch_smp_init()
  ├─ z_sys_init_run_level(PRE_KERNEL_2)
  └─ 切到 main thread -> bg_thread_main()

bg_thread_main()
  ├─ z_sys_init_run_level(POST_KERNEL)   ← 本实例在此阶段被“过一遍”
  ├─ soc_late_init_hook() / board_late_init_hook()
  ├─ z_sys_init_run_level(APPLICATION)
  ├─ z_init_static_threads()
  └─ CONFIG_SMP: z_sys_init_run_level(SMP)

4.3 do_device_init()

int do_device_init(const struct device *dev)
{
    int rc = 0;

    if (dev->ops.init != NULL) {
        rc = dev->ops.init(dev);
        if (rc != 0) {
            dev->state->init_res = -rc;     /* init 失败时保存正的 errno */
        }
    }

    dev->state->initialized = true;         /* 无论成功失败都标记 initialized */
    /* 成功后自动执行 runtime PM auto enable */
    if (rc == 0) {
        (void)pm_device_runtime_auto_enable(dev);
    }

    return -rc;
}

4.4 本实例的特殊性:boot 时其实“什么都没做”

注意 BT_HCI_CONTROLLER_INIT(0) 传给 DEVICE_DT_INST_DEFINEinit_fn = NULL

  • 启动时 dev->ops.init(dev) 是空操作,仅把 state->initialized 置 true;
  • 真正的初始化发生在 host 调用 bt_enable()bt_hci_open() 时,由 hci_driver_open() 完成:ll_init()、创建 recv_thread / prio_recv_thread 等。

这是 HCI“软设备”与传感器驱动最大的不同:传感器在 boot 时就探测硬件;HCI 控制器要等 host 主动打开。


五、后续怎么使用(chosen + DEVICE_DT_GET)

5.1 host 怎么找到它:chosen node

zephyr/subsys/bluetooth/host/hci_core.c

#if DT_HAS_CHOSEN(zephyr_bt_hci)
#define BT_HCI_NODE   DT_CHOSEN(zephyr_bt_hci)
#define BT_HCI_DEV    DEVICE_DT_GET(BT_HCI_NODE)
#endif

生成头文件里:

#define DT_CHOSEN_zephyr_bt_hci \
    DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller

与驱动实例(3.3 节)指向同一个节点 —— 这就是匹配闭环。

5.2 bt_dev.hci 持有设备指针

struct bt_dev bt_dev = {
    ...
    .hci = BT_HCI_DEV,      /* = &__device_dts_ord_<N> */
};

5.3 打开与双向数据流

bt_enable()bt_hci_open(bt_dev.hci, bt_recv)hci_core.c):

static inline int bt_hci_open(const struct device *dev, bt_hci_recv_t recv)
{
    struct bt_hci_driver_data *data = dev->data;

    data->recv = recv;                                     /* host 注册收包回调 */
    err = DEVICE_API_GET(bt_hci, dev)->open(dev);          /* → hci_driver_open() */
    ...
}
Host → Controller: bt_hci_send(bt_dev.hci, buf)
                     └─ DEVICE_API_GET(bt_hci, dev)->send(dev, buf)
                          └─ hci_driver_send() → cmd_handle()/acl_handle()/iso_handle()

Controller → Host: recv_thread() → bt_hci_recv(dev, buf)
                     └─ data->recv(dev, buf)   /* 即 host 注册的 bt_recv */

注意:bt_hci_open() 通过 DEVICE_API_GET(bt_hci, dev) 取设备对象里的 api 字段(&hci_driver_api)再调 .open;而 data_0 里的 recv 字段则是 host 反向注册给驱动用的。这一“取”一“注册”,正好对应 3.4 节设备对象里的 .api.data


六、DTS 构建流程

本文 2.3 节 config_0 用到的 BT_DT_HCI_DRIVER_CONFIG_INST_GET(0)、3.3 节的 DT_N_INST_0_... 等宏,最终都来自构建期生成的 devicetree_generated.h(由 binding 的属性默认值展开而来,例如 bt-hci-quirks 的默认 ["no-auto-dle"])。

cmake/modules/dts.cmake 中的关键变量:

set(GEN_EDT_SCRIPT         ${DT_SCRIPTS}/gen_edt.py)
set(GEN_DEFINES_SCRIPT     ${DT_SCRIPTS}/gen_defines.py)
set(EDT_PICKLE             ${PROJECT_BINARY_DIR}/edt.pickle)
set(ZEPHYR_DTS             ${PROJECT_BINARY_DIR}/zephyr.dts)
set(DEVICETREE_GENERATED_H ${BINARY_DIR_INCLUDE_GENERATED}/devicetree_generated.h)
set(DTS_POST_CPP           ${PROJECT_BINARY_DIR}/zephyr.dts.pre)

构建流程:

DTS 输入                          C preprocessor          gen_edt.py              gen_defines.py
  ├─ SoC .dtsi          ──┐                           ┌── 输入:zephyr.dts.pre ── 输出:       ┌── 输入:edt.pickle
  ├─ board .dts           ├── zephyr.dts.pre         │   + bindings dirs          ├─ zephyr.dts   ├─ 输出:devicetree_generated.h
  ├─ shield .overlay      └─                      ──┘                           └─ edt.pickle    └─ 输出:dts_bindings_used.txt
  └─ app overlay

C 代码
  └─ #include <zephyr/devicetree.h>
       └─ #include <zephyr/devicetree_generated.h>

调试时常看的文件:

文件用途
build/zephyr/zephyr.dts合并后的最终 DTS,适合人工阅读
build/zephyr/zephyr.dts.pre预处理后的中间 DTS
build/zephyr/edt.pickleedtlib 的结构化解析结果
build/zephyr/include/generated/zephyr/devicetree_generated.hC 宏生成结果
build/zephyr/dts_bindings_used.txt本次构建使用到的 binding 列表

七、常用 devicetree 宏

7.1 先看懂 DT_N_... 这类名字

你在生成头文件里经常会看到像下面这样的宏名:

DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller
DT_N_INST_0_zephyr_bt_hci_ll_sw_split
DT_N_NODELABEL_uart0

它们并不是手写出来的,而是构建系统根据设备树生成的 C 预处理符号。可以把它们理解为:

  • DT_N_:表示“这是一个 devicetree 节点标识符”;
  • DT_N_S_...:按节点路径生成的节点;
  • DT_N_INST_0_<compat>:某个 compatible 的第 0 个实例;
  • DT_N_NODELABEL_<label>:通过 node label 引用的节点;
  • DT_N_ALIAS_<alias>:通过 /aliases 引用的节点。

例如:

DT_INST(0, zephyr_bt_hci_ll_sw_split)

会被展开为:

DT_N_INST_0_zephyr_bt_hci_ll_sw_split

而这个宏又会在生成头文件中映射到某个实际节点,例如:

#define DT_N_INST_0_zephyr_bt_hci_ll_sw_split \
    DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller

这就是为什么你看见 DT_N_... 这类名字时,先别慌:它们本质上就是“设备树节点的 C 语言名字”。

用途
DT_NODELABEL(label)通过 node label 获取节点 ID
DT_ALIAS(alias)通过 /aliases 获取节点 ID
DT_PATH(...)通过路径获取节点 ID
DT_PROP(node_id, prop)读取属性
DT_PROP_OR(node_id, prop, default)属性不存在时使用默认值
DT_REG_ADDR(node_id) / DT_REG_SIZE(node_id)读取 reg 地址/大小
DT_IRQN(node_id)读取中断号
DT_INST_PROP(inst, prop)读取当前 compatible 实例属性
DT_INST_FOREACH_STATUS_OKAY(fn)遍历所有 okay 实例
DT_NODE_HAS_STATUS(node_id, okay)判断节点状态
DT_NODE_HAS_PROP(node_id, prop)判断属性存在
DT_PHA(node_id, phs, cell)读取 phandle-array 的 cell

推荐尽量使用 *_dt_spec 辅助结构:

struct gpio_dt_spec reset = GPIO_DT_SPEC_GET(node_id, reset_gpios);
struct i2c_dt_spec i2c = I2C_DT_SPEC_GET(node_id);
struct spi_dt_spec spi = SPI_DT_SPEC_GET(node_id, SPI_WORD_SET(8), 0);

这些 helper 会把 bus device、地址/chip select、GPIO flags 等组合好,减少手写错误。


八、设备 PM 资源

当前设备 PM 资源在 include/zephyr/pm/device.h 中定义:

#define PM_DEVICE_DT_DEFINE(node_id, pm_action_cb, ...) \
    Z_PM_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_ID(node_id), pm_action_cb, ...)

#define PM_DEVICE_DT_INST_DEFINE(idx, pm_action_cb, ...) \
    Z_PM_DEVICE_DEFINE(DT_DRV_INST(idx),                 \
                       Z_DEVICE_DT_DEV_ID(DT_DRV_INST(idx)), \
                       pm_action_cb, ...)

#define PM_DEVICE_GET(dev_id) Z_PM_DEVICE_GET(dev_id)

典型组合(PM 资源和 device 分开定义,以下为通用示例,用传感器 temp_* 命名;本文 HCI 实例未启用 PM,故 2.3 节中 pm 参数直接传 NULL):

static int temp_pm_action(const struct device *dev,
                          enum pm_device_action action)
{
    switch (action) {
    case PM_DEVICE_ACTION_RESUME:
        return temp_resume(dev);
    case PM_DEVICE_ACTION_SUSPEND:
        return temp_suspend(dev);
    default:
        return -ENOTSUP;
    }
}

#define TEMP_DEFINE(inst)                                           \
    PM_DEVICE_DT_INST_DEFINE(inst, temp_pm_action);                 \
    DEVICE_DT_INST_DEFINE(inst,                                     \
                          temp_init,                               \
                          PM_DEVICE_DT_INST_GET(inst),             \
                          &temp_data_##inst,                       \
                          &temp_cfg_##inst,                        \
                          POST_KERNEL,                             \
                          CONFIG_SENSOR_INIT_PRIORITY,             \
                          &temp_api);

DEVICE_DT_INST_DEFINE() 创建 device;PM_DEVICE_DT_INST_DEFINE() 创建 PM resources;PM_DEVICE_DT_INST_GET() 把二者连接起来。


九、设备访问 API

推荐方式 DEVICE_DT_GET():编译期解析,O(1):

const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart0));

if (!device_is_ready(uart)) {
    return -ENODEV;
}

device_get_binding() 运行时按名称遍历,适合 shell/调试/兼容旧代码:

const struct device *z_impl_device_get_binding(const char *name)
{
    if ((name == NULL) || (name[0] == '\0')) {
        return NULL;
    }

    STRUCT_SECTION_FOREACH(device, dev) {
        if ((dev->name == name) || (strcmp(name, dev->name) == 0)) {
            return z_impl_device_is_ready(dev) ? dev : NULL;
        }
    }

    return NULL;
}

device_is_ready()

bool z_impl_device_is_ready(const struct device *dev)
{
    if (dev == NULL) {
        return false;
    }

    return dev->state->initialized && (dev->state->init_res == 0U);
}

无论 init 成功失败都会标记 initialized = true,失败时 init_res 保存正的 errno 值。device_is_ready() 检查二者都满足才返回 true。


十、Binding YAML

一个现代 binding 示例(通用示例,用传感器 temp_* 命名;本文 HCI 的 binding 见 2.2 节):

# dts/bindings/sensor/vendor,temp-sensor.yaml
compatible: "vendor,temp-sensor"

description: Vendor temperature sensor

include: [sensor-device.yaml, i2c-device.yaml]

properties:
  reg:
    required: true

  resolution:
    type: int
    default: 12
    enum: [9, 10, 11, 12]
    description: Temperature conversion resolution in bits.

  int-gpios:
    type: phandle-array
    description: Optional data-ready interrupt GPIO.

Binding 的作用:定义属性类型、默认值、合法枚举;指定 bus 通用属性;让 gen_edt.py 可以结构化解析节点并生成更可靠的 C 宏。


十一、调试技巧

# 查看最终 DTS
west build -b <board> <app>
cat build/zephyr/zephyr.dts

# 查看生成宏
grep -R "DT_N_" build/zephyr/include/generated -n | head
# 或直接打开 build/zephyr/include/generated/zephyr/devicetree_generated.h

# 查看使用到的 binding
cat build/zephyr/dts_bindings_used.txt

针对本文实例,还可以直接验证“匹配闭环”:

# 1. chosen 指向的节点
grep "DT_CHOSEN_zephyr_bt_hci" \
    build/zephyr/include/generated/zephyr/devicetree_generated.h

# 2. 驱动实例映射到的节点(应指向同一个 DT_N_S_...)
grep "DT_N_INST_0_zephyr_bt_hci_ll_sw_split" \
    build/zephyr/include/generated/zephyr/devicetree_generated.h

# 3. 该实例的设备对象符号(链接后存在即说明驱动被编译)
grep -r "__device_dts_ord_" build/zephyr/zephyr.map | head

在代码中做条件保护:

#if DT_NODE_HAS_STATUS(SENSOR_NODE, okay)
const struct device *sensor = DEVICE_DT_GET(SENSOR_NODE);
#endif

#if DT_NODE_HAS_PROP(SENSOR_NODE, resolution)
#define SENSOR_RESOLUTION DT_PROP(SENSOR_NODE, resolution)
#else
#define SENSOR_RESOLUTION 12
#endif

十二、常见错误

现象常见原因修复方向
DEVICE_DT_GET() 链接错误节点 okay 但没有驱动实例化 device检查 compatible、驱动 Kconfig、DT_INST_FOREACH_STATUS_OKAY()
device_is_ready() 为 falseinit 失败或依赖设备未 ready看驱动 init 返回值和依赖 bus
DT_PROP() 编译错误属性不存在或 binding 未定义DT_PROP_OR() 或修 binding
I2C_DT_SPEC_INST_GET() 编译失败节点不在 I2C bus 下或缺少 reg检查 DTS 拓扑和 binding include
overlay 不生效overlay 路径/board/shield 选择错误看 CMake 输出中的 DTS files 列表
PM 编译错误直接把 PM 回调传给 device 宏使用 PM_DEVICE_DT_DEFINE() + PM_DEVICE_DT_GET()
bt_enable() 报 HCI 设备未 ready / 找不到 HCIchosen zephyr,bt-hci 未指向 enabled 的 bt_hci_controller 节点检查 DTS chosen、status = "okay"CONFIG_BT_LL_SW_SPLIT
DEVICE_DT_GET()undefined reference to __device_dts_ord_<N>该节点没有驱动调用 DEVICE_DT_INST_DEFINE()确认 DT_DRV_COMPAT 与节点 compatible 一致、驱动被编译

十三、源码阅读路线

  1. cmake/modules/dts.cmake:DTS 构建管线。
  2. scripts/dts/gen_edt.py:生成 zephyr.dtsedt.pickle
  3. scripts/dts/gen_defines.py:生成 devicetree_generated.h
  4. include/zephyr/devicetree.h:devicetree C 宏 API。
  5. include/zephyr/device.h:device 定义、获取、依赖和 init entry 宏。
  6. include/zephyr/init.h:init entry section 规则。
  7. kernel/init.c:启动阶段和 init level 遍历。
  8. kernel/device.c:device init、binding 查找、ready 判断。
  9. include/zephyr/pm/device.h:设备 PM resource 宏和 API。

本文档已按当前 Zephyr 4.4.99 源码更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值