浅析Zephyr在ESP32上的启动流程

描述

  理解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

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分