理解Zephyr在一款soc上的啟動流程,有利于分析和調試開機過程卡死,驅動異常等的問題。因此在上手一款新的soc時掌握Zephyr在其上面的啟動流程非常必要。本文對Zephyr在ESP32上的啟動流程進行分析,說明ESP32從上電開始如何執行到Zephyr應用的main函數。
Zephyr支持兩種ESP32引導方式:
配置CONFIG_BOOTLOADER_ESP_IDF=n:ROM Boot -》 Zephyr
配置CONFIG_BOOTLOADER_ESP_IDF=y. ROM Boot -》 ESP32 Bootloader -》 Zephyr
本文只分析CONFIG_BOOTLOADER_ESP_IDF=y的流程,在該流程理解另一種也類似,在CONFIG_BOOTLOADER_ESP_IDF=n的情況下相當于是在ESP32的bootloader處放了一個zephyr應用。
ESP32下Zephyr是被當作ESP32的APP被引導,因此有必要簡單了解ESP32的啟動流程
ESP32啟動階段
ESP32是雙核CPU,其中cpu0叫做PRO CPU, cpu1叫做APP CPU,啟動流程如下:
SOC上電, PRO CPU開始運行,跳到ROM 0x40000400 處復位向量代碼處執行
在PRO CPU上運行ROM上一級引導代碼從Flash的0x1000讀出二級引導程序加載到內部IRAM
跳轉到內部IRAM上二級引導程序執行
二級引導程序從 Flash 的 0x8000 偏移地址處讀取分區表, 從分區表中讀到APP的信息
二級引導程序將Zephyr數據和代碼段復制到DRAM和IRAM。對于Zephyr內一些加載地址位于DROM和IROM區域的段,通過配置 Flash MMU 為其提供正確的映射。
二級引導程序會從Zephyr二進制鏡像文件的頭部尋找的入口地址,然后跳轉到該地址處運行。
以上流程中1~3是已經被固化到ESP32的ROM中無法修改,4~6是由modules/hal/espressif/components/bootloader完成,可以做定制修改,但一般不修改。以上1~6都是在PRO CPU中執行。
Zephyr的入口地址就是函數__start,第六步后就會跳轉到__start中執行
Zephyr階段
Zephyr階段運行到main主要步驟:__start-》z_cstart-》bg_thread_main-》main
__start
文件位置zephyr/soc/xtensa/esp32/soc.c, 主要完成下面內容:
搬移中斷向量表
初始化bss段
關閉中斷
確保APP CPU沒有運行(將在后面SMP初始化階段打開)
代碼摘要如下
void __attribute__((section(“.iram1”))) \_\_start(void)
{
//搬移中斷向量表
__asm__ __volatile__ (
“wsr %0, vecbase”
:
: “r”(&_init_start));
//BSS段初始化
(void)memset(&_bss_start, 0,
(&_bss_end - &_bss_start) * sizeof(_bss_start));
__asm__ __volatile__ (
“”
:
: “g”(&_bss_start)
: “memory”);
//關閉中斷
__asm__ __volatile__ (
“wsr %0, PS”
:
: “r”(PS_INTLEVEL(XCHAL_EXCM_LEVEL) | PS_UM | PS_WOE));
//關閉APP CPU
*app_cpu_config_reg &= ~DPORT_APPCPU_CLKGATE_EN;
//初始化cpu指針
__asm__ volatile(“wsr.MISC0 %0; rsync” : : “r”(&_kernel.cpus[0]));
//開始zephyr初始化
z_cstart();
CODE_UNREACHABLE;
}
是否發現跳到zephyr的__start是一個C函數,但之前Zephyr并沒有做C堆棧(SP指針)初始化?這是因為在ESP32的bootloader階段已經做了,Zephyr無需再做。
z_cstart
主要完成kernel初始化,PRE_KERNEL_1和PRE_KERNEL_2級別的驅動初始化,然后啟動main thread:bg_thread_main,剩下的其它初始化和應用程序的main都在bg_thread_main中。
代碼摘要如下
__boot_func
FUNC_NORETURN void z_cstart(void)
{
//架構相關的內核初始化
arch_kernel_init();
// static devices初始化
z_device_state_init();
//初始化PRE_KERNEL_1和PRE_KERNEL_2驅動,大多都是硬件相關
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);
//創建并切換到main thread運行
switch_to_main_thread(prepare_multithreading());
CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}
__boot_func
static char *prepare_multithreading(void)
{
char *stack_ptr;
//初始化OS調度器
z_sched_init();
//創建main thread
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”);
//將main thread加入到就緒態
z_mark_thread_as_started(&z_main_thread);
z_ready_thread(&z_main_thread);
//為每顆CPU 創建idle thread
for (int i = 0; i 《 CONFIG_MP_NUM_CPUS; i++) {
init_idle_thread(i);
_kernel.cpus[i].idle_thread = &z_idle_threads[i];
_kernel.cpus[i].id = i;
_kernel.cpus[i].irq_stack =
(Z_KERNEL_STACK_BUFFER(z_interrupt_stacks[i]) +
K_KERNEL_STACK_SIZEOF(z_interrupt_stacks[i]));
}
initialize_timeouts();
return stack_ptr;
}
main thread被加入到就緒態,因此下一次調度時bg_thread_main就會被執行
bg_thread_main
在bg_thread_main中完成剩余的驅動初始化,并且啟動esp32的第二顆CPU: APP CPU, 然后運行到應用的main函數。
代碼摘要如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
__boot_func
static void bg_thread_main(void *unused1, void *unused2, void *unused3)
{
z_sys_post_kernel = true;
//初始化POST_KERNEL級別驅動
z_sys_init_run_level(_SYS_INIT_LEVEL_POST_KERNEL);
boot_banner();
//初始化APPLICATION級別驅動
z_sys_init_run_level(_SYS_INIT_LEVEL_APPLICATION);
//初始化靜態聲明的thread
z_init_static_threads();
#ifdef CONFIG_SMP
//初始化SMP, 到這里才會啟動ESP32的另一顆CPU
z_smp_init();
//初始SMP級別的驅動,例如跨CPU通信的mailbox, ipm驅動
z_sys_init_run_level(_SYS_INIT_LEVEL_SMP);
#endif
extern void main(void);
//執行應用程序的main
main();
/* Mark nonessenrial since main() has no more work to do */
z_main_thread.base.user_options &= ~K_ESSENTIAL;
}
關于main
這里調用的main函數,是在Zephyr應用程序中實現,最后通過鏈接器鏈接在一起。Zephyr應用程序的main是在main thread中執行,由于main thread的默認優先級比較高0, 因此要注意不要在main中去做while(1),避免導致其它搶占式線程拿不到CPU。
關于SMP
從前面的分析可以看到z_smp_init前,Zephyr上包括main thread的所有代碼都是在PRO CPU上執行,在z_smp_init后Zephyr的代碼才有機會運行到APP CPU上, SMP是一個很大的議題,不是在本文分析范圍內。這里簡單列出esp32 SMP初始化的主要流程供參考:
z_smp_init(smp.c)-》arch_start_cpu(esp32-mp.c)-》appcpu_start-》esp32_rom_ets_set_appcpu_boot_addr-》appcpu_entry1-》z_appcpu_stack_switch-》appcpu_entry2-》smp_init_top(smp.c)
參考
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/startup.html
編輯:jq
-
cpu
+關注
關注
68文章
10902瀏覽量
213001 -
soc
+關注
關注
38文章
4204瀏覽量
219090 -
SMP
+關注
關注
0文章
76瀏覽量
19746 -
ESP32
+關注
關注
18文章
978瀏覽量
17525
原文標題:Zephyr ESP32啟動流程
文章出處:【微信號:ZephyrProject,微信公眾號:ZephyrProject】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論