Zephyr 在 esp32c3 上的启动流程
回顾 链接到标题
在 Zephyr 下 esp32c3 构建引导之 ESP Bootloader和 Zephyr 下 esp32c3 构建引导之 MCUBoot两篇文章中我们详细说明了 Zephyr 的启动过程,启动过程可以简要为下面两种:
- 无 mcuboot: ROM-> ESP Bootloader-> Zephyr.bin(App)
- 有 mcuboot: ROM-> MCUboot -> Zephyr.bin(App)
本文则继续 Zephyr.bin 如何从启动一路执行到应用的
main()
。
在前文中说明过 ESP Bootloader 会从 Zephyr.bin 中的 header 中拿到 Entry Point 地址,并跳转执行,完成引导进入 Zephyr 执行。而 MCUBoot 会从 MetaData 中的。entry_addr 段中拿到 Entry Point,并跳转执行。
Entry Point 链接到标题
ESP Bootloader 和 MCUBoot 引导的 Zephyr 其 Entry Point 存储方式不太一样,但最后都是保存 __start()
的地址,也就是说两种引导方式最后都是跳转到 Zephyrd 的 __start()
运行,因此 esp32c3 在 Zephyr 下是从 __start()
开始执行的。
ESP Bootloader 访问的 Entry Point 链接到标题
ESP Bootloader 引导 Zephyr.bin 是直接解析 zephyr.elf 将其 Entry Point 作为跳转地址保存在 Zerphyr.bin 的 header 中,该入口地址由 zephyr/soc/riscv/esp32c3/default.ld
指定:
/* Default entry point: */
ENTRY(CONFIG_KERNEL_ENTRY)
对于 esp32c3,CONFIG_KERNEL_ENTRY
在 zephyr/Kconfig.zephyr
中被默认为 __start
config KERNEL_ENTRY
string "Kernel entry symbol"
default "__start"
help
Code entry symbol, to be set at linking phase.
MCUBoot 访问的 Entry Point 链接到标题
MCUBoot 会从 MetaData 中的。entry_addr 段中拿到 Entry Point, metadata 定义在 zephyr/soc/riscv/esp32c3/default.ld
.metadata :
{
/* Magic byte for load header */
LONG(0xace637d3)
/* Application entry point address */
KEEP(*(.entry_addr))
/* IRAM metadata:
- - Destination address(VMA)for IRAM region
- - Flash offset(LMA)for start of IRAM region
- - Size of IRAM region
*/
LONG(ADDR(.iram0.text))
LONG(LOADADDR(.iram0.text))
LONG(SIZEOF(.iram0.text))
/* DRAM metadata:
- - Destination address(VMA)for DRAM region
- - Flash offset(LMA)for start of DRAM region
- - Size of DRAM region
*/
LONG(ADDR(.dram0.data))
LONG(LOADADDR(.dram0.data))
LONG(LOADADDR(.dummy.dram.data)+ SIZEOF(.dummy.dram.data)- LOADADDR(.dram0.data))
}> metadata
入口地址被放到 KEEP(*(.entry_addr))
位置。在 zephyr/soc/riscv/espressif_esp32/esp32c3/loader.c
中将 __start
的地址放到 section(".entry_addr")
# define HDR_ATTR __attribute__ (( section(".entry_addr")))__attribute__ (( used))
static HDR_ATTR void(*_entry_point)(void)= &__start;
启动流程 链接到标题
Zephyr 的开始执行位置 链接到标题
从前面的分析我们已经知道了 esp32c3 下无论是 ESP Bootloader 还是 MCUBoot 引导的 Zephyr 跳到 Zephyr 最开始执行的都是 __start()
,该函数定义在 zephyr/soc/riscv/espressif_esp32/esp32c3/loader.c
中:
void __start(void)
{
# ifdef CONFIG_BOOTLOADER_MCUBOOT
//ESP Bootloader 会为 esp32c3 进行 rom mmu 的初始化,而 MCUBoot 不会,因此由 MCUBoot 引导的需要在 Zephyr 通过 map_rom_segments 做
int err = map_rom_segments();
if(err != 0){
ets_printf("Failed to setup XIP, aborting\n");
abort();
}
# endif
//esp 平台初始化
__esp_platform_start();
}
esp 平台初始化 链接到标题
zephyr/soc/riscv/espressif_esp32/esp32c3/soc.c
中 __esp_platform_start
完成 esp 平台的初始化,之后后通过 z_cstart()
进入 Zephyr 正式执行。
void __attribute__ (( section(".iram1")))__esp_platform_start(void)
{
# ifdef CONFIG_RISCV_GP
//配置 gp 寄存器,启动时要做的第一件事,链接器可以放宽其他代码段来访问与 __global_pointer$ 相关的内容
__asm__ __volatile__(".option push\n"
".option norelax\n"
"la gp, __global_pointer$\n"
".option pop");
# endif /* CONFIG_RISCV_GP */
//加载中断向量表
__asm__ __volatile__("la t0, _esp32c3_vector_table\n"
"csrw mtvec, t0\n");
//初始化清零 bss 段
z_bss_zero();
//禁用普通中断
csr_read_clear(mstatus, MSTATUS_MIE);
//复位初始化原因
esp_reset_reason_init();
# ifdef CONFIG_MCUBOOT
//Zephyr 被编译为 MCUBoot 时执行 esp-idf 提供的 bootloader
bootloader_init();
# else
//ESP Bootloader 引导加载程序用 RTC WDT 检查应用程序中与启动顺序相关的问题。 启动 Zephyr 环境时需要禁用它。
wdt_hal_context_t rtc_wdt_ctx = {.inst = WDT_RWDT, .rwdt_dev = &RTCCNTL};
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
wdt_hal_disable(&rtc_wdt_ctx);
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
/* Configure the Cache MMU size for instruction and rodata in flash. */
// Flash 中的指令和 rodata 配置 Cache MMU
extern uint32_t esp_rom_cache_set_idrom_mmu_size(uint32_t irom_size,
uint32_t drom_size);
extern int _rodata_reserved_start;
uint32_t rodata_reserved_start_align =
(uint32_t)&_rodata_reserved_start & ~(MMU_PAGE_SIZE - 1);
uint32_t cache_mmu_irom_size =
(( rodata_reserved_start_align - SOC_DROM_LOW)/ MMU_PAGE_SIZE)*
sizeof(uint32_t);
esp_rom_cache_set_idrom_mmu_size(cache_mmu_irom_size,
CACHE_DROM_MMU_MAX_END - cache_mmu_irom_size);
// 使能 wifi phy 子系统 clock
REG_CLR_BIT(SYSTEM_WIFI_CLK_EN_REG, SYSTEM_WIFI_CLK_SDIOSLAVE_EN);
SET_PERI_REG_MASK(SYSTEM_WIFI_CLK_EN_REG, SYSTEM_WIFI_CLK_EN);
//配置 CPU 时钟、RTC 慢时钟和快时钟,并执行 RTC 慢时钟校准。
esp_clk_init();
esp_timer_early_init();
# endif /* CONFIG_MCUBOOT */
//初始化 esp32c3 中断控制器
esp_intr_initialize();
//启动 Zerphyr
z_cstart();
CODE_UNREACHABLE;
}
启动 Zephyr 链接到标题
通过 z_cstart
完成基本的初始化,请进行多线程环境初始化然后切换到主现场进行初始化,下面代码移除一些启动过程的非必须代码再进行说明
__boot_func
FUNC_NO_STACK_PROTECTOR
FUNC_NORETURN void z_cstart(void)
{
// 执行 Zephyr 系统早期初始化函数(EARLY 等级设备或者系统初始化)
z_sys_init_run_level(INIT_LEVEL_EARLY);
// 架构相关的内核初始化
arch_kernel_init();
//LOG 系统核心初始化
LOG_CORE_INIT();
# if defined(CONFIG_MULTITHREADING)
//建立 dummy thread,为切换到主线程做准备
struct k_thread dummy_thread;
z_dummy_thread_init(&dummy_thread);
# endif
// 初始化 device 统计模块
z_device_state_init();
//对 kernel 启动前需要初始化的硬件进行初始化(PRE_KERNEL_1 和 PRE_KERNEL_2 等级的设备或者系统初始化)
z_sys_init_run_level(INIT_LEVEL_PRE_KERNEL_1);
z_sys_init_run_level(INIT_LEVEL_PRE_KERNEL_2);
//准备多线程环境,并切换到主线程执行
switch_to_main_thread(prepare_multithreading());
CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}
准备多线程环境 链接到标题
prepare_multithreading
完成调度器的初始化,并创建主线程 bg_thread_main
__boot_func
static char *prepare_multithreading(void)
{
char *stack_ptr;
//初始化调度器
z_sched_init();
//创建主线程,并标记为 ready
stack_ptr = z_setup_new_thread(&z_main_thread, z_main_stack,
CONFIG_MAIN_STACK_SIZE, bg_thread_main,
NULL, NULL, NULL,
CONFIG_MAIN_THREAD_PRIORITY,
K_ESSENTIAL, "main");
z_mark_thread_as_started(&z_main_thread);
z_ready_thread(&z_main_thread);
//对 CPU0 进行初始化,指定 idle 线程和 irq 堆栈
z_init_cpu(0);
return stack_ptr;
}
切换到主线程执行 链接到标题
switch_to_main_thread
通过呼叫 z_swap_unlocked
切换到主线程 bg_thread_main
执行, 主线程在完成 POST_KERNEL 和 APPLICATION 等级的设备/系统初始化,将静态线程全部初始化,最后再执行应用程序的 main.
__boot_func
static void bg_thread_main(void *unused1, void *unused2, void *unused3)
{
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
ARG_UNUSED(unused3);
//进行 kernel 启动后 POST_KERNEL 等级的设备和系统初始化
z_sys_post_kernel = true;
z_sys_init_run_level(INIT_LEVEL_POST_KERNEL);
//显示 boot banner
boot_banner();
//CPP 环境静态初始化
# if defined(CONFIG_CPP)
void z_cpp_init_static(void);
z_cpp_init_static();
# endif
////进行 kernel 启动后 APPLICATION 等级的设备和系统初始化
z_sys_init_run_level(INIT_LEVEL_APPLICATION);
//初始化静态线程,由 K_THREAD_DEFINE 创建的静态线程
z_init_static_threads();
extern int main(void);
//跳到应用程序的 main 执行
(void)main();
//主线程退出,标记为非 K_ESSENTIAL
z_main_thread.base.user_options &= ~K_ESSENTIAL;
}
小节 链接到标题
从上面可以看到从 Zephyr 被引导后,执行序列为
__start()
__esp_platform_start()
z_cstart()
prepare_multithreading()
切换到主线程
bg_thread_main()
main()
在 z_cstart()之前都是 esp32c3 特定初始化代码。从z_cstart开始就是Zephyr的共用代码。