Android啓動流程(一)

本章節內容是基於展訊32bit ARM手機平臺的android啓動流程進行解析,不同廠商的手機平臺啓動流程也都大體相似,涉及的代碼大多開源,可以在相關社區進行下載。不同廠商的順序第一步都是ROM,高通8996稱做PBL,接下來展訊是spl,高通8996是XBL,64bit或者一些平臺會有secure相關或者資源管理相關的步驟,然後是uboot,高通一直用lk,最後就是kernel和Android。
啓動順序爲:

  1. ROM啓動
  2. spl
  3. uboot
  4. kernel
  5. Andriod
    我也將按照上述順序對啓動流程進行解析,本小節首先介紹ROM啓動,spl和uboot。
    #1 ROM啓動
    手機SOC芯片內部一般都固化了一片ROM,手機上電後,ROM啓動代碼開始執行,實現的功能較簡單,主要功能爲識別啓動方式,並將下一級啓動代碼加載進內部SRAM,然後進行跳轉。大體流程如下代碼所示。
    (1) 設置cpu mode
    (2) 判斷啓動方式,進入下載模式還是啓動模式。此處一般是判斷pin狀態或按鍵狀態來決定
    (3a)下載模式,初始化usb或者uart,從pc下載image
    (3b) 啓動模式,初始化emmc,從flash加載spl到iram,之後跳轉到iram spl起始地址。
    #2 spl
    SOC芯片內部都有一定大小的SRAM,不需進行ram的初始化和刷新操作即可正常使用。ROM啓動中如果判斷啓動方式爲啓動模式,就會將spl從emmc相應分區中加載到sram中,然後將PC指到相應的地址,即完成了ROM到spl的跳轉。
    spl主要工作是運行環境初始化和DDR初始化,然後將uboot從emmc相應分區load到DDR SDRAM上,並跳轉到uboot的入口執行uboot啓動流程。
    彙編階段依次執行設置cpu模式,建立臨時棧,disable MMU, clear bss,到此C語言環境建立好了,可以跳轉到C代碼運行。
    spl_start.S
b reset
reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0, cpsr
	bic	r0, r0, #0x1f
	orr	r0, r0, #0xd3
	msr	cpsr,r0

	/*set up temp stack*/
	LDR 	sp, =SVC_STACK_TEMP

	bl	cpu_init_crit  //disable MMU

	ldr 	r0, _bss_start_ofs
	ldr 	r1, _bss_end_ofs
	bl 	clear_bss

	bl	Chip_Init
	ldr     sp, =SPL_STACK
	b	nand_boot

SPL主要功能在C代碼執行的Chip_Init和nand_boot中實現。
Chip_Init主要實現如代碼所示,MCU_Init對clock進行初始化,主要針對mcu, ddr以及訪存通路上bus的clock初始化。Vol_Init進行電壓調整。sdam_init對手機平臺使用的ddr sdram進行初始化,這也是spl需要完成的最主要工作。

void Chip_Init (void)
{
    ...
    MCU_Init();  //初始化clock

    Vol_Init();  //初始化power

    sdram_init(); //初始化ddr
    ...
}
ddr sdram初始化完成後,就可以將uboot從emmc flash中加載到內存中運行,此工作由nand_boot函數完成。
void nand_boot(void)
{
    ...
    if (TRUE == Emmc_Init()) {  //emmc初始化
	Emmc_Read(PARTITION_BOOT2, 2, (CONFIG_EMMC_U_BOOT_SECTOR_NUM + 1), (uint8 *)(CONFIG_U_BOOT_DST_ADDR));      //加載uboot 
    }
    uboot = (void *)CONFIG__U_BOOT_START_ADDR;
    (*uboot) ();                //跳轉到uboot執行
}

#3 uboot
uboot執行過程可分爲兩部分,初始化流程和啓動流程。
##初始化流程
uboot代碼已經可以在ddr sdram中進行執行了,初始化流程主要工作是運行環境的初始化和各驅動模塊的初始化,是一個線性流程。
start.S

reset:

	/*
	 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
	 * except if in HYP mode already
	 */
	mrs	r0, cpsr
	and	r1, r0, #0x1f		@ mask mode bits
	teq	r1, #0x1a		@ test for HYP mode
	bicne	r0, r0, #0x1f		@ clear all mode bits
	orrne	r0, r0, #0x13		@ set SVC mode
	orr	r0, r0, #0xc0		@ disable FIQ and IRQ
	msr	cpsr,r0
	
	bl	cpu_init_cp15
	bl	_main

彙編代碼中先設置cpu運行模式,disable中斷,然後調用cpu_init_cp15對MMU, dcache進行disable,icache是否disable與具體配置有關。

ENTRY(_main)
	ldr	sp, =(CONFIG_SYS_INIT_SP_ADDR)           //(1)
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */
	mov	r2, sp
	sub	sp, sp, #GD_SIZE	/* allocate one GD above SP */
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */

	mov	r9, sp		/* GD is above SP */    //(2)
	mov	r1, sp
	mov	r0, #0
clr_gd:
	cmp	r1, r2			/* while not at end of GD */
	strlo	r0, [r1]		/* clear 32-bit GD word */
	addlo	r1, r1, #4		/* move to next */
	blo	clr_gd
	...
	bl	board_init_f	                         //(3)
	...
	b	relocate_code                            //(4)
	...
	bl	c_runtime_cpu_setup                      //(5)
	blo	clbss_l
	...
	ldr	pc, =board_init_r                        //(6)
ENDPROC(_main)

_main完成的工作如上述代碼所示:
(1)設置初始化棧
(2)分配global data佔用空間,並進行初始化
(3)調用board_init_f,f是front,與後面的board_init_r呼應,r是rear。該函數除了對global data成員變量進行初始化外,還對init_sequence數組中成員函數進行調用,進行必要的板級初始化。其中dram_init會對sdram進行必要的配置,一般此處只是對sdram大小進行配置,後面的reserve函數們會對內存的使用進行分配,如爲mmu, lcd framework reserve內存等。

void board_init_f(ulong bootflag)
{
	...
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}	
	...
	//省略了內存分佈代碼
}

init_fnc_t *init_sequence[] = {
	arch_cpu_init,		/* basic arch cpu dependent setup */
	mark_bootstage,
#ifdef CONFIG_OF_CONTROL
	fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
	board_early_init_f,
#endif
	timer_init,		/* initialize timer */
#ifdef CONFIG_BOARD_POSTCLK_INIT
	board_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHC
	get_clocks,
#endif
	env_init,		/* initialize environment */
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,		/* stage 1 init of console */
	display_banner,		/* say that we are here */
	print_cpuinfo,		/* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)
	checkboard,		/* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
	init_func_i2c,
#endif
	dram_init,		/* configure available RAM banks */
	NULL,
};

(4)relocate, 我使用的手機平臺中沒有進行實現,relocate應該主要在如下兩種情況需要:
a. 只有一級啓動,比如啓動代碼在nor flash中啓動,既需要對內存進行初始化,也需要把自己加載到內存中運行。
b. 內存空間不足,uboot和kernel共用同一段內存,比如都在起始地址附近,所以在加載kernel之前要搬運uboot到較遠的地址再運行。
(5)建立C運行環境,clear bss
(6)調用board_init_r
board_init_r會依次調用各驅動模塊的初始化接口,其中有很多與板級相關或與配置先關,如下代碼只列出了我所使用平臺執行的初始化功能。

void board_init_r(gd_t *id, ulong dest_addr)
{
	enable_caches();  //使能cache,根據具體需求決定是否使能
	board_init();     //具體的板級初始化,下面有展開
#ifdef CONFIG_CLOCKS
	set_cpu_clk_info(); //初始化clock framework,依賴CONFIG_CLOCKS
#endif
	serial_initialize(); //初始化串口
	mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN); //堆初始化
	power_init_board();  //板級的power初始化,由板級代碼實現
#ifdef CONFIG_GENERIC_MMC
	mmc_initialize(gd->bd); //flash相關初始化,此平臺使用emmc,如果使用其他存儲設備就進行相應的初始化
#endif
	stdio_init();	//包括LCD的初始化,此時logo會load到fb,但是背光沒有使能,還沒能顯示
	console_init_r();	//初始化控制檯
	 /* set up exceptions */
	interrupt_init();       //初始化中斷
	/* enable exceptions */
	enable_interrupts();    //使能中斷,根據具體需求決定是否使能
#ifdef CONFIG_BOARD_LATE_INIT
	board_late_init();      //由板級代碼實現
#endif	
	for (;;) {
		main_loop();
	}
}
int board_init()
{

	setup_chipram_env();

	gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;
	ADI_init();
	misc_init();
	regulator_init();
	pmic_adc_Init();
	pin_init();
	vendor_eic_init();
	vendor_intc_enable();
	vendor_gpio_init();
	vendor_pmu_lowpower_init();
	vendor_pmu_init();

	return 0;
}

mian_loop提供了一套命令選擇的流程,需要配置三個宏:

#define CONFIG_PREBOOT "role" //確定了執行真正的命令列表前有哪些準備工作的命令需要執行
#define CONFIG_BOOTDELAY 5   //確定了等待鍵盤輸入時間
#define CONFIG_BOOTCOMMAND "cboot normal" //確定了bootcmd值

至此,uboot的初始化流程結束了,下面會跳轉到do_cboot進入啓動流程。
##啓動流程
支持的啓動模式如下枚舉變量boot_mode_enum_type的定義,do_cboot中會根據不同的按鍵狀態,寄存器等條件選擇進入到不同的開機模式。
typedef enum {
CMD_UNDEFINED_MODE=0,
CMD_POWER_DOWN_DEVICE,
CMD_NORMAL_MODE,
CMD_RECOVERY_MODE,
CMD_FASTBOOT_MODE,
CMD_ALARM_MODE,
CMD_CHARGE_MODE,
CMD_ENGTEST_MODE,
CMD_WATCHDOG_REBOOT,
CMD_AP_WATCHDOG_REBOOT,
CMD_SPECIAL_MODE,
CMD_UNKNOW_REBOOT_MODE,
CMD_PANIC_REBOOT,
CMD_VMM_PANIC_MODE, //0xd
CMD_TOS_PANIC_MODE,
CMD_EXT_RSTN_REBOOT_MODE,
CMD_CALIBRATION_MODE,
CMD_FACTORYTEST_MODE,
CMD_AUTODLOADER_REBOOT,
CMD_AUTOTEST_MODE,
CMD_IQ_REBOOT_MODE,
CMD_SLEEP_MODE,

/*this is not a mode name ,beyond CMD_MAX_MODE means overflow*/
CMD_MAX_MODE

}boot_mode_enum_type;
正常情況下會調用normal_mode()進行正常啓動,normal_mode()調用vlx_nand_boot,參數BOOT_PART是boot分區名,BACKLIGHT_ON是打開背光。

void normal_mode(void)
{
	vibrator_hw_init();

	set_vibrator(1);
	vlx_nand_boot(BOOT_PART, BACKLIGHT_ON, LCD_ON);
}
void vlx_nand_boot(char *kernel_pname, int backlight_set, int lcd_enable)
{
#ifdef CONFIG_SPLASH_SCREEN
	lcd_init_time = SCI_GetTickCount();
	printf("lcd start init time:%dms\n", lcd_init_time);
	if(lcd_enable) {
		extern void lcd_enable(void);
		debug("[LCD] Drawing the logo...\n");
		drv_lcd_init();
		lcd_splash(LOGO_PART);
		lcd_enable();
	}
	set_backlight(backlight_set);  //此時會有logo顯示
#endif
	set_vibrator(0);

	_boot_load_kernel_ramdisk_image(kernel_pname, hdr, &dt_adr);  

	while (s_boot_image_table[i]) {
		j = 0;
		while (s_boot_image_table[i][j].partition) {
			_boot_load_required_image(s_boot_image_table[i][j]);
			j++;
		}
		i++;
	}
	vlx_entry(dt_adr);
}

vlx_nand_boot除了顯示logo外,主要進行image的加載,先加載kernel, dt和ramdisk這些與內核運行相關的image文件,然後會根據s_boot_image_table數組的定義加載其它image,比如modem的image。加載完成後會調用vlx_entry, vlx_entry會disbale cache, MMU,然後跳轉到kernel。

發佈了29 篇原創文章 · 獲贊 27 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章