Zephyr 设备初始化顺序

在一个主板上会有众多的设备,而这些设备的初始化顺序是有要求的,例如在一个 esp32 上有一个 i2c 设备,在初始化的时候需要先初始化 i2c 设备,然后再初始化其他依赖 i2c 的设备。在 Zephyr 中就通过设备初始化等级和优先级和设备树来控制设备的初始化顺序。

初始化方式 链接到标题

在 Zephyr 中有两种设备初始化方式:

  • 系统初始化:在编译期间就决定好设备的初始化顺序,在系统启动时 Zephyr 安装初始化顺序自动初始化设备。
  • 延迟初始化:在编译期间指定设备需要延迟初始化,用户写代码时根据需要在合适的地方通过调用device_init进行初始化

系统初始化 链接到标题

Zephyr 中定义了 6 个初始化等级

  • EARLY
  • PRE_KERNEL_1
  • PRE_KERNEL_2
  • POST_KERNEL
  • APPLICATION
  • SMP

Zephyr 系统初始化时会按照从上到下的顺序分为不同阶段依次进行初始化。这些初始化等级并不只是为了设备驱动,内核对象初始化,子模块初始化都会根据需要将其初始化安排到不同阶段。通常设备的初始化会使用DEVICE_DEFINE相关宏来指定初始化等级,而内核对象,子模块会使用SYS_INIT相关宏来指定初始化等级。例如

SYS_INIT(nxp_flexram_init, EARLY, 0);
SYS_INIT(init_mbox_module, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
I2C_DEVICE_DT_INST_DEFINE(_num,						\
	    i2c_sbcon_init,						\
	    NULL,							\
	    &i2c_sbcon_dev_data_##_num,					\
	    &i2c_sbcon_dev_cfg_##_num,					\
	    PRE_KERNEL_2, CONFIG_I2C_INIT_PRIORITY, &api);
DEVICE_DEFINE(stm32_ptp_clock_0, PTP_CLOCK_NAME, ptp_stm32_init,
		NULL, &ptp_stm32_0_context, NULL, POST_KERNEL,
		CONFIG_ETH_STM32_HAL_PTP_CLOCK_INIT_PRIO, &api);
SYS_INIT(k_sys_work_q_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
SYS_INIT(lvgl_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
SYS_INIT(arc_shared_intc_update_post_smp, SMP, 0);

需要注意的是从 v4.0.0 开始新增的设备驱动初始化将不再使用EARLY,APPLICATION,SMP初始化等级,已有的也将逐步废弃。

设备驱动初始化等级 链接到标题

在 Zephyr 下总共有了 3 个设备驱动初始化等级:

  • PRE_KERNEL_1
    • 用于无依赖项的设备,例如仅依赖处理器/SOC 中现有硬件的设备。设备在该初始化阶段无法调用内核功能,但中断子系统已就绪,允许配置中断。此级别的初始化函数运行于中断堆栈环境
  • PRE_KERNEL_2
    • 用于依赖于 PRE_KERNEL_1 级别初始化的设备。由于内核尚未初始化完成,备在该初始化阶段也无法调用内核功能(如动态内存分配、任务调度等),因为内核尚未初始化完成。此级别的初始化函数在中断堆栈上执行。
  • POST_KERNEL
    • 用于在配置期间需要内核服务的设备。此级别的初始化函数在内核主任务上下文中运行。

设备的初始化会使用DEVICE_DEFINE相关宏,例如:

#define DEVICE_DEFINE(dev_id, name, init_fn, pm, data, config, level, prio, api) 
#define DEVICE_DT_DEFINE(node_id, init_fn, pm, data, config, level, prio, api, ...) 

参数中的level就是初始化等级,不同的初始化等级之间按照PRE_KERNEL_1 -> PRE_KERNEL_2 -> POST_KERNEL 的顺序进行初始化。

设备初始化优先级 链接到标题

prio就是初始化优先级,同一个初始化等级下可以安排多个设备驱动,这些设备之间通过优先级来决定初始化顺序。prio需要满足下面规则:

  • 取值范围 0 至 99 的整数,数值越小表示初始化越早
  • 必须为十进制整数,禁止使用前导零(如 07)或符号(如+32、-5)
  • 允许通过宏定义符号名称(例如#define MY_INIT_PRIO 32)
  • 禁止使用符号表达式(如 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5)
  • 允许不同设备使用相同优先级

例如,下面两种做法都是允许的

DEVICE_DEFINE(stm32_ptp_clock_0, PTP_CLOCK_NAME, ptp_stm32_init,
		NULL, &ptp_stm32_0_context, NULL, POST_KERNEL,
		CONFIG_ETH_STM32_HAL_PTP_CLOCK_INIT_PRIO, &api);

DEVICE_DEFINE(stm32_ptp_clock_0, PTP_CLOCK_NAME, ptp_stm32_init,
		NULL, &ptp_stm32_0_context, NULL, POST_KERNEL,
		20, &api);

下面写法是错误的:

DEVICE_DEFINE(stm32_ptp_clock_0, PTP_CLOCK_NAME, ptp_stm32_init,
		NULL, &ptp_stm32_0_context, NULL, POST_KERNEL,
		CONFIG_ETH_STM32_HAL_PTP_CLOCK_INIT_PRIO+2, &api);

DEVICE_DEFINE(stm32_ptp_clock_0, PTP_CLOCK_NAME, ptp_stm32_init,
		NULL, &ptp_stm32_0_context, NULL, POST_KERNEL,
		120, &api);

设备排序 链接到标题

如果初始化等级和优先级相同,那么 Zephyr 会按照设备在设备树中的顺序进行初始化。设备实例在构建时会根据设备树中定义的顺序被给定一个序号,在同一个初始化等级下会先按优先级排序,再按序号排序。

例如:

DEVICE_DEFINE(counter0, "counter0", counter_esp32_init, NULL,
		&counter_data_0, &counter_config_0, PRE_KERNEL_1, 10,
		&counter_api);

#define ESP32_COUNTER_INIT(idx)  
    DEVICE_DT_INST_DEFINE(idx, counter_esp32_init, NULL, &counter_data_##idx,                  \
			      &counter_config_##idx, PRE_KERNEL_1, CONFIG_COUNTER_INIT_PRIORITY,   \
			      &counter_api);
#define ESP32_I2C_INIT(idx) \
    I2C_DEVICE_DT_DEFINE(I2C(idx), i2c_esp32_init, NULL, &i2c_esp32_data_##idx,		   \
			     &i2c_esp32_config_##idx, POST_KERNEL, CONFIG_I2C_INIT_PRIORITY,	   \
			     &i2c_esp32_driver_api);

#define SHT3XD_DEFINE(inst)							\							\
	SENSOR_DEVICE_DT_INST_DEFINE(inst, sht3xd_init, NULL,			\
		&sht3xd0_data_##inst, &sht3xd0_cfg_##inst,			\
		POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,			\
		&sht3xd_driver_api);

ESP32_COUNTER_INIT(0)
ESP32_COUNTER_INIT(1)
ESP32_I2C_INIT(0)
SHT3XD_DEFINE(0)

在 Kconfig 中优先级的默认值为

  • CONFIG_COUNTER_INIT_PRIORITY=60
  • CONFIG_I2C_INIT_PRIORITY=50
  • CONFIG_SENSOR_INIT_PRIORITY=90

从设备树中可以看到 counter0 是在 counter1 之前定义的,所以 i2c0 的序号会小一些,所以 i2c0 会先初始化。这个最终的序号可以在 devicetree_generator.h 中找到

 /*
 *   37  /soc/counter@6001f000
 *   38  /soc/counter@60020000
 *   62  /soc/i2c@60013000
 *   63  /soc/i2c@60013000/sht3xd@44
 */

可以得到:

设备名称 初始化等级 优先级 序号
counter0 PRE_KERNEL_1 60 37
counter1 PRE_KERNEL_1 60 38
i2c0 POST_KERNEL 50 62
sht3xd POST_KERNEL 90 63

counter0 和 counter1 初始化等级和优先级一样,按照序号排序。i2c0 和 sht3xd0 初始化等级一样优先级不同按照优先级排序

那么初始化的顺序就是:counter0->counter1->i2c0->sht3xd0

延迟初始化 链接到标题

设备的初始化也可以推迟到以后的时间。在这种情况下,Zephyr 不会在系统启动时自动初始化设备。方法时在要推迟设备驱动程序初始化的设备树节点中添加属性 zephyr,deferred-init , 在由应用程序调用 device_init 来完成初始化,例如让 sht3xd 延迟初始化:

&i2c0 {
	status = "okay";
	clock-frequency = <I2C_BITRATE_STANDARD>;
	pinctrl-0 = <&i2c0_default>;
	pinctrl-names = "default";

	sht3xd:sht3xd@44 {
		compatible = "sensirion,sht3xd";
		reg = <0x44>;
		label = "SHT3XD";
		status = "okay";
		zephyr,deferred-init;
	};
};

应用程序中需要的时机进行调用

const struct device *dev;
dev = DEVICE_DT_GET(DT_NODELABEL(adc0));
device_init(dev);

设备间依赖 链接到标题

设备间依赖分为硬件依赖和功能依赖两种。

硬件依赖 链接到标题

所谓硬件依赖就是设备 A 需要设备 B 才能工作,这种物理上的强依赖,没有 A 设备 B 就无法工作,例如 sht3xd 是挂在 i2c0 上的,所以 sht3xd0 需要 i2c0 才能工作。硬件依赖在设备树中可以完整的体现,例如前文的 std3xd 就挂在 i2c0 下。而下面示例的 uart0 依赖 rtc

uart0: uart@60000000 {
    compatible = "espressif,esp32-uart";
    reg = < 0x60000000 0x400 >;
    status = "okay";
    interrupts = < 0x15 0x3 0x0 >;
    interrupt-parent = < &intc >;
    clocks = < &rtc 0x1 >;
    current-speed = < 0x1c200 >;
    pinctrl-0 = < &uart0_default >;
    pinctrl-names = "default";
};

功能依赖 链接到标题

功能依赖就是设备 A 需要设备 B 提供的功能,这种依赖是设备驱动实现者建立的,例如 i2c 驱动里面要打印一些信息方便调试,那么就需要 uart 先初始化 i2c 中的打印信息才能正常。这种依赖是一种弱依赖,没有 A 设备 B 也可以工作。功能依赖一般是由初始化等级和优先级来控制的。

依赖关系的检查 链接到标题

硬件依赖检查,硬件依赖是强制性依赖,Zephyr 在构建的时候会进行检查,如果不满足硬件依赖的关系构建过程就会报错,例如:i2c0 和 sht3xd 的初始化等级都在 POST_KERNEL,而优先级上 i2c0 高于 sht3x,如果我将 sht3xd 的优先级CONFIG_SENSOR_INIT_PRIORITY改为 20,构建时会报错:

ERROR: Device initialization priority validation failed, the sequence of initialization calls does not match the devicetree dependencies.
ERROR: /soc/i2c@60013000/sht3xd@44 <sht3xd_init> is initialized before its dependency /soc/i2c@60013000 <i2c_esp32_init> (POST_KERNEL+1 < POST_KERNEL+7)

或将 sht3xd 的初始化等级改为 PRE_KERNEL_1,那么构建过程也会报错。

ERROR: Device initialization priority validation failed, the sequence of initialization calls does not match the devicetree dependencies.
ERROR: /soc/i2c@60013000/sht3xd@44 <sht3xd_init> is initialized before its dependency /soc/i2c@60013000 <i2c_esp32_init> (PRE_KERNEL_1+10 < POST_KERNEL+6)

功能依赖检查并不是强制的,如果你发现一些设备驱动中调用其它设备的功能未工作,可以构建完成后可以通过west build -t initlevels获取初始化的等级顺序,人工进行检查,下面是 esp32c3 的一个输出结果

EARLY
PRE_KERNEL_1
  __init_esp32c3_zgp_init: esp32c3_zgp_init(NULL)
  __init_sys_clock_driver_init: sys_clock_driver_init(NULL)
  __init___device_dts_ord_34: clock_control_esp32_init(__device_dts_ord_34)
  __init_statics_init_pre: statics_init(NULL)
  __init_init_mem_slab_obj_core_list: init_mem_slab_obj_core_list(NULL)
  __init___device_dts_ord_6: gpio_esp32_init(__device_dts_ord_6)
  __init___device_dts_ord_47: uart_esp32_init(__device_dts_ord_47)
  __init___device_dts_ord_48: uart_esp32_init(__device_dts_ord_48)
  __init___device_dts_ord_49: serial_esp32_usb_init(__device_dts_ord_49)
  __init_uart_console_init: uart_console_init(NULL)
PRE_KERNEL_2
  __init_esp_heap_runtime_init: esp_heap_runtime_init(NULL)
POST_KERNEL
  __init_enable_logger: enable_logger(NULL)
  __init_malloc_prepare: malloc_prepare(NULL)
  __init_k_sys_work_q_init: k_sys_work_q_init(NULL)
  __init___device_dts_ord_17: flash_esp32_init(__device_dts_ord_17)
  __init___device_dts_ord_53: adc_esp32_init(__device_dts_ord_53)
  __init___device_dts_ord_55: adc_esp32_init(__device_dts_ord_55)
  __init___device_dts_ord_62: i2c_esp32_init(__device_dts_ord_62)
  __init___device_dts_ord_7: gpio_keys_init(__device_dts_ord_7)
  __init___device_dts_ord_8: longpress_init(__device_dts_ord_8)
  __init___device_dts_ord_36: esp32_temp_init(__device_dts_ord_36)
  __init___device_dts_ord_63: sht3xd_init(__device_dts_ord_63)
  __init_enable_shell_uart: enable_shell_uart(NULL)
  __init_littlefs_init: littlefs_init(NULL)
APPLICATION
  __init__zbus_init: _zbus_init(NULL)
SMP

需要注意的是,延迟初始化的设备将不加入检查。例如 i2c0 如何被标记为延迟初始化,构建的时就不会对 i2c0 和 sht3xd 的依赖关系进行检查来。

除了构建时检查外,在 Zephyr 运行时也可以通过 device 的 shell 命令列出当前那些设备已经工作和其依赖的设备,要做到这一点需要开启如下配置项:

CONFIG_DEVICE_DEPS=y
CONFIG_DEVICE_SHELL=y
CONFIG_SHELL=y

Zephyr 运行过程中可以通过 shell 命令执行device list看到设备的状态和依赖

uart:~$ device list 
devices:
- rtc@60008000 (READY)
- gpio@60004000 (READY)
- uart@60043000 (READY)
  requires: rtc@60008000
- uart@60010000 (READY)
  requires: rtc@60008000
- uart@60000000 (READY)
  requires: rtc@60008000
- adc@60040004 (READY)
- adc@60040000 (READY)
- flash-controller@60002000 (READY)
- i2c@60013000 (READY)
  requires: rtc@60008000
- longpress (READY)
  requires: buttons
- buttons (READY)
  requires: gpio@60004000
- coretemp@60040058 (READY)
- SHT3XD (DISABLED)
  requires: i2c@60013000

参考 链接到标题

https://docs.zephyrproject.org/4.0.0/kernel/drivers/index.html