1、bootloader到kernel啓動總邏輯流程圖
ARM架構中,EL0/EL1是必須實現,EL2/EL3是選配,ELx跟層級對應關係:
EL0 -- app
EL1 -- Linux kernel 、lk
EL2 -- hypervisor(虛擬化)
EL3 -- ARM trust firmware 、pre-loader
若平臺未實現EL3(atf),pre-loader直接加載lk:
若平臺實現EL3,則需要先加載完ATF再由ATF去加載lk:
bootloader 啓動分兩個階段,一個是pre-loader加載lk(u-boot)階段,另一個是lk加載kernel階段。下面跟着流程圖簡述第一個階段的加載流程。
1-3:設備上電起來後,跳轉到Boot ROM(不是flash)中的boot code中執行把pre-loader加載起到ISRAM, 因爲當前DRAM(RAM分SRAM跟DRAM,簡單來說SRAM就是cache,DRAM就是普通內存)還沒有準備好,所以要先把pre-loader load到芯片內部的ISRAM(Internal SRAM)中。
4-6:pre-loader初始化好DRAM後就將lk從flash(nand/emmc)中加載到DRAM中運行;
7-8:解壓bootimage成ramdisk跟kernel並載入DRAM中,初始化dtb;
9-11:lk跳轉到kernl初始化, kernel初始化完成後fork出init進程, 然後拉起ramdisk中的init程序,進入用戶空間初始化,init進程fork出zygote進程..直到整個Android啓動完成.
2、從pre-loader到lk(mt6580爲例)
Pre-loader主要乾的事情就是初始化某些硬件,比如: UART,GPIO,DRAM,TIMER,RTC,PMIC 等等,建立起最基本的運行環境,最重要的就是初始化DRAM.
時序圖:
源碼流程如下:
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;" >./bootloader/preloader/platform/mt6580/src/init/init.s .section .text.start ... .globl _start ... /* set the cpu to SVC32 mode */ MRS r0,cpsr BIC r0,r0,#0x1f ORR r0,r0,#0xd3 MSR cpsr,r0 /* disable interrupt */ MRS r0, cpsr MOV r1, #INT_BIT ORR r0, r0, r1 MSR cpsr_cxsf, r0 ... setup_stk : /* setup stack */ LDR r0, stack LDR r1, stacksz ... entry : LDR r0, =bldr_args_addr /* 跳轉到C代碼 main 入口 */ B mainde>
init.s 主要乾的事情是切換系統到管理模式(svc)(如果平臺有實現el3,那麼pre-loader運行在el3,否則運行在el1),禁止irq/fiq,設置stack等, 然後jump到c代碼main函數入口。
進入源碼分析。
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;" >./bootloader/preloader/platform/mt6580/src/core/main.c void main(u32 *arg) { struct bldr_command_handler handler; u32 jump_addr, jump_arg; /* get the bldr argument */ bldr_param = (bl_param_t *)*arg; // 初始化uart mtk_uart_init(UART_SRC_CLK_FRQ, CFG_LOG_BAUDRATE); // 這裏幹了很多事情,包括各種的平臺硬件(timer,pmic,gpio,wdt...)初始化工作. bldr_pre_process(); handler.priv = NULL; handler.attr = 0; handler.cb = bldr_cmd_handler; // 這裏是獲取啓動模式等信息保存到全局變量g_boot_mode和g_meta_com_type 中. BOOTING_TIME_PROFILING_LOG("before bldr_handshake"); bldr_handshake(&handler); BOOTING_TIME_PROFILING_LOG("bldr_handshake"); // 下面跟 secro img 相關,跟平臺設計強相關. /* security check */ sec_lib_read_secro(); sec_boot_check(); device_APC_dom_setup(); BOOTING_TIME_PROFILING_LOG("sec_boot_check"); /* 如果已經實現EL3,那麼進行tz預初始化 */ #if CFG_ATF_SUPPORT trustzone_pre_init(); #endif /* bldr_load_images 此函數要做的事情就是把lk從ROM中指定位置load到DRAM中,開機log中可以看到具體信息: [PART] load "lk" from 0x0000000001CC0200 (dev) to 0x81E00000 (mem) [SUCCESS] 這裏準備好了jump到DRAM的具體地址,下面詳細分析. */ if (0 != bldr_load_images(&jump_addr)) { print("%s Second Bootloader Load Failed\n", MOD); goto error; } /* 該函數的實現體是platform_post_init,這裏要乾的事情其實比較簡單,就是通過 hw_check_battery去判斷當前系統是否存在電池(判斷是否有電池ntc腳來區分), 如果不存在就陷入while(1)卡住了,所以在es階段調試有時候 需要接電源調試的,就需要改這裏面的邏輯纔可正常開機 */ bldr_post_process(); // atf 正式初始化,使用特有的系統調用方式實現. #if CFG_ATF_SUPPORT trustzone_post_init(); #endif /* 跳轉傳入lk的參數,包括boot time/mode/reason 等,這些參數在 platform_set_boot_args 函數獲取。 */ jump_arg = (u32)&(g_dram_buf->boottag); /* 執行jump系統調用,從 pre-loader 跳轉到 lk執行,de>
main 函數小結:
1、各種硬件初始化(uart、pmic、wdt、timer、mem..);
2、獲取系統啓動模式等,保存在全局變量中;
3、Security check,跟secro.img相關;
4、如果系統已經實現el3,則進入tz初始化;
5、獲取lk加載到DRAM的地址(固定值),然後從ROM中找到lk分區的地址, 如果沒找到jump_addr,則 goto error;
6、battery check,如果沒有電池就會陷入while(1);
7、jump到lk(如果有實現el3,則會先jump到el3,然後再回到lk)
3、重點函數分析
bldr_load_images
函數主要乾的事情就是找到lk分區地址和lk加載到DRAM中的地址, 準備好jump到lk執行,如下源碼分析:
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 bldr_load_images(u32 *jump_addr) { int ret = 0; blkdev_t *bootdev; u32 addr = 0; char *name; u32 size = 0; u32 spare0 = 0; u32 spare1 = 0; ... /* 這個地址是一個固定值,可以查到定義在: ./bootloader/preloader/platform/mt6580/default.mak:95: CFG_UBOOT_MEMADDR := 0x81E00000 從log中可以看到: [BLDR] jump to 0x81E00000 */ addr = CFG_UBOOT_MEMADDR; /* 然後去ROM找到lk所在分區地址 */ ret = bldr_load_part("lk", bootdev, &addr, &size); if (ret) return ret; *jump_addr = addr; } // 這個函數邏輯很簡單,就不需要多說了. int bldr_load_part(char *name, blkdev_t *bdev, u32 *addr, u32 *size) { part_t *part = part_get(name); if (NULL == part) { print("%s %s partition not found\n", MOD, name); return -1; } return part_load(bdev, part, addr, 0, size); } // 真正的load實現是在part_load函數. int part_load(blkdev_t *bdev, part_t *part, u32 *addr, u32 offset, u32 *size) { int ret; img_hdr_t *hdr = (img_hdr_t *)img_hdr_buf; part_hdr_t *part_hdr = &hdr->part_hdr; gfh_file_info_t *file_info_hdr = &hdr->file_info_hdr; /* specify the read offset */ u64 src = part->startblk * bdev->blksz + offset; u32 dsize = 0, maddr = 0; u32 ms; // 檢索分區頭是否正確。 /* retrieve partition header. */ if (blkdev_read(bdev, src, sizeof(img_hdr_t), (u8*)hdr,0) != 0) { print("[%s]bdev(%d) read error (%s)\n", MOD, bdev->type, part->name); return -1; } if (part_hdr->info.magic == PART_MAGIC) { /* load image with partition header */ part_hdr->info.name[31] = '\0'; /* 輸出分區的各種信息,從log中可以看到: [PART] Image with part header [PART] name : lk [PART] addr : FFFFFFFFh mode : -1 [PART] size : 337116 [PART] magic: 58881688h */ print("[%s]Img with part header\n", MOD); print("[%s]name:%s\n", MOD, part_hdr->info.name); print("[%s]addr:%xh\n", MOD, part_hdr->info.maddr); print("[%s]size:%d\n", MOD, part_hdr->info.dsize); print("[%s]magic:%xh\n", MOD, part_hdr->info.magic); maddr = part_hdr->info.maddr; dsize = part_hdr->info.dsize; src += sizeof(part_hdr_t); memcpy(part_info + part_num, part_hdr, sizeof(part_hdr_t)); part_num++; } else { print("[%s]%s img not exist\n", MOD, part->name); return -1; } // 如果maddr沒有定義,那麼就使用前面傳入的地址addr. if (maddr == PART_HEADER_MEMADDR/*0xffffffff*/) maddr = *addr; if_overlap_with_dram_buffer((u32)maddr, ((u32)maddr + dsize)); ms = get_timer(0); if (0 == (ret = blkdev_read(bdev, src, dsize, (u8*)maddr,0))) *addr = maddr; ms = get_timer(ms); /* 如果一切順利就會打印出關鍵信息: [PART] load "lk" from 0x0000000001CC0200 (dev) to 0x81E00000 (mem) [SUCCESS] [PART] load speed: 25324KB/s, 337116 bytes, 13ms */ print("\n[%s]load \"%s\" from 0x%llx(dev) to 0x%x (mem) [%s]\n", MOD, part->name, src, maddr, (ret == 0) ? "SUCCESS" : "FAILED"); if( ms == 0 ) ms+=1; print("[%s]load speed:%dKB/s,%d bytes,%dms\n", MOD, ((dsize / ms) * 1000) / 1024, dsize, ms); return ret; }de>
bldr_post_process
函數主要乾的事情就是從pmic去檢查是否有電池存在,如果沒有就等待, 如下源碼分析,比較簡單:
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 void bldr_post_process(void) { platform_post_init(); } // 重點是這個函數: void platform_post_init(void) { /* normal boot to check battery exists or not */ if (g_boot_mode == NORMAL_BOOT && !hw_check_battery() && usb_accessory_in()) { ... pl_charging(1); do { mdelay(300); /* 檢查電池是否存在, 如果使用電源調試則需要修改此函數邏輯 */ if (hw_check_battery()) break; /* 喂狗,以免超時被狗咬 */ platform_wdt_all_kick(); } while(1); /* disable force charging mode */ pl_charging(0); } ... }de>