深入 bootloader 啓動分析筆記
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 啓動完成.
-- 從 pre-loader 到 lk(mt6580 爲例)
Pre-loader 主要乾的事情就是初始化某些硬件,比如:UART,GPIO,DRAM,TIMER,RTC,PMIC 等等,建立起最基本的運行環境, 最重要的就是初始化 DRAM。
源碼流程如下:
./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 main
init.s 主要乾的事情是切換系統到管理模式(svc)(如果平臺有實現 el3,那麼 pre-loader 運行在 el3,否則運行在 el1),禁止 irq/fiq,設置 stack 等, 然後 jump 到 c 代碼 main 函數入口。
進入源碼分析
./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執行,
如果實現了EL3情況就要
複雜一些,需要先跳轉到EL3初始化,然後再跳回lk,pre-loader執行在EL3,
LK執行在EL1)
從log可以類似看到這些信息:
[BLDR] jump to 0x81E00000
[BLDR] <0x81E00000>=0xEA000007
[BLDR] <0x81E00004>=0xEA0056E2
*/
#if CFG_ATF_SUPPORT
/* 64S3,32S1,32S1 (MTK_ATF_BOOT_OPTION = 0)
* re-loader jump to LK directly and then LK jump to kernel directly */
if ( BOOT_OPT_64S3 == g_smc_boot_opt &&
BOOT_OPT_32S1 == g_lk_boot_opt &&
BOOT_OPT_32S1 == g_kernel_boot_opt) {
print("%s 64S3,32S1,32S1, jump to LK\n", MOD);
bldr_jump(jump_addr, jump_arg, sizeof(boot_arg_t));
} else {
// 如果 el3 使用aarch64實現,則jump到atf.
print("%s Others, jump to ATF\n", MOD);
bldr_jump64(jump_addr, jump_arg, sizeof(boot_arg_t));
}
#else
bldr_jump(jump_addr, jump_arg, sizeof(boot_arg_t));
#endif
// 如果沒有取到jump_addr,則打印錯誤提示,進入while(1)等待.
error:
platform_error_handler();
}
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)
重點函數分析
bldr_load_images
函數主要乾的事情就是找到 lk 分區地址和 lk 加載到 DRAM 中的地址, 準備好 jump 到 lk 執行。
如下源碼分析:
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;
}
bldr_post_process
函數主要乾的事情就是從 pmic 去檢查是否有電池存在,如果沒有就等待
如下源碼分析,比較簡單:
// 就是包了一層而已.
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);
}
...
}
Pre-loader 到 Lk 的源碼分析到這就完成了.
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/hBeSNXXAuomVvQIBajpS5Q