bootloader過程(preloader---->lk)

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>
如果實現了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)


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>
Pre-loader 到 Lk的源碼分析到這就完成了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章