On a motherboard, there are numerous devices, and the order of their initialization is critical. For example, on an ESP32, an I2C device must be initialized before other devices that depend on it. In Zephyr, the initialization order of devices is controlled through device initialization levels, priorities, and the device tree.
Initialization Methods
There are two methods for device initialization in Zephyr:
- System Initialization: The initialization order of devices is determined at compile time, and during system startup, Zephyr automatically initializes devices in the specified order.
- Deferred Initialization: Devices that require deferred initialization are specified at compile time, and users can call
<span>device_init</span>
at appropriate places in their code to perform the initialization as needed.
System Initialization
Zephyr defines six initialization levels:
- EARLY
- PRE_KERNEL_1
- PRE_KERNEL_2
- POST_KERNEL
- APPLICATION
- SMP
During system initialization, Zephyr initializes devices in different stages from top to bottom. These initialization levels are not only for device drivers; kernel object initialization and submodule initialization are also arranged according to their needs. Typically, device initialization uses <span>DEVICE_DEFINE</span>
macros to specify the initialization level, while kernel objects and submodules use <span>SYS_INIT</span>
macros for the same purpose. For example:
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);
It is important to note that starting from version 4.0.0, the initialization of new device drivers will no longer use the <span>EARLY</span>
, <span>APPLICATION</span>
, and <span>SMP</span>
initialization levels, and existing ones will gradually be deprecated.
Device Driver Initialization Levels
There are three device driver initialization levels in Zephyr:
- PRE_KERNEL_1
- Used for devices without dependencies, such as those that only rely on existing hardware in the processor/SOC. Devices at this initialization stage cannot call kernel functions, but the interrupt subsystem is ready, allowing for interrupt configuration. Initialization functions at this level run in the interrupt stack environment.
- PRE_KERNEL_2
- Used for devices that depend on PRE_KERNEL_1 level initialization. Since the kernel has not yet completed initialization, functions at this level cannot call kernel features (such as dynamic memory allocation, task scheduling, etc.). Initialization functions at this level also execute on the interrupt stack.
- POST_KERNEL
- Used for devices that require kernel services during configuration. Initialization functions at this level run in the context of the kernel’s main task.
Device initialization uses <span>DEVICE_DEFINE</span>
macros, for example:
#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, ...)
The <span>level</span>
parameter specifies the initialization level, and devices are initialized in the order of <span>PRE_KERNEL_1</span>
-> <span>PRE_KERNEL_2</span>
-> <span>POST_KERNEL</span>
.
Device Initialization Priority
The <span>prio</span>
parameter specifies the initialization priority. Multiple device drivers can be arranged under the same initialization level, and the order of initialization is determined by their priority. The <span>prio</span>
must satisfy the following rules:
- Integer values ranging from 0 to 99; smaller values indicate earlier initialization.
- Must be a decimal integer; leading zeros (e.g., 07) or signs (e.g., +32, -5) are not allowed.
- Macro-defined symbolic names are allowed (e.g., #define MY_INIT_PRIO 32).
- Symbolic expressions (e.g., CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5) are not allowed.
- Different devices can use the same priority.
For example, both of the following approaches are allowed:
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);
The following approach is incorrect:
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);
Device Ordering
If the initialization level and priority are the same, Zephyr initializes devices in the order they appear in the device tree. Device instances are assigned a number based on the order defined in the device tree during the build process. Within the same initialization level, devices are first sorted by priority and then by their assigned number.
For example:
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)
In Kconfig, the default values for priorities are:
<span>CONFIG_COUNTER_INIT_PRIORITY=60</span>
<span>CONFIG_I2C_INIT_PRIORITY=50</span>
<span>CONFIG_SENSOR_INIT_PRIORITY=90</span>
From the device tree, it can be seen that counter0 is defined before counter1, so i2c0 will have a smaller number and will be initialized first. The final assigned number can be found in <span>devicetree_generator.h</span>
:
/*
* 37 /soc/counter@6001f000
* 38 /soc/counter@60020000
* 62 /soc/i2c@60013000
* 63 /soc/i2c@60013000/sht3xd@44
*/
Thus, we can conclude:
Device Name | Initialization Level | Priority | Number |
---|---|---|---|
counter0 | PRE_KERNEL_1 | 60 | 37 |
counter1 | PRE_KERNEL_1 | 60 | 38 |
i2c0 | POST_KERNEL | 50 | 62 |
sht3xd | POST_KERNEL | 90 | 63 |
Since counter0 and counter1 have the same initialization level and priority, they are sorted by their assigned number. i2c0 and sht3xd0 have the same initialization level but different priorities, so they are sorted by priority.
The final initialization order is: counter0->counter1->i2c0->sht3xd0.
Deferred Initialization
Device initialization can also be postponed to a later time. In this case, Zephyr will not automatically initialize the device at system startup. The method is to add the property <span>zephyr,deferred-init</span>
to the device tree node of the device that needs deferred initialization, and the initialization is completed by calling <span>device_init</span>
from the application, for example, to delay the initialization of 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;
};
};
The application needs to call it at the appropriate time:
const struct device *dev;
dev = DEVICE_DT_GET(DT_NODELABEL(adc0));
device_init(dev);
Device Dependencies
Device dependencies can be classified into hardware dependencies and functional dependencies.
Hardware Dependencies
Hardware dependency means that device A requires device B to function. This is a strong physical dependency; without device A, device B cannot operate. For example, the sht3xd is connected to i2c0, so sht3xd0 requires i2c0 to function. Hardware dependencies can be fully reflected in the device tree, as shown earlier where std3xd is connected to i2c0. The following example shows uart0 depending on 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";
};
Functional Dependencies
Functional dependency means that device A requires functionality provided by device B. This type of dependency is established by the device driver implementer. For example, if the i2c driver needs to print some information for debugging, it requires uart to be initialized first to print the information correctly. This is a weak dependency; device A can still function without device B. Functional dependencies are generally controlled by initialization levels and priorities.
Dependency Checks
Hardware dependency checks are mandatory. Zephyr performs checks during the build process, and if hardware dependencies are not satisfied, the build process will report an error. For example, if both i2c0 and sht3xd have their initialization levels set to POST_KERNEL, and the priority of i2c0 is higher than that of sht3xd, changing the priority of sht3xd to <span>CONFIG_SENSOR_INIT_PRIORITY</span>
to 20 will result in a build error:
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)
Alternatively, changing the initialization level of sht3xd to PRE_KERNEL_1 will also result in a build error.
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)
Functional dependency checks are not mandatory. If you find that some device drivers are not functioning correctly due to calls to other devices’ functionalities, you can complete the build and use <span>west build -t initlevels</span>
to obtain the order of initialization levels for manual checking. Below is an output result for 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
It is important to note that deferred initialized devices will not be included in the checks. For example, if i2c0 is marked for deferred initialization, the build process will not check the dependency relationship between i2c0 and sht3xd.
In addition to build-time checks, Zephyr can also list the current status of devices and their dependencies at runtime using shell commands. To do this, the following configuration options need to be enabled:
CONFIG_DEVICE_DEPS=y
CONFIG_DEVICE_SHELL=y
CONFIG_SHELL=y
During runtime, you can execute the shell command <span>device list</span>
to see the status and dependencies of devices:
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
References
https://docs.zephyrproject.org/4.0.0/kernel/drivers/index.html
This article is reprinted from HalfCoder. Click here to read the original article for more information.
KubeCon + CloudNativeCon end-user ticket program
Sponsor KubeCon + CloudNativeCon
2024 China KubeCon + CloudNativeCon + Open Source Summit + AI_dev exciting content
Contact Linux Foundation APAC
The Linux Foundation is a non-profit organization and an important part of the technology ecosystem.
The Linux Foundation supports the creation of a sustainable open-source ecosystem by providing financial and intellectual resources, infrastructure, services, events, and training. Through collaborative efforts, the Linux Foundation and its projects have formed extraordinarily successful investments in the creation of shared technologies. Please follow LFAPAC (Linux Foundation APAC) on WeChat.