Linux啟動流程_LK流程_bootstrap2(1)
深入,並且廣泛
-沉默犀牛
此篇部落格原部落格來自freebuf,原作者SetRet。原文連結:https://www.freebuf.com/news/135084.html
寫在前面的話
寫這篇文章之前,我只好假定你所知道的跟我一樣淺薄(針對本文這一方面),所以如果你看到一些實在是太小兒科的內容,請你多加擔待,這確實就是我目前的水平,謝謝。
這裡就開始啦!
上一篇我們已經分析過Kmain函數了,現在來回顧一遍發現Kmain顯示初始化了一些硬體資源,包括執行緒初始化,CPU的一些設定,平臺初始化(board、clk、qgic、scm),和串列埠除錯,開啟這些資源就是為LK和kernel的執行準備環境。(這一點從完成了這些初始化之後才打印 welcome lk 也能看得出來)接下來就是一些軟體的初始化,包括為bootstate賦值(bootloader的啟動時間),堆初始化,dpc初始化,定時器初始化。做完這些準備工作之後,就啟動了bootstrap2執行緒,我們這篇文章就看一下這個執行緒做了什麼。
大致描述bootstrap2
本文介紹一下兩個函式,其他兩個函式在很多平臺是空函式
target_init(); // 初始化spmi、emmc,檢測pwr key按鍵和震動等
apps_init(); // 呼叫所有apps欄位的init函式
target_init
void target_init(void) { uint32_t base_addr; uint8_t slot; dprintf(INFO, "target_init()\n"); /*初始化 SPMI(system power management interface) 系統電源管理結構的控制器*/ spmi_init(PMIC_ARB_CHANNEL_NUM, PMIC_ARB_OWNER_ID); /*獲取並設定 音量上鍵 和 音量下鍵 的狀態,而所有按鍵的狀態都儲存在全域性陣列 key_bitmap 中*/ target_keystatus(); target_sdc_init(); //初始化 emmc if (partition_read_table()) //讀取分割槽表 { dprintf(CRITICAL, "Error reading the partition table info\n"); ASSERT(0); } #if LONG_PRESS_POWER_ON shutdown_detect(); //檢測開機時間不足夠長則關機 #endif #if PON_VIB_SUPPORT vib_timed_turn_on(VIBRATE_TIME); //開啟手機震動 4/1 秒,提示使用者手機開啟 #endif if (target_use_signed_kernel()) target_crypto_init_params(); //初始化加密解密引擎,用於解密核心 }
現在針對這個函式做一些說明:
1.emmc
emmc 是目前手機領域流行的儲存裝置,相當於 pc 端的 ssd 硬碟,這裡涉及到一個比較重要的全域性資料 static struct mmc_device *dev:
/* * sdhci host structure, holding information about host * controller parameters */ struct sdhci_host { uint32_t base; /* Base address for the host */ uint32_t cur_clk_rate; /* Running clock rate */ uint32_t timing; /* current timing for the host */ bool tuning_in_progress; /* Tuning is being executed */ uint8_t major; /* host controller minor ver */ uint16_t minor; /* host controller major ver */ bool use_cdclp533; /* Use cdclp533 calibration circuit */ event_t* sdhc_event; /* Event for power control irqs */ struct host_caps caps; /* Host capabilities */ struct sdhci_msm_data *msm_host; /* MSM specific host info */ }; /* mmc card register */ struct mmc_card { uint32_t rca; /* Relative addres of the card*/ uint32_t ocr; /* Operating range of the card*/ uint32_t block_size; /* Block size for the card */ uint32_t wp_grp_size; /* WP group size for the card */ uint64_t capacity; /* card capacity */ uint32_t type; /* Type of the card */ uint32_t status; /* Card status */ uint8_t *ext_csd; /* Ext CSD for the card info */ uint32_t raw_csd[4]; /* Raw CSD for the card */ uint32_t raw_scr[2]; /* SCR for SD card */ uint32_t rpmb_size; /* Size of rpmb partition */ uint32_t rel_wr_count; /* Reliable write count */ struct mmc_cid cid; /* CID structure */ struct mmc_csd csd; /* CSD structure */ struct mmc_sd_scr scr; /* SCR structure */ struct mmc_sd_ssr ssr; /* SSR Register */ }; /* mmc device config data */ struct mmc_config_data { uint8_t slot; /* Sdcc slot used */ uint32_t pwr_irq; /* Power Irq from card to host */ uint32_t sdhc_base; /* Base address for the sdhc */ uint32_t pwrctl_base; /* Base address for power control registers */ uint16_t bus_width; /* Bus width used */ uint32_t max_clk_rate; /* Max clock rate supported */ uint8_t hs200_support; /* SDHC HS200 mode supported or not */ uint8_t hs400_support; /* SDHC HS400 mode supported or not */ uint8_t use_io_switch; /* IO pad switch flag for shared sdc controller */ }; /* mmc device structure */ struct mmc_device { struct sdhci_host host; /* Handle to host controller */ struct mmc_card card; /* Handle to mmc card */ struct mmc_config_data config; /* Handle for the mmc config data */ }; static struct mmc_device *dev;
2.讀取分割槽表
讀取分割槽表,不論是 MBR 還是 GPT 分割槽格式,讀取後都存放在以下結構中:
struct partition_entry {
unsigned char type_guid[PARTITION_TYPE_GUID_SIZE];
unsigned dtype;
unsigned char unique_partition_guid[UNIQUE_PARTITION_GUID_SIZE];
unsigned long long first_lba;
unsigned long long last_lba;
unsigned long long size;
unsigned long long attribute_flag;
unsigned char name[MAX_GPT_NAME_SIZE];
uint8_t lun;
};
struct partition_entry *partition_entries;
3.檢測pwr key
在開啟了長按開機鍵開機的設定後才會生效, 很多平臺預設開啟了此選項,到這裡檢測開機時間不足夠長則關機,按照這裡的程式碼理解,關機後,每次按下開機鍵,系統其實已經啟動到 shutdown_detect 這個位置了,不過由於按鍵時間不長,所以沒有螢幕沒有點亮,系統沒有完全啟動。
apps_init
extern const struct app_descriptor __apps_start;
extern const struct app_descriptor __apps_end;
struct app_descriptor {
const char *name;
app_init init;
app_entry entry;
unsigned int flags;
};
static int app_thread_entry(void *arg)
{
const struct app_descriptor *app = (const struct app_descriptor *)arg;
app->entry(app, NULL);
return 0;
}
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);
}
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);
}
}
}
整個遍歷 app 並啟動的過程並不複雜,有趣的是 __apps_start 和 __apps_end 的定義,這兩個變數符號在所有原始檔中並不存在,而是在 arch/arm/*.ld 連結指令碼中存在,這樣類似的結構在前面 heap_init 中已經遇到過,區別在於 __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 };
下面是 aboot app 宣告的 app 的實際例子:
APP_START(aboot)
.init = aboot_init,
APP_END
巨集展開後的實際效果如下:
struct app_descriptor _app_aboot ____attribute((section(".apps"))) = {.name = "aboot", .init = aboot_init};
這樣通過遍歷 .apps 段就可以獲取 aboot 的描述資訊,呼叫 aboot 的 init 函數了