Pre-loader 運行在ISRAM,待完成 DRAM 的初始化後,再將lk載入DRAM中,最後通過特殊sys call手段實現跳轉到lk的執行入口,正式進入lk初始化階段.
一、lk執行入口:
位於.text.boot 這個section(段),具體定義位置爲:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >./lk/arch/arm/system-onesegment.ld:10: .text.boot : { *(.text.boot) } ./lk/arch/arm/system-twosegment.ld:10: .text.boot : { *(.text.boot) }de>
該段的代碼執行入口是crt0.S文件,位置爲:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >./lk/arch/arm/crt0.Sde>
crt0.S 中會經過一系列的初始化準備操作,最終跳轉到C代碼入口kmain函數開始執行,這個是 我們需要重點分析關注的,kmain的位置:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >./lk/kernel/main.cde>
From Lk to Kernel 總時序圖
二、源碼分析:
de style="display: inline; padding: 0px; overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >1、crt0.S .section ".text.boot" ... .Lstack_setup: /* ==set up the stack for irq, fi==q, abort, undefined, system/user, and lastly supervisor mode */ mrs r0, cpsr bic r0, r0, #0x1f ldr r2, =abort_stack_top orr r1, r0, #0x12 // irq msr cpsr_c, r1 ldr r13, =irq_save_spot /* save a pointer to a temporary dumping spot used during irq delivery */ orr r1, r0, #0x11 // fiq msr cpsr_c, r1 mov sp, r2 orr r1, r0, #0x17 // abort msr cpsr_c, r1 mov sp, r2 orr r1, r0, #0x1b // undefined msr cpsr_c, r1 mov sp, r2 orr r1, r0, #0x1f // system msr cpsr_c, r1 mov sp, r2 orr r1, r0, #0x13 // supervisor msr cpsr_c, r1 mov sp, r2 ... bl kmainde>
crt0.S 小結:
這裏主要乾的事情就是建立fiq/irq/abort等各種模式的stack,初始化向量表,然後切換到管理模式(pre-loader運行在EL3, lk運行在EL1),最後跳轉到C代碼入口 kmain 執行.
2、kmain :
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >void kmain(void) { boot_time = get_timer(0); /* 早期初始化線程池的上下文,包括運行隊列、線程鏈表的建立等, lk架構支持多線程,但是此階段只有一個cpu處於online,所以也只有一條代碼執行路徑. */ thread_init_early(); /* 架構初始化,包括DRAM,MMU初始化使能,使能協處理器, preloader運行在ISRAM,屬於物理地址,而lk運行在DRAM,可以選擇開啓MMU或者關閉,開啓MMU可以加速lk的加載過程. */ arch_early_init(); /* 平臺硬件早期初始化,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等, 初始化平臺硬件,建立lk基本運行環境。 */ platform_early_init(); boot_time = get_timer(0); // 這個是保留的空函數. target_early_init(); dprintf(CRITICAL, "welcome to lk\n\n"); /* 執行定義在system-onesegment.ld 描述段中的構造函數,不太清楚具體機制: __ctor_list = .; .ctors : { *(.ctors) } __ctor_end = .; */ call_constructors(); //內核堆鏈表上下文初始化等. heap_init(); // 線程池初始化,前提是PLATFORM_HAS_DYNAMIC_TIMER需要支持. thread_init(); // dpc系統是什麼?據說是一個類似work_queue的東東,dpc的簡稱是什麼就不清楚了. dpc_init(); // 初始化內核定時器 timer_init(); // 創建系統初始化工作線程,執行app初始化,lk把業務部分當成一個app. thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE)); // 使能中斷. exit_critical_section(); // become the idle thread thread_become_idle(); }de>
kmain 小結:
。初始化線程池,建立線程管理鏈表、運行隊列等;
。初始化各種平臺硬件,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等,建立lk基本運行環境;
。初始化內核heap、內核timer等;
。創建系統初始化主線程,進入bootstrap2執行,使能中斷,當前線程進入idle;
3、bootstrap2 分析:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >static int bootstrap2(void *arg) { ... /* 平臺相關初始化,包括nand/emmc,LCM顯示驅動,啓動模式選擇,加載logo資源, 具體代碼流程如下時序圖. */ platform_init(); ... /* app初始化,跳轉到mt_boot_init入口開始執行,對應的 ".apps" 這個section. */ apps_init(); return 0; }de>
platform_init 時序圖:
這裏的 apps_init 跳轉機制還有點特別:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >extern const struct app_descriptor __apps_start; extern const struct app_descriptor __apps_end; void apps_init(void) { const struct app_descriptor *app; /* 這裏具體幹了什麼?如何跳轉到mt_boot_init入口?有點不知所云 依次遍歷 從__apps_start 到__apps_end 又是什麼東東? */ for (app = &__apps_start; app != &__apps_end; app++) { if (app->init) app->init(app); } ... }de>
這個__apps_start 跟 __apps_end哪裏定義的? 是怎麼回事呢? 這裏就需要了解一點編譯鏈接原理跟memory 佈局的東東, 這個實際上是指memory中的一個只讀數據段的起始&結束地址區間, 它定義在這個文件中:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >./lk/arch/arm/system-onesegment.ld:47: __apps_start = .; .rodata : { ... . = ALIGN(4); __apps_start = .; KEEP (*(.apps)) __apps_end = .; . = ALIGN(4); __rodata_end = . ; }de>
該mem地址區間是[__apps_start, __apps_end],顯然區間就是“.apps” 這個section內容了. 那麼這個section是在哪裏初始化的呢?繼續看:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >./lk/app/mt_boot/mt_boot.c:1724: APP_START(mt_boot) .init = mt_boot_init, APP_ENDde>
展開APP_START:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname, #define APP_END };de>
到這裏就很明顯了,編譯鏈接系統會將mt_boot_init這個地址記錄到".apps"這個section中!所以下面代碼要乾的事情就很清晰了,執行app->init(app)後就等價於調用了void mt_boot_init(const struct app_descriptor *app) 函數.
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >for (app = &__apps_start; app != &__apps_end; app++) { if (app->init) app->init(app); }de>
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 分析
de style="display: inline; padding: 0px; overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >void mt_boot_init(const struct app_descriptor *app) { unsigned usb_init = 0; unsigned sz = 0; int sec_ret = 0; char tmp[SN_BUF_LEN+1] = {0}; unsigned ser_len = 0; u64 key; u32 chip_code; char serial_num[SERIALNO_LEN]; /* 獲取串號字符串 */ key = get_devinfo_with_index(13); key = (key << 32) | (unsigned int)get_devinfo_with_index(12); /* 芯片代碼 */ chip_code = board_machtype(); if (key != 0) get_serial(key, chip_code, serial_num); else memcpy(serial_num, DEFAULT_SERIAL_NUM, SN_BUF_LEN); /* copy serial from serial_num to sn_buf */ memcpy(sn_buf, serial_num, SN_BUF_LEN); dprintf(CRITICAL,"serial number %s\n",serial_num); /* 從特定分區獲取產品sn號,如果獲取失敗就使用默認值 DEFAULT_SERIAL_NUM */ #ifdef SERIAL_NUM_FROM_BARCODE ser_len = read_product_info(tmp); if (ser_len == 0) { ser_len = strlen(DEFAULT_SERIAL_NUM); strncpy(tmp, DEFAULT_SERIAL_NUM, ser_len); } memset( sn_buf, 0, sizeof(sn_buf)); strncpy( sn_buf, tmp, ser_len); #endif sn_buf[SN_BUF_LEN] = '\0'; surf_udc_device.serialno = sn_buf; /* mtk平臺默認不支持 fastboot */ if (g_boot_mode == FASTBOOT) goto fastboot; /* secure boot相關 */ #ifdef MTK_SECURITY_SW_SUPPORT #if MTK_FORCE_VERIFIED_BOOT_SIG_VFY g_boot_state = BOOT_STATE_RED; #else if (0 != sec_boot_check(0)) { g_boot_state = BOOT_STATE_RED; } #endif #endif /* 這裏乾的事情就比較多了,跟進g_boot_mode選擇各種啓動模式,例如: normal、facotry、fastboot、recovery等,然後從ROM中的boot.img分區找到(解壓) ramdisk跟zImage的地址loader到DRAM的特定地址中,kernel最終load到DRAM中的地址 (DRAM_PHY_ADDR + 0x8000) == 0x00008000. read the data of boot (size = 0x811800) */ boot_linux_from_storage(); fastboot: target_fastboot_init(); if (!usb_init) /*Hong-Rong: wait for porting*/ udc_init(&surf_udc_device); mt_part_dump(); sz = target_get_max_flash_size(); fastboot_init(target_get_scratch_address(), sz); udc_start(); }de>
mt_boot_init 分析小結:
。獲取設備串號字符串、芯片代碼、sn號等.
。如果實現了secure boot則進行sec boot的check工作;
。進入 boot_linux_from_storage 函數初始化,該函數很重要,幹了很多事情,如下分析.
5、boot_linux_from_storage 分析:
de style="display: inline; padding: 0px; overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >int boot_linux_from_storage(void) { int ret=0; ... switch (g_boot_mode) { case NORMAL_BOOT: case META_BOOT: case ADVMETA_BOOT: case SW_REBOOT: case ALARM_BOOT: case KERNEL_POWER_OFF_CHARGING_BOOT: case LOW_POWER_OFF_CHARGING_BOOT: /* 檢查boot分區的頭部是否有bootopt標識,如果沒有就報錯 */ ret = mboot_android_load_bootimg_hdr("boot", CFG_BOOTIMG_LOAD_ADDR); if (ret < 0) { msg_header_error("Android Boot Image"); } /* 64bit & 32bit kimg地址獲取不一樣*/ if (g_is_64bit_kernel) { kimg_load_addr = (unsigned int)target_get_scratch_address(); } else { kimg_load_addr = (g_boot_hdr!=NULL) ? g_boot_hdr->kernel_addr : CFG_BOOTIMG_LOAD_ADDR; } /* 從EMMC的boot分區取出bootimage載入到DRAM dprintf(CRITICAL, " > from - 0x%016llx (skip boot img hdr)\n",start_addr); dprintf(CRITICAL, " > to - 0x%x (starts with kernel img hdr)\n",addr); len = dev->read(dev, start_addr, (uchar*)addr, g_bimg_sz); <<= 系統調用load到DRAM 開機log: [3380] > from - 0x0000000001d20800 (skip boot img hdr) [3380] > to - 0x80008000 (starts with kernel img hdr) */ ret = mboot_android_load_bootimg("boot", kimg_load_addr); if (ret < 0) { msg_img_error("Android Boot Image"); } dprintf(CRITICAL,"[PROFILE] ------- load boot.img takes %d ms -------- \n", (int)get_timer(time_load_bootimg)); break; case RECOVERY_BOOT: ... break; case FACTORY_BOOT: case ATE_FACTORY_BOOT: ... break; ... } /* 重定位根文件系統(ramdisk)地址 */ memcpy((g_boot_hdr!=NULL) ? (char *)g_boot_hdr->ramdisk_addr : (char *)CFG_RAMDISK_LOAD_ADDR, (char *)(g_rmem_off), g_rimg_sz); g_rmem_off = (g_boot_hdr!=NULL) ? g_boot_hdr->ramdisk_addr : CFG_RAMDISK_LOAD_ADDR; ... /* 傳入cmdline,設置selinux */ #if SELINUX_STATUS == 1 cmdline_append("androidboot.selinux=disabled"); #elif SELINUX_STATUS == 2 cmdline_append("androidboot.selinux=permissive"); #endif /* 準備啓動linux kernel */ boot_linux((void *)CFG_BOOTIMG_LOAD_ADDR, (unsigned *)CFG_BOOTARGS_ADDR, (char *)cmdline_get(), board_machtype(), (void *)CFG_RAMDISK_LOAD_ADDR, g_rimg_sz); while (1) ; return 0; }de>
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的加載做出來,期間操作相當複雜,這裏只簡單關注主流程.
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >void boot_linux(void *kernel, unsigned *tags, char *cmdline, unsigned machtype, void *ramdisk, unsigned ramdisk_size) { ... // 新架構都是走fdt分支. #ifdef DEVICE_TREE_SUPPORT boot_linux_fdt((void *)kernel, (unsigned *)tags, (char *)cmdline, machtype, (void *)ramdisk, ramdisk_size); while (1) ; #endif ... int boot_linux_fdt(void *kernel, unsigned *tags, char *cmdline, unsigned machtype, void *ramdisk, unsigned ramdisk_size) { ... void (*entry)(unsigned,unsigned,unsigned*) = kernel; ... // find dt from kernel img if (fdt32_to_cpu(*(unsigned int *)dtb_addr) == FDT_MAGIC) { dtb_size = fdt32_to_cpu(*(unsigned int *)(dtb_addr+0x4)); } else { dprintf(CRITICAL,"Can't find device tree. Please check your kernel image\n"); while (1) ; } ... if (!has_set_p2u) { /* 控制進入kernel後uart的輸出,非eng版本默認是關閉的,如果調試需要就可以改這裏爲 "printk.disable_uart=0" */ #ifdef USER_BUILD sprintf(cmdline,"%s%s",cmdline," printk.disable_uart=1"); #else sprintf(cmdline,"%s%s",cmdline," printk.disable_uart=0 ddebug_query=\"file *mediatek* +p ; file *gpu* =_\""); #endif ... } ... // led,irq關閉 platform_uninit(); // 關閉I/D-cache,關閉MMU,今天kernel的條件. arch_disable_cache(UCACHE); arch_disable_mmu(); // sec init extern void platform_sec_post_init(void)__attribute__((weak)); if (platform_sec_post_init) { platform_sec_post_init(); } // 如果是正在充電,檢測到power key後執行reset. if (kernel_charging_boot() == 1) { if (pmic_detect_powerkey()) { dprintf(CRITICAL,"[%s] PowerKey Pressed in Kernel Charging Mode Before Jumping to Kernel, Reboot Os\n", __func__); mtk_arch_reset(1); } } #endif ... // 輸出關鍵信息。 dprintf(CRITICAL,"cmdline: %s\n", cmdline); dprintf(CRITICAL,"lk boot time = %d ms\n", lk_t); dprintf(CRITICAL,"lk boot mode = %d\n", g_boot_mode); dprintf(CRITICAL,"lk boot reason = %s\n", g_boot_reason[boot_reason]); dprintf(CRITICAL,"lk finished --> jump to linux kernel %s\n\n", g_is_64bit_kernel ? "64Bit" : "32Bit"); // 執行系統調用,跳轉到kernel,這裏的entry實際上就是前面的kernel在DRAM的入口地址. if (g_is_64bit_kernel) { lk_jump64((u32)entry, (u32)tags, 0, KERNEL_64BITS); } else { dprintf(CRITICAL,"[mt_boot] boot_linux_fdt entry:0x%08x, machtype:%d\n",entry,machtype); entry(0, machtype, tags); } while (1); return 0; }de>
開機log打印信息:
de style="display: inline; padding: 0px; color: rgb(0, 0, 0); overflow: initial; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; margin: 0px; font-size: 13.6px; word-break: normal; border: 0px; max-width: initial; line-height: inherit; word-wrap: normal; background: 0px 0px transparent;" >[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 [4260] lk boot time = 2178 ms [4260] lk boot mode = 0 [4260] lk boot reason = power_key [4260] lk finished --> jump to linux kernel 32Bit [4260] [mt_boot] boot_linux_fdt entry:0x80008000, machtype:6580de>
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;