Zephyr上电如何运行到main
背景:
由于工作需要,这几天用到STM32F0XX,客户指定使用zephyr,由于之前未接触过这个系统,故经过多次折腾(主要是github断断续续),终于成功搭建了环境。经过对源码的一翻恶补,对一些关键信息记录如下。
zephyr版本:2.3.0
正题:
1、上电复位入口
vector_table.S
主要完成工作:芯片上电启动,完成向量复位。上电后调用第一个函数z_arm_reset。
.word z_arm_reset
调用z_arm_reset函数。此函数位于reset.S文件中,又命令为__start。主要完成前期初始化工作,然后调用z_arm_prep_c函数。
2、C环境准备
z_arm_prep_c函数是个C语言实现的函数,位于prep_c.c中。
void z_arm_prep_c(void)
{
relocate_vector_table();
#if defined(CONFIG_CPU_HAS_FPU)
z_arm_floating_point_init();
#endif
z_bss_zero();
z_data_copy();
#if defined(CONFIG_ARMV7_R) && defined(CONFIG_INIT_STACKS)
z_arm_init_stacks();
#endif
z_arm_interrupt_init();
z_cstart();
CODE_UNREACHABLE;
}
主要完成 bss 段初始化,数据拷贝,栈初始化及中断栈初始化工作。之后调用 z_cstart()函数。
FUNC_NORETURN void z_cstart(void)
{
/* gcov hook needed to get the coverage report.*/
gcov_static_init();
LOG_CORE_INIT();
/* perform any architecture-specific initialization */
arch_kernel_init();
#if defined(CONFIG_MULTITHREADING)
/* Note: The z_ready_thread() call in prepare_multithreading() requires
* a dummy thread even if CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN=y
*/
struct k_thread dummy_thread;
z_dummy_thread_init(&dummy_thread);
#endif
/* perform basic hardware initialization */
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);
#ifdef CONFIG_STACK_CANARIES
uintptr_t stack_guard;
z_early_boot_rand_get((u8_t *)&stack_guard, sizeof(stack_guard));
__stack_chk_guard = stack_guard;
__stack_chk_guard <<= 8;
#endif /* CONFIG_STACK_CANARIES */
#ifdef CONFIG_MULTITHREADING
prepare_multithreading();
switch_to_main_thread();
#else
bg_thread_main(NULL, NULL, NULL);
/* LCOV_EXCL_START
* We've already dumped coverage data at this point.
*/
irq_lock();
while (true) {
}
/* LCOV_EXCL_STOP */
#endif
/*
* Compiler can't tell that the above routines won't return and issues
* a warning unless we explicitly tell it that control never gets this
* far.
*/
CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}
z_cstart()主要完成 内核初始化工作。
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);
这两个函数完成了低级设备的驱动初始化工作。
zephyr中设备驱动共分四级:
#define _SYS_INIT_LEVEL_PRE_KERNEL_1 0
#define _SYS_INIT_LEVEL_PRE_KERNEL_2 1
#define _SYS_INIT_LEVEL_POST_KERNEL 2
#define _SYS_INIT_LEVEL_APPLICATION 3
设备模型:
struct device {
const char *name;
const void *config_info;
const void *driver_api;
void * const driver_data;
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
int (*device_pm_control)(struct device *device, u32_t command,
void *context, device_pm_cb cb, void *arg);
struct device_pm * const pm;
#endif
};
注意:zephyr 中 大多使用 DEVICE_AND_API_INIT 宏来处理设备驱动。如下示例:
#define STM32_I2C_INIT(name) \
STM32_I2C_IRQ_HANDLER_DECL(name); \
\
static const struct i2c_stm32_config i2c_stm32_cfg_##name = { \
.i2c = (I2C_TypeDef *)DT_REG_ADDR(DT_NODELABEL(name)), \
.pclken = { \
.enr = DT_CLOCKS_CELL(DT_NODELABEL(name), bits), \
.bus = DT_CLOCKS_CELL(DT_NODELABEL(name), bus), \
}, \
STM32_I2C_IRQ_HANDLER_FUNCTION(name) \
.bitrate = DT_PROP(DT_NODELABEL(name), clock_frequency), \
}; \
\
static struct i2c_stm32_data i2c_stm32_dev_data_##name; \
\
DEVICE_AND_API_INIT(i2c_stm32_##name, DT_LABEL(DT_NODELABEL(name)), \
&i2c_stm32_init, &i2c_stm32_dev_data_##name, \
&i2c_stm32_cfg_##name, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&api_funcs); \
\
STM32_I2C_IRQ_HANDLER(name)
/* I2C instances declaration */
#if DT_NODE_HAS_STATUS(DT_NODELABEL(i2c1), okay)
STM32_I2C_INIT(i2c1);
#endif
使用STM32_I2C_INIT(i2c1) 结合 DEVICE_AND_API_INIT 宏,实现I2C1外设的设备驱动关联。
设备驱动启动后,若配置为多线程,则调用
prepare_multithreading();
switch_to_main_thread();
若未配置多线程,则(默认)调用
bg_thread_main(NULL, NULL, NULL);
3、线程准备
static void bg_thread_main(void *unused1, void *unused2, void *unused3)
{
...
z_sys_post_kernel = true;
z_sys_init_run_level(_SYS_INIT_LEVEL_POST_KERNEL);
...
/* Final init level before app starts */
z_sys_init_run_level(_SYS_INIT_LEVEL_APPLICATION);
z_init_static_threads();
#ifdef CONFIG_SMP
z_smp_init();
z_sys_init_run_level(_SYS_INIT_LEVEL_SMP);
#endif
#ifdef CONFIG_BOOT_TIME_MEASUREMENT
z_timestamp_main = k_cycle_get_32();
#endif
extern void main(void);
main();
/* Mark nonessenrial since main() has no more work to do */
z_main_thread.base.user_options &= ~K_ESSENTIAL;
...
} /* LCOV_EXCL_LINE ... because we just dumped final coverage data */
bg_thread_main()函数主要先进行 _SYS_INIT_LEVEL_POST_KERNEL 及 _SYS_INIT_LEVEL_APPLICATION 级别的设备初始化工作。
z_init_static_threads() 对线程进行初始化,之后调用应用程序的入口函数,也就是我们用C编写的 main() 函数。
好了,到此为止,系统已交到我们手上,可以愉快地玩耍了。
本次仅说明了从上电到main的过程,其它关于内核的调试机制,队列的处理等等后续有时间再补充。