Android启动流程(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/flaoter/article/details/72993551

本章节内容是基于展讯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。

猜你喜欢

转载自blog.csdn.net/flaoter/article/details/72993551