1. 程式人生 > >Linux啟動流程_LK流程_aboot_init(不包含recovery boot)(2.1)

Linux啟動流程_LK流程_aboot_init(不包含recovery boot)(2.1)

    深入,並且廣泛
    				-沉默犀牛

此篇部落格原部落格來自freebuf,原作者SetRet。原文連結:https://www.freebuf.com/news/135084.html

寫在前面的話

寫這篇文章之前,我只好假定你所知道的跟我一樣淺薄(針對本文這一方面),所以如果你看到一些實在是太小兒科的內容,請你多加擔待,這確實就是我目前的水平,謝謝。

這裡就開始啦!

上一篇部落格分析了bootstrap2都做了些什麼,內容並不多,我們大概總結一下:
先後進行了:初始化了 SPMI(system power management interface) 系統電源管理結構的控制器,檢測音量上下按鍵的狀態並記錄,emmc,讀分割槽表,檢測pwr_key按鍵時間、震動,初始化加密引擎,然後就遍歷了.apps段,執行在這個欄位中的init函式,接下來就進入到了aboot_init函數了。(很多平臺在這個階段只有aboot這個app)

大致描述aboot_init

aboot_init中的程式碼比較多,分為四個部分來介紹:
1.init部分
2.檢測啟動方式
3.fastboot部分模式啟動
4.非fastboot模式啟動

init部分

1.獲取分頁大小,並儲存到全域性變數 page_size 和 page_mask 中

/* Setup page size information for nv storage */
	if (target_is_emmc_boot())
	{
		page_size = mmc_page_size();
		page_mask = page_size - 1;
		mmc_blocksize = mmc_get_device_blocksize();
		mmc_blocksize_mask = mmc_blocksize - 1;
	}
	else
	{
		page_size = flash_page_size();
		page_mask = page_size - 1;
	}
	ASSERT((MEMBASE + MEMSIZE) > MEMBASE);

2.從 emmc 中的 aboot 分割槽或 deviceinfo 分割槽獲取 device

read_device_info(&device);

這裡的 device 是一個全域性變數,其結構如下:

typedef struct device_info device_info;

#define DEVICE_MAGIC "ANDROID-BOOT!"
#define DEVICE_MAGIC_SIZE 13
#define MAX_PANEL_ID_LEN 64
#define MAX_VERSION_LEN  64

struct device_info
{
  unsigned char magic[DEVICE_MAGIC_SIZE];
  bool is_unlocked;
  bool is_tampered;
  bool is_verified;
  bool charger_screen_enabled;
  char display_panel[MAX_PANEL_ID_LEN];
  char bootloader_version[MAX_VERSION_LEN];
  char radio_version[MAX_VERSION_LEN];
};

static device_info device = {DEVICE_MAGIC, 0, 0, 0, 0, {0}, {0},{0}};

其中儲存的資訊在後期經常會用到,比如 device_info.is_unlocked 就是 bootloader 是否解鎖的標誌位。


3.從 emmc 中的 config 分割槽或 frq 分割槽獲取 is_allow_unlock 標誌位

read_allow_oem_unlock(&device);

一般都為允許,使用加密手段來限制解鎖


4.初始化開始螢幕資訊和全域性的螢幕資訊快取 display_panel_buf,大小為 128

#if DISPLAY_SPLASH_SCREEN
#if NO_ALARM_DISPLAY
	if (!check_alarm_boot()) {
#endif
		dprintf(SPEW, "Display Init: Start\n");
#if DISPLAY_HDMI_PRIMARY
	if (!strlen(device.display_panel))
		strlcpy(device.display_panel, DISPLAY_PANEL_HDMI,
			sizeof(device.display_panel));
#endif
#if ENABLE_WBC
		/* Wait if the display shutdown is in progress */
		while(pm_app_display_shutdown_in_prgs());
		if (!pm_appsbl_display_init_done())
			target_display_init(device.display_panel);
		else
			display_image_on_screen();
#else
		target_display_init(device.display_panel);
#endif
		dprintf(SPEW, "Display Init: Done\n");
#if NO_ALARM_DISPLAY
	}
#endif
#endif

5.獲取序列號並儲存在全域性變數 sn_buf 中

	target_serialno((unsigned char *) sn_buf);
	dprintf(SPEW,"serial number: %s\n",sn_buf);

序列號就儲存在 target_sdc_init 中初始化的 dev 變數中,還記得target_sdc_init這個函式嗎?
在bootstrap2 -> target_init -> target_sdc_init //初始化emmc


2.檢測啟動方式

這部分的程式碼只是通過檢測關機的方式,按鍵的狀態來設定確定進入哪種模式的啟動方式,但是不同的機型,對應的按鍵組合並不相同

/*
	 * Check power off reason if user force reset,
	 * if yes phone will do normal boot.
	 */
	if (is_user_force_reset())
		goto normal_boot;

	/* Check if we should do something other than booting up */
	if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN))
	{
		dprintf(ALWAYS,"dload mode key sequence detected\n");
		reboot_device(EMERGENCY_DLOAD);
		dprintf(CRITICAL,"Failed to reboot into dload mode\n");

		boot_into_fastboot = true;
	}
	if (!boot_into_fastboot)
	{
		if (keys_get_state(KEY_HOME) || keys_get_state(KEY_VOLUMEUP))
			boot_into_recovery = 1;
		if (!boot_into_recovery &&
			(keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN)))
			boot_into_fastboot = true;
	}
	#if NO_KEYPAD_DRIVER
	if (fastboot_trigger())
		boot_into_fastboot = true;
	#endif

#if USE_PON_REBOOT_REG
	reboot_mode = check_hard_reboot_mode();
#else
	reboot_mode = check_reboot_mode();
#endif
	if (reboot_mode == RECOVERY_MODE)
	{
		boot_into_recovery = 1;
	}
	else if(reboot_mode == FASTBOOT_MODE)
	{
		boot_into_fastboot = true;
	}
	else if(reboot_mode == ALARM_BOOT)
	{
		boot_reason_alarm = true;
	}
#if VERIFIED_BOOT
	else if (VB_M <= target_get_vb_version())
	{
		if (reboot_mode == DM_VERITY_ENFORCING)
		{
			device.verity_mode = 1;
			write_device_info(&device);
		}
#if ENABLE_VB_ATTEST
		else if (reboot_mode == DM_VERITY_EIO)
#else
		else if (reboot_mode == DM_VERITY_LOGGING)
#endif
		{
			device.verity_mode = 0;
			write_device_info(&device);
		}
		else if (reboot_mode == DM_VERITY_KEYSCLEAR)
		{
			if(send_delete_keys_to_tz())
				ASSERT(0);
		}
	}
#endif

常用的就只有 普通模式/recovery 模式/fastboot 模式 3 種,也是需要重點分析的 3 種
非 fastboot 模式啟動就是 recovery 模式 或者 普通模式啟動,這兩者所使用的是同一套載入流程,所以可以歸類為同一類。


3.fastboot部分模式啟動

fastboot 模式啟動fastboot 模式是 android 定義的一套通訊協議,可以指定引數寫入 emmc 分割槽的方法,通俗的說就是刷機的介面

fastboot:
	
	aboot_fastboot_register_commands();			//fastboot指令註冊
	partition_dump();
	
	/*設定 usb 監聽的執行緒,並且啟動了解析指令的執行緒*/
	fastboot_init(target_get_scratch_address(), target_get_max_flash_size());		

fastboot 本身就是 lk 的一部分,負責對外的一個介面而已,並不是一個單獨的系統,是依賴於 lk 存在的。fastboot 可以分為以下幾個方面:

  • fastboot 指令註冊
  • fastboot 監聽啟動
  • fastboot 指令解析

按照這個分類來一步步分析整個 fastboot 的框架

fastboot 指令註冊

void aboot_fastboot_register_commands(void)
{
  int i;

  struct fastboot_cmd_desc cmd_list[] = {
                      /* By default the enabled list is empty. */
                      {"", NULL},
                      /* move commands enclosed within the below ifndef to here
                       * if they need to be enabled in user build.
                       */
#ifndef DISABLE_FASTBOOT_CMDS
                      /* Register the following commands only for non-user builds */
                      {"flash:", cmd_flash},
                      {"erase:", cmd_erase},
                      {"boot", cmd_boot},
                      {"continue", cmd_continue},
                      {"reboot", cmd_reboot},
                      {"reboot-bootloader", cmd_reboot_bootloader},
                      {"oem unlock", cmd_oem_unlock},
                      {"oem unlock-go", cmd_oem_unlock_go},
                      {"oem lock", cmd_oem_lock},
                      {"oem verified", cmd_oem_verified},
                      {"oem device-info", cmd_oem_devinfo},
                      {"preflash", cmd_preflash},
                      {"oem enable-charger-screen", cmd_oem_enable_charger_screen},
                      {"oem disable-charger-screen", cmd_oem_disable_charger_screen},
                      {"oem select-display-panel", cmd_oem_select_display_panel},
#if UNITTEST_FW_SUPPORT
                      {"oem run-tests", cmd_oem_runtests},
#endif
#endif
                      };

  int fastboot_cmds_count = sizeof(cmd_list)/sizeof(cmd_list[0]);
  for (i = 1; i < fastboot_cmds_count; i++)
    fastboot_register(cmd_list[i].name,cmd_list[i].cb);
/*********************************************************************************************************/
  /* publish variables and their values */
  fastboot_publish("product",  TARGET(BOARD));
  fastboot_publish("kernel",   "lk");
  fastboot_publish("serialno", sn_buf);

  /*
   * partition info is supported only for emmc partitions
   * Calling this for NAND prints some error messages which
   * is harmless but misleading. Avoid calling this for NAND
   * devices.
   */
  if (target_is_emmc_boot())
    publish_getvar_partition_info(part_info, ARRAY_SIZE(part_info));

  /* Max download size supported */
  snprintf(max_download_size, MAX_RSP_SIZE, "\t0x%x",
      target_get_max_flash_size());
  fastboot_publish("max-download-size", (const char *) max_download_size);
  /* Is the charger screen check enabled */
  snprintf(charger_screen_enabled, MAX_RSP_SIZE, "%d",
      device.charger_screen_enabled);
  fastboot_publish("charger-screen-enabled",
      (const char *) charger_screen_enabled);
  snprintf(panel_display_mode, MAX_RSP_SIZE, "%s",
      device.display_panel);
  fastboot_publish("display-panel",
      (const char *) panel_display_mode);
  fastboot_publish("version-bootloader", (const char *) device.bootloader_version);
  fastboot_publish("version-baseband", (const char *) device.radio_version);
}

這個函式可以分為兩部分:
1.fastboot 指令註冊,fastboot 的指令使用了一個 struct fastboot_cmd_desc 型別的區域性陣列來儲存,這個結構包含了 fastboot 的 指令字串 和 處理函式。有了這個陣列後,就可以使用 fastboot_register 函式將指令註冊到全域性連結串列 cmd_list 中,這樣fastboot 所有的指令都可以通過遍歷連結串列而得到。

2.fastboot 資料註冊,fastboot 模式還儲存了一些主要的裝置和廠商資訊,這些資訊都統一由 fastboot_publish 來註冊,註冊的資訊和指令一樣,儲存在一個全域性連結串列 varlist 中,這個結構比較簡單,只有名稱和對應的資料,和指令一樣的道理,通過遍歷 varlist 就可以找到全部的資料。

通過上面的兩個步驟後, fastboot 的所有 指令 和 資料 就註冊完成了,接下來的需要啟動 fastboot 對 USB 裝置的監聽,以接送 fastboot 命令


fastboot 監聽啟動

fastboot_init 的程式碼可以分為以下 3 個流程:

  1. usb_if(usb controller interface) 初始化

  2. usb_if 繫結

  3. fastboot 執行緒啟動

usb_if(usb controller interface) 初始化

usb_if 是一個全域性變數,這個階段就是為這個結構體賦予一些需要的值,方便後面使用

  1. 初始化了兩個全域性變數 download_basedownload_size, 這塊空間是 fastboot 刷入系統時的緩衝區
  2. 獲取序列號並賦值給 surf_udc_device
  3. 初始化 usb_if 中的各控制函式,
usb_if 繫結

usb_if 初始化完成後,需要設定通訊的渠道,和響應命令的方法
建立了兩個事件, usb_onlinetxn_done:
usb_online 是響應 usb 上線的事件,然後等待處理命令
txn_done 則是在請求 usb 操作時等待返回的訊號,由 req_complete 函式傳送此訊號

fastboot 執行緒啟動

這一部分比較簡單,主要的功能就是啟動一個執行緒等待 fastboot 的指令傳入,並且啟動 udc

  1. 新註冊了以下兩條指令 getvar: 和 download:, 以及一條資料 version
  2. 建立並啟動 fastboot 執行緒,執行緒的功能就是等待 usb_online 事件,然後解析 fastboot 指令
  3. 開啟 udc

fastboot 指令解析

申請並清零緩衝區,然後使用 usb_read 介面獲取 usb 資料。
遍歷 cmdlist, 比對 指令 呼叫對應指令的處理函式