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 個流程:
-
usb_if(usb controller interface) 初始化
-
usb_if 繫結
-
fastboot 執行緒啟動
usb_if(usb controller interface) 初始化
usb_if 是一個全域性變數,這個階段就是為這個結構體賦予一些需要的值,方便後面使用
- 初始化了兩個全域性變數
download_base
和download_size
, 這塊空間是 fastboot 刷入系統時的緩衝區 - 獲取序列號並賦值給
surf_udc_device
- 初始化
usb_if
中的各控制函式,
usb_if 繫結
usb_if 初始化完成後,需要設定通訊的渠道,和響應命令的方法
建立了兩個事件, usb_online
和 txn_done
:
usb_online
是響應 usb 上線的事件,然後等待處理命令
txn_done
則是在請求 usb 操作時等待返回的訊號,由 req_complete 函式傳送此訊號
fastboot 執行緒啟動
這一部分比較簡單,主要的功能就是啟動一個執行緒等待 fastboot 的指令傳入,並且啟動 udc
- 新註冊了以下兩條指令
getvar
: 和download
:, 以及一條資料version
- 建立並啟動
fastboot
執行緒,執行緒的功能就是等待usb_online
事件,然後解析fastboot
指令 - 開啟
udc
fastboot 指令解析
申請並清零緩衝區,然後使用 usb_read 介面獲取 usb 資料。
遍歷 cmdlist, 比對 指令 呼叫對應指令的處理函式