bootloader(lk---->kernel)

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;


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