本章节内容是基于展讯32bit ARM手机平台的android启动流程进行解析,不同厂商的手机平台启动流程也都大体相似,涉及的代码大多开源,可以在相关社区进行下载。不同厂商的顺序第一步都是ROM,高通8996称做PBL,接下来展讯是spl,高通8996是XBL,64bit或者一些平台会有secure相关或者资源管理相关的步骤,然后是uboot,高通一直用lk,最后就是kernel和Android。
启动顺序为:
- ROM启动
- spl
- uboot
- kernel
- 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。