bootloader(lk->kernel)

Pre-loader 运行在ISRAM,待完成 DRAM 的初始化后,再将lk载入DRAM中,最后通过特殊sys call手段实现跳转到lk的执行入口,正式进入lk初始化阶段.

一、lk执行入口:

位于.text.boot 这个section(段),具体定义位置为:

  1. ./lk/arch/arm/system-onesegment.ld:10: .text.boot : { *(.text.boot) }

  2. ./lk/arch/arm/system-twosegment.ld:10: .text.boot : { *(.text.boot) }

该段的代码执行入口是crt0.s文件,位置为:

vendor/mediatek/proprietary/bootable/bootloader/lk/arch/arm/crt0.s (mt6765)

./lk/arch/arm/crt0.S

crt0.S 中会经过一系列的初始化准备操作,最终跳转到C代码入口kmain函数开始执行,这个是 我们需要重点分析关注的,kmain的位置:

./lk/kernel/main.c

From Lk to Kernel 总时序图

 

二、源码分析:

 
  1. 1、crt0.S

  2.  
  3. .section ".text.boot"

  4.  
  5. ...

  6.  
  7. .Lstack_setup:

  8. /* ==set up the stack for irq, fi==q, abort, undefined, system/user, and lastly supervisor mode */

  9. mrs r0, cpsr

  10. bic r0, r0, #0x1f

  11.  
  12. ldr r2, =abort_stack_top

  13. orr r1, r0, #0x12 // irq

  14. msr cpsr_c, r1

  15. ldr r13, =irq_save_spot /* save a pointer to a temporary dumping spot used during irq delivery */

  16.  
  17. orr r1, r0, #0x11 // fiq

  18. msr cpsr_c, r1

  19. mov sp, r2

  20.  
  21. orr r1, r0, #0x17 // abort

  22. msr cpsr_c, r1

  23. mov sp, r2

  24.  
  25. orr r1, r0, #0x1b // undefined

  26. msr cpsr_c, r1

  27. mov sp, r2

  28.  
  29. orr r1, r0, #0x1f // system

  30. msr cpsr_c, r1

  31. mov sp, r2

  32.  
  33. orr r1, r0, #0x13 // supervisor

  34. msr cpsr_c, r1

  35. mov sp, r2

  36. ...

  37.  
  38. bl kmain

crt0.S 小结:

这里主要干的事情就是建立fiq/irq/abort等各种模式的stack,初始化向量表,然后切换到管理模式(pre-loader运行在EL3, lk运行在EL1),最后跳转到C代码入口 kmain 执行.

2、kmain :

 
  1. void kmain(void)

  2. {

  3. boot_time = get_timer(0);

  4.  
  5. /* 早期初始化线程池的上下文,包括运行队列、线程链表的建立等,

  6. lk架构支持多线程,但是此阶段只有一个cpu处于online,所以也只有一条代码执行路径.

  7. */

  8. thread_init_early();

  9.  
  10. /* 架构初始化,包括DRAM,MMU初始化使能,使能协处理器,

  11. preloader运行在ISRAM,属于物理地址,而lk运行在DRAM,可以选择开启MMU或者关闭,开启MMU可以加速lk的加载过程.

  12. */

  13. arch_early_init();

  14.  
  15. /*

  16. 平台硬件早期初始化,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等,

  17. 初始化平台硬件,建立lk基本运行环境。

  18. */

  19. platform_early_init();

  20.  
  21. boot_time = get_timer(0);

  22.  
  23. // 这个是保留的空函数.

  24. target_early_init();

  25.  
  26. dprintf(CRITICAL, "welcome to lk\n\n");

  27.  
  28. /*

  29. 执行定义在system-onesegment.ld 描述段中的构造函数,不太清楚具体机制:

  30. __ctor_list = .;

  31. .ctors : { *(.ctors) }

  32. __ctor_end = .;

  33. */

  34. call_constructors();

  35.  
  36. //内核堆链表上下文初始化等.

  37. heap_init();

  38.  
  39. // 线程池初始化,前提是PLATFORM_HAS_DYNAMIC_TIMER需要支持.

  40. thread_init();

  41.  
  42. // dpc系统是什么?据说是一个类似work_queue的东东,dpc的简称是什么就不清楚了.

  43. dpc_init();

  44.  
  45. // 初始化内核定时器

  46. timer_init();

  47.  
  48. // 创建系统初始化工作线程,执行app初始化,lk把业务部分当成一个app.

  49. thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));

  50.  
  51. // 使能中断.

  52. exit_critical_section();

  53.  
  54. // become the idle thread

  55. thread_become_idle();

  56. }

kmain 小结:

。初始化线程池,建立线程管理链表、运行队列等;

。初始化各种平台硬件,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等,建立lk基本运行环境;

。初始化内核heap、内核timer等;

。创建系统初始化主线程,进入bootstrap2执行,使能中断,当前线程进入idle;

 

3、bootstrap2 分析:

 
  1. static int bootstrap2(void *arg)

  2. {

  3. ...

  4. /*

  5. 平台相关初始化,包括nand/emmc,LCM显示驱动,启动模式选择,加载logo资源,

  6. 具体代码流程如下时序图.

  7. */

  8. platform_init();

  9. ...

  10.  
  11. /*

  12. app初始化,跳转到mt_boot_init入口开始执行,对应的 ".apps" 这个section.

  13. */

  14. apps_init();

  15.  
  16. return 0;

  17. }

platform_init 时序图:

这里的 apps_init 跳转机制还有点特别

 
  1. extern const struct app_descriptor __apps_start;

  2. extern const struct app_descriptor __apps_end;

  3. void apps_init(void)

  4. {

  5. const struct app_descriptor *app;

  6.  
  7. /* 这里具体干了什么?如何跳转到mt_boot_init入口?有点不知所云

  8. 依次遍历 从__apps_start 到__apps_end 又是什么东东?

  9. */

  10. for (app = &__apps_start; app != &__apps_end; app++) {

  11. if (app->init)

  12. app->init(app);

  13. }

  14.  
  15. ...

  16. }

这个__apps_start 跟 __apps_end哪里定义的? 是怎么回事呢? 这里就需要了解一点编译链接原理跟memory 布局的东东, 这个实际上是指memory中的一个只读数据段的起始&结束地址区间, 它定义在这个文件中:

 
  1. ./lk/arch/arm/system-onesegment.ld:47: __apps_start = .;

  2.  
  3. .rodata : {

  4. ...

  5. . = ALIGN(4);

  6. __apps_start = .;

  7. KEEP (*(.apps))

  8. __apps_end = .;

  9. . = ALIGN(4);

  10. __rodata_end = . ;

  11. }

该mem地址区间是[__apps_start, __apps_end],显然区间就是“.apps” 这个section内容了. 那么这个section是在哪里初始化的呢?继续看:

 
  1. ./lk/app/mt_boot/mt_boot.c:1724:

  2.  
  3. APP_START(mt_boot)

  4. .init = mt_boot_init,

  5. APP_END

展开APP_START:

 
  1. #define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,

  2. #define APP_END };

到这里就很明显了,编译链接系统会将mt_boot_init这个地址记录到".apps"这个section中!所以下面代码要干的事情就很清晰了,执行app->init(app)后就等价于调用了void mt_boot_init(const struct app_descriptor *app) 函数.

 
  1. for (app = &__apps_start; app != &__apps_end; app++) {

  2. if (app->init)

  3. app->init(app);

  4. }

bootstrap2 函数小结:

。平台相关初始化,包括nand/emmc,显现相关驱动,启动模式选择,加载logo资源 检测是否DA模式,检测分区中是否有KE信息,如果就KE信息,就从分区load 到DRAM, 点亮背光,显示logo,禁止I/D-cache和MMU,跳转到DA(??),配置二级cache的size 获取bat电压,判断是否低电量是否显示充电logo等,总之此函数干的事情比较多.时序图(platform_init)可以比较清晰直观的描述具体细节

。跳转到到mt_boot_init函数,对应的 ".apps" 这个section,相关机制上面已经详细描述,不再复述.

4、mt_boot_init 分析

 
  1. void mt_boot_init(const struct app_descriptor *app)

  2. {

  3. unsigned usb_init = 0;

  4. unsigned sz = 0;

  5. int sec_ret = 0;

  6. char tmp[SN_BUF_LEN+1] = {0};

  7. unsigned ser_len = 0;

  8. u64 key;

  9. u32 chip_code;

  10. char serial_num[SERIALNO_LEN];

  11.  
  12. /* 获取串号字符串 */

  13. key = get_devinfo_with_index(13);

  14. key = (key << 32) | (unsigned int)get_devinfo_with_index(12);

  15.  
  16. /* 芯片代码 */

  17. chip_code = board_machtype();

  18.  
  19. if (key != 0)

  20. get_serial(key, chip_code, serial_num);

  21. else

  22. memcpy(serial_num, DEFAULT_SERIAL_NUM, SN_BUF_LEN);

  23. /* copy serial from serial_num to sn_buf */

  24. memcpy(sn_buf, serial_num, SN_BUF_LEN);

  25. dprintf(CRITICAL,"serial number %s\n",serial_num);

  26.  
  27. /* 从特定分区获取产品sn号,如果获取失败就使用默认值 DEFAULT_SERIAL_NUM */

  28. #ifdef SERIAL_NUM_FROM_BARCODE

  29. ser_len = read_product_info(tmp);

  30. if (ser_len == 0) {

  31. ser_len = strlen(DEFAULT_SERIAL_NUM);

  32. strncpy(tmp, DEFAULT_SERIAL_NUM, ser_len);

  33. }

  34. memset( sn_buf, 0, sizeof(sn_buf));

  35. strncpy( sn_buf, tmp, ser_len);

  36. #endif

  37. sn_buf[SN_BUF_LEN] = '\0';

  38. surf_udc_device.serialno = sn_buf;

  39.  
  40. /* mtk平台默认不支持 fastboot */

  41. if (g_boot_mode == FASTBOOT)

  42. goto fastboot;

  43.  
  44. /* secure boot相关 */

  45. #ifdef MTK_SECURITY_SW_SUPPORT

  46. #if MTK_FORCE_VERIFIED_BOOT_SIG_VFY

  47. g_boot_state = BOOT_STATE_RED;

  48. #else

  49. if (0 != sec_boot_check(0)) {

  50. g_boot_state = BOOT_STATE_RED;

  51. }

  52. #endif

  53. #endif

  54.  
  55. /* 这里干的事情就比较多了,跟进g_boot_mode选择各种启动模式,例如:

  56. normal、facotry、fastboot、recovery等,然后从ROM中的boot.img分区找到(解压)

  57. ramdisk跟zImage的地址loader到DRAM的特定地址中,kernel最终load到DRAM中的地址

  58. (DRAM_PHY_ADDR + 0x8000) == 0x00008000.

  59. read the data of boot (size = 0x811800)

  60. */

  61. boot_linux_from_storage();

  62.  
  63. fastboot:

  64. target_fastboot_init();

  65. if (!usb_init)

  66. /*Hong-Rong: wait for porting*/

  67. udc_init(&surf_udc_device);

  68.  
  69. mt_part_dump();

  70. sz = target_get_max_flash_size();

  71. fastboot_init(target_get_scratch_address(), sz);

  72. udc_start();

  73.  
  74. }

mt_boot_init 分析小结:

。获取设备串号字符串、芯片代码、sn号等.

。如果实现了secure boot则进行sec boot的check工作;

。进入 boot_linux_from_storage 函数初始化,该函数很重要,干了很多事情,如下分析.

 

5、boot_linux_from_storage 分析:

 
  1. int boot_linux_from_storage(void)

  2. {

  3. int ret=0;

  4. ...

  5.  
  6. switch (g_boot_mode) {

  7. case NORMAL_BOOT:

  8. case META_BOOT:

  9. case ADVMETA_BOOT:

  10. case SW_REBOOT:

  11. case ALARM_BOOT:

  12. case KERNEL_POWER_OFF_CHARGING_BOOT:

  13. case LOW_POWER_OFF_CHARGING_BOOT:

  14. /* 检查boot分区的头部是否有bootopt标识,如果没有就报错 */

  15. ret = mboot_android_load_bootimg_hdr("boot", CFG_BOOTIMG_LOAD_ADDR);

  16. if (ret < 0) {

  17. msg_header_error("Android Boot Image");

  18. }

  19.  
  20. /* 64bit & 32bit kimg地址获取不一样*/

  21. if (g_is_64bit_kernel) {

  22. kimg_load_addr = (unsigned int)target_get_scratch_address();

  23. } else {

  24. kimg_load_addr = (g_boot_hdr!=NULL) ? g_boot_hdr->kernel_addr : CFG_BOOTIMG_LOAD_ADDR;

  25. }

  26.  
  27. /*

  28. 从EMMC的boot分区取出bootimage载入到DRAM

  29. dprintf(CRITICAL, " > from - 0x%016llx (skip boot img hdr)\n",start_addr);

  30. dprintf(CRITICAL, " > to - 0x%x (starts with kernel img hdr)\n",addr);

  31. len = dev->read(dev, start_addr, (uchar*)addr, g_bimg_sz); <<= 系统调用load到DRAM

  32.  
  33. 开机log:

  34. [3380] > from - 0x0000000001d20800 (skip boot img hdr)

  35. [3380] > to - 0x80008000 (starts with kernel img hdr)

  36. */

  37. ret = mboot_android_load_bootimg("boot", kimg_load_addr);

  38. if (ret < 0) {

  39. msg_img_error("Android Boot Image");

  40. }

  41.  
  42. dprintf(CRITICAL,"[PROFILE] ------- load boot.img takes %d ms -------- \n", (int)get_timer(time_load_bootimg));

  43.  
  44. break;

  45.  
  46. case RECOVERY_BOOT:

  47. ...

  48. break;

  49.  
  50. case FACTORY_BOOT:

  51. case ATE_FACTORY_BOOT:

  52. ...

  53.  
  54. break;

  55. ...

  56.  
  57. }

  58.  
  59. /* 重定位根文件系统(ramdisk)地址 */

  60. memcpy((g_boot_hdr!=NULL) ? (char *)g_boot_hdr->ramdisk_addr : (char *)CFG_RAMDISK_LOAD_ADDR, (char *)(g_rmem_off), g_rimg_sz);

  61. g_rmem_off = (g_boot_hdr!=NULL) ? g_boot_hdr->ramdisk_addr : CFG_RAMDISK_LOAD_ADDR;

  62.  
  63. ...

  64.  
  65. /* 传入cmdline,设置selinux */

  66. #if SELINUX_STATUS == 1

  67. cmdline_append("androidboot.selinux=disabled");

  68. #elif SELINUX_STATUS == 2

  69. cmdline_append("androidboot.selinux=permissive");

  70. #endif

  71.  
  72. /* 准备启动linux kernel */

  73. boot_linux((void *)CFG_BOOTIMG_LOAD_ADDR, (unsigned *)CFG_BOOTARGS_ADDR,

  74. (char *)cmdline_get(), board_machtype(), (void *)CFG_RAMDISK_LOAD_ADDR, g_rimg_sz);

  75.  
  76. while (1) ;

  77.  
  78. return 0;

  79. }

boot_linux_from_storage 小结:

。跟据g_boot_mode选择各种启动模式,例如: normal、facotry、fastboot、recovery等,然后从EMMC中的boot分区找到(解压) ramdisk跟zImage的地址通过read系统调用load到DRAM址中, kernel最终load到DRAM的地址:(DRAM_PHY_ADDR + 0x8000);

。重定位根文件系统地址;

。跳转到 boot_linux,正式拉起kernel;

 

6、boot_linux 分析: 

boot_linux 实际上跑的是boot_linux_fdt,这个函数有对dtb的加载做出来,期间操作相当复杂,这里只简单关注主流程.

 
  1. void boot_linux(void *kernel, unsigned *tags,

  2. char *cmdline, unsigned machtype,

  3. void *ramdisk, unsigned ramdisk_size)

  4. {

  5. ...

  6. // 新架构都是走fdt分支.

  7. #ifdef DEVICE_TREE_SUPPORT

  8. boot_linux_fdt((void *)kernel, (unsigned *)tags,

  9. (char *)cmdline, machtype,

  10. (void *)ramdisk, ramdisk_size);

  11.  
  12. while (1) ;

  13. #endif

  14. ...

  15.  
  16. int boot_linux_fdt(void *kernel, unsigned *tags,

  17. char *cmdline, unsigned machtype,

  18. void *ramdisk, unsigned ramdisk_size)

  19. {

  20. ...

  21. void (*entry)(unsigned,unsigned,unsigned*) = kernel;

  22. ...

  23.  
  24. // find dt from kernel img

  25. if (fdt32_to_cpu(*(unsigned int *)dtb_addr) == FDT_MAGIC) {

  26. dtb_size = fdt32_to_cpu(*(unsigned int *)(dtb_addr+0x4));

  27. } else {

  28. dprintf(CRITICAL,"Can't find device tree. Please check your kernel image\n");

  29. while (1) ;

  30. }

  31. ...

  32.  
  33. if (!has_set_p2u) {

  34. /* 控制进入kernel后uart的输出,非eng版本默认是关闭的,如果调试需要就可以改这里为

  35. "printk.disable_uart=0"

  36. */

  37.  
  38. #ifdef USER_BUILD

  39. sprintf(cmdline,"%s%s",cmdline," printk.disable_uart=1");

  40. #else

  41. sprintf(cmdline,"%s%s",cmdline," printk.disable_uart=0 ddebug_query=\"file *mediatek* +p ; file *gpu* =_\"");

  42. #endif

  43. ...

  44. }

  45.  
  46. ...

  47.  
  48. // led,irq关闭

  49. platform_uninit();

  50.  
  51. // 关闭I/D-cache,关闭MMU,今天kernel的条件.

  52. arch_disable_cache(UCACHE);

  53. arch_disable_mmu();

  54.  
  55. // sec init

  56. extern void platform_sec_post_init(void)__attribute__((weak));

  57. if (platform_sec_post_init) {

  58. platform_sec_post_init();

  59. }

  60.  
  61. // 如果是正在充电,检测到power key后执行reset.

  62. if (kernel_charging_boot() == 1) {

  63. if (pmic_detect_powerkey()) {

  64. dprintf(CRITICAL,"[%s] PowerKey Pressed in Kernel Charging Mode Before Jumping to Kernel, Reboot Os\n", __func__);

  65. mtk_arch_reset(1);

  66. }

  67. }

  68. #endif

  69. ...

  70.  
  71. // 输出关键信息。

  72. dprintf(CRITICAL,"cmdline: %s\n", cmdline);

  73. dprintf(CRITICAL,"lk boot time = %d ms\n", lk_t);

  74. dprintf(CRITICAL,"lk boot mode = %d\n", g_boot_mode);

  75. dprintf(CRITICAL,"lk boot reason = %s\n", g_boot_reason[boot_reason]);

  76. dprintf(CRITICAL,"lk finished --> jump to linux kernel %s\n\n", g_is_64bit_kernel ? "64Bit" : "32Bit");

  77.  
  78. // 执行系统调用,跳转到kernel,这里的entry实际上就是前面的kernel在DRAM的入口地址.

  79. if (g_is_64bit_kernel) {

  80. lk_jump64((u32)entry, (u32)tags, 0, KERNEL_64BITS);

  81. } else {

  82. dprintf(CRITICAL,"[mt_boot] boot_linux_fdt entry:0x%08x, machtype:%d\n",entry,machtype);

  83. entry(0, machtype, tags);

  84. }

  85. while (1);

  86. return 0;

  87. }

开机log打印信息:

 
  1. [4260] cmdline: console=tty0 console=ttyMT0,921600n1 root=/dev/ram vmalloc=496M androidboot.hardware=mt6580 androidboot.verifiedbootstate=green bootopt=64S3,32S1,32S1 printk.disable_uart=1 bootprof.pl_t=1718 bootprof.lk_t=2178 boot_reason=0 androidboot.serialno=0123456789ABCDEF androidboot.bootreason=power_key gpt=1

  2.  
  3. [4260] lk boot time = 2178 ms

  4.  
  5. [4260] lk boot mode = 0

  6.  
  7. [4260] lk boot reason = power_key

  8.  
  9. [4260] lk finished --> jump to linux kernel 32Bit

  10.  
  11. [4260] [mt_boot] boot_linux_fdt entry:0x80008000, machtype:6580

boot_linux 小结:

。初始化DTB(device tree block);

。准备各种cmdline参数传入kernel;

。关闭I/D-cache、MMU;

。打印关键信息,正式拉起kernel.

到这里,bootloader两个阶段就分析完了!

 

Bootloader 启动简单总结:

 

Pre-loader -》lk主要干的事情:

1、初始化 DRAM等必须硬件;

2、与flashtool USB握手,download 相关检测 & sec boot检测;

3、将lk载入DRAM,若实现了EL3则把atf载入内存;

4、跳转到lk,若实现了EL3,则先跳转到atf,初始化atf后再跳转回lk初始化;

 

lk -》 kernel 主要干的事情:

1、打开MMU,使能I/D-cache,加速lk执行,显示logo、充电相关;

2、从emmc中boot分区取出boot.img解压,将根文件系统(ramdisk)、zImage load到DRAM;

3、解析dtb,写入到DRAM指定区域;

4、关闭MMU、irq / fiq,关闭I/D-cache, 拉起 kernel;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值