1. 程式人生 > >[Android]高通平臺BootLoader啟動流程

[Android]高通平臺BootLoader啟動流程

一、什麼是BootLoader

BootLoader程式碼是晶片復位後,進入作業系統之前執行的一段程式碼。主要用於完成由硬體啟動到作業系統啟動的過渡,從而為作業系統提供基本的執行環境。

BootLoder主要的啟動流程可以概括為:PBL階段、SBL階段、LK階段。之後會載入並啟動kernel。

二、名詞解釋


5個處理器: 

  1. APPS :Cortex A53 core(MSM8953),執行android。
  2. RPM(Resource Power Manager):CortexM3 core,主要用於低功耗應用。
  3. Modem(MSS_QDSP6) :高通自有指令集處理器,處理3G、4G通訊協議等。
  4. Pronto(WCNSS): 處理wifi相關程式碼。
  5. LPASS :音訊相關。

啟動相關: 

  1. PBL(Primary Boot Loader):位於晶片內ROM中,是晶片上電後執行的真正第一行程式碼,在正常啟動流程中會載入SBL1。如果啟動異常會虛擬出9008埠可用於 Emergency download(短接板子上的force_boot_from_usb引腳(MSM8953 為gpio37)到1.8v可以強制進入緊急下載模式)。(此段程式碼封裝在晶片內部,並不開源)
  2. SBL1 (Second BootLoader stage 1) :位於eMMC中,由PBL載入,初始化buses、DDR、clocks等。
  3. QSEE/TrustZone  安全相關。
  4. Debug Policy 除錯相關。
  5. APPSBL :即為BootLoader,目前使用LK(little kernel)。
  6. HLOS (High LevelOperating System) 即為Linux/Android。

三、啟動流程

  1. AP側CPU上電。
  2. 在晶片內部ROM的PBL首先執行,PBL會從boot device(eMMC)中載入並驗證SBL1到TCM中。這裡的TCM可以理解為CPU的二級快取。既然PBL能夠從boot device(eMMC)中載入SBL1,那PBL應該是初始化過boot device的。  
  3. SBL1初始化DDR,並從boot device中載入並且校驗如下映象: QSEE或者TZ映象、QHEE映象、RPM_FW、映象、APPSBL等。
  4. SBL1載入並驗證完上述映象後,即將執行權轉移到QSEE中,QSEE將設定並初始化一個安全的執行環境。
  5. QSEE通知RPM去執行RPM_FW相關程式碼。
  6. QSEE將執行權轉移到APPSBL中,APPSBL也就是LK。
  7. LK載入HLOS的kernel。

四、程式碼流程簡要流程 

SBL

PBL部分並不開源,所以從SBL開始。

sbl1入口: sbl1.s

此部分程式碼路徑在:boot_images/core/boot/secboot3/hw/msm89xx/sbl1/sbl1.s,此檔案引導處理器,主要有實現如下操作:

部分原始碼:

  • 設定硬體,繼續boot程序。
  • 初始化ddr。
  • 載入Trust_Zone作業系統。
  • 載入RPM韌體。
  • 載入APPSBL然後繼續boot程序。 
IMPORT |Image$$SBL1_SVC_STACK$$ZI$$Limit|
IMPORT |Image$$SBL1_UND_STACK$$ZI$$Limit|
IMPORT |Image$$SBL1_ABT_STACK$$ZI$$Limit|
IMPORT boot_undefined_instruction_c_handler
IMPORT boot_swi_c_handler
IMPORT boot_prefetch_abort_c_handler
IMPORT boot_data_abort_c_handler
IMPORT boot_reserved_c_handler
IMPORT boot_irq_c_handler
IMPORT boot_fiq_c_handler
IMPORT boot_nested_exception_c_handler
IMPORT sbl1_main_ctl #主要關注此函式
IMPORT boot_crash_dump_regs_ptr
...

 sbl1_main_ctl函式

路徑:...\sbl1\sbl1_mc.c

/* Calculate the SBL start time for use during boot logger initialization. */
sbl_start_time = CALCULATE_TIMESTAMP(HWIO_IN(TIMETICK_CLK));
boot_clock_debug_init();
/* Enter debug mode if debug cookie is set */
sbl1_debug_mode_enter();  
/* Initialize the stack protection canary */
boot_init_stack_chk_canary();

/* Initialize boot shared imem */
boot_shared_imem_init(&bl_shared_data);
/*初始化RAM*/
boot_ram_init(&sbl1_ram_init_data);
/*初始化log系統,即串列埠驅動*/
sbl1_boot_logger_init(&boot_log_data, pbl_shared);
/*檢索PBL傳遞過來的資料*/ 
sbl1_retrieve_shared_info_from_pbl(pbl_shared); 
/* Initialize the QSEE interface */
sbl1_init_sbl_qsee_interface(&bl_shared_data,&sbl_verified_info);
/* Initialize SBL memory map. Initializing early because drivers could be located in RPM Code RAM. */
sbl1_populate_initial_mem_map(&bl_shared_data);
/*初始化DAL*/
boot_DALSYS_InitMod(NULL); 
/*配置PMIC晶片,以便我們能通過PS_HOLD復位*/
sbl1_hw_init();
/*執行sbl1的目標依賴程序*/
boot_config_process_bl(&bl_shared_data, SBL1_IMG, sbl1_config_table);

sbl1_config_table函式

路徑:boot_images\core\boot\secboot3\hw\msmxxxx\sbl1\sbl1_config.c

sbl1_config_table為一個結構體陣列,裡面儲存了載入QSEE、RPM、APPSBL等映象所需要的配置引數及執行函式。

boot_configuration_table_entry sbl1_config_table[] = 
{

 /* SBL1 -> QSEE */
  {
    SBL1_IMG,                   /* host_img_id */
    CONFIG_IMG_QC,              /* host_img_type */
    GEN_IMG,                    /* target_img_id */
    CONFIG_IMG_ELF,             /* target_img_type */
    ...
    load_qsee_pre_procs,        /* pre_procs */ 
    load_qsee_post_procs,       /* post_procs */
}
/* SBL1 -> QHEE */
...
/* SBL1 -> RPM */
...
/* SBL1 -> APPSBL (即lk部分) */
...

LK

LK大概流程:

bootloader/lk目錄結構:

  • 進行各種早期的初始化工作,包括 cpu, emmc, ddr, clocks, thread 等。
  • 判斷進入 recovery 或 fastboot 的條件是否被觸發,不觸發則為Normal Boot。
  • 從 emmc 中獲取 boot.img 並載入到指定記憶體區域 (scratch region)。
  • 載入Kernel、Ramdisk、DeviceTree等到指定地址。
  • 引導Kernel
-app        #應用相關
-arch       #arm體系、CPU架構
-dev        #裝置驅動
-images     #image圖片資源
-include    #標頭檔案
-kernel     #LK系統、主檔案,main.c
-lib        #庫檔案
-platform   #平臺檔案,如:msm8916
-project    #mk檔案
-scripts    #指令碼檔案
-target     #目標裝置檔案

程式碼流程: 

lk 是使用 arm 彙編 和 c 語言聯合編譯而成的,其中偏向硬體和底層的程式碼使用 arm 彙編 編寫,而偏上層提供功能的程式碼則使用 c 編寫。

入口:

lk 程式碼的入口點是在 arch/arm 目錄下的以 .ld 為字尾的 link 指令碼檔案中指定。

指定的入口點均為位於 arch/arm/crt0.S 檔案中的 _start 函式:

  • system-onesegment.ld 
  • system-twosegment.ld

_start 最主要的作用是設定一些 cpu 的特性,然後初始化各種 c 程式執行需要的棧環境,完成後直接跳轉到 kmian 函式進入 c 語言環境。 

Kmain()函式:

void kmain(void)
{
	thread_init_early(); // 初始化化lk執行緒上下文
	arch_early_init(); // 架構初始化,如關閉cache,使能mmu
	platform_early_init(); // 平臺硬體早期初始化
	target_early_init(); //目標裝置早期初始化
	bs_set_timestamp(BS_BL_START);
	call_constructors(); //靜態建構函式初始化
	heap_init(); // 堆初始化
	thread_init(); // 初始化執行緒
	dpc_init();  //lk系統控制器初始化
	timer_init(); //kernel時鐘初始化

#if (!ENABLE_NANDWRITE)
	thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE)); // 建立一個執行緒初始化系統
	exit_critical_section(); //使能中斷
	thread_become_idle(); //本執行緒切換為idle執行緒
#else
        bootstrap_nandwrite(); 
#endif
}

bootstrap2 

此函式由kmain中建立的執行緒呼叫,路徑為:bootable\bootloader\lk\kernel\main.c。

static int bootstrap2(void *arg)
{
	arch_init(); //架構初始化
	bio_init();
	fs_init();
	platform_init(); //平臺初始化, 主要初始化系統時鐘,超頻等
	target_init(); //目標裝置初始化,主要初始化Flash,整合分割槽表等
	apps_init(); // 應用功能初始化,呼叫aboot_init,載入kernel等
}

apps_init()函式:

app_init 函式位於 app/app.c 檔案中

/* app entry point */
struct app_descriptor;
typedef void (*app_init)(const struct app_descriptor *);
typedef void (*app_entry)(const struct app_descriptor *, void *args);

/* app startup flags */
#define APP_FLAG_DONT_START_ON_BOOT 0x1

/* each app needs to define one of these to define its startup conditions */
struct app_descriptor {
  const char *name;
  app_init  init;
  app_entry entry;
  unsigned int flags;
};

void apps_init(void)
{
  const struct app_descriptor *app;

  /* call all the init routines */
  for (app = &__apps_start; app != &__apps_end; app++) {
    if (app->init)
      app->init(app);
  }

  /* start any that want to start on boot */
  for (app = &__apps_start; app != &__apps_end; app++) {
    if (app->entry && (app->flags & APP_FLAG_DONT_START_ON_BOOT) == 0) {
      start_app(app);
    }
  }
}

static void start_app(const struct app_descriptor *app)
{
  thread_t *thr;
  printf("starting app %s\n", app->name);

  thr = thread_create(app->name, &app_thread_entry, (void *)app, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
  if(!thr)
    {
      return;
    }
  thread_resume(thr);
}

 整個遍歷 app 並啟動的過程並不複雜,有趣的是 __apps_start__apps_end 的定義,這兩個變數符號在所有原始檔中並不存在,而是在 arch/arm/*.ld 連結指令碼中存在, __apps_start__apps_end 是自定義的兩個符號,代表了自定義段 .apps 的開始位置和結束位置。也就是說所有的 app 都通過在特殊的段 .apps 中註冊實現了一套外掛系統,是一個十分精巧的設計。後續的任何新的 app 只需要使用以下兩個巨集宣告即可註冊到 .apps 段中:

#define __SECTION(x) __attribute((section(x)))
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };

bootloader/lk/app/aboot/aboot.c 結尾:

APP_START(aboot)
	.init = aboot_init,
APP_END

 說明apps_init函式呼叫了aboot_init函式

aboot_init函式:

是通過 APP_START註冊的 aboot 入口函式, aboot 的所有功能都是由此開始。位於 bootloader/lk/app/aboot/aboot.c檔案中。

簡單流程:

  • 設定NAND/eMMC讀取資訊頁面的大小。
  • 列印一些裝置模組的Log資訊以及初始化。
  • 檢測按鍵,判斷是否進入fastboot或者recovery模式。
  • 獲取device info(裝置是否lock,是否root,img是否已鑑權等資訊)
  • 從eMMC或NAND flash中載入核心。

主要函式:boot_linux_from_mmc 

boot.img 解析:https://blog.csdn.net/ty3219/article/details/78879398

  • 首先載入boot image
  • 獲取image header size,分配image header buffer,從指定分割槽載入image header。
  • 如果配置了Secure Boot
  • imagesizeActure += 簽名size   
  • imagesize = imagesizeActure + image header pagesize
  • 分配image buffer並從分割槽中載入image
  • 校驗簽名
  • 校驗失敗通知TZ,啟動失敗。
  • 校驗成功則即系載入image。
  • 解壓縮boot.img,分別載入kernel、ramdisk、device tree等到指定地址。
  • 通知TZ載入結束。啟動kernel。
  • BootLoader結束。

boot.image加簽:

  1.  將原始boot.img通過SHA256演算法進行計算得到一個雜湊值。
  2. 使用private key對雜湊值進行加簽。
  3. 將最終的雜湊值和簽名新增到原始boot.img末尾。

簽名校驗: 

  1. 將boot.img進行SHA256雜湊計算,獲得檔案digest1。
  2. 將簽名通過public key進行解密,獲得檔案digest2。
  3. 對比digest1和digest2兩個檔案。
  4. 相同則校驗成功,不同則校驗失敗。

Boot流程的粗略理解。