本章節內容是基於展訊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。