Zephyr 在 esp32c3 上的启动流程

回顾 链接到标题

Zephyr 下 esp32c3 构建引导之 ESP BootloaderZephyr 下 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_ENTRYzephyr/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的共用代码。