1. 程式人生 > >Linux啟動流程_LK流程_bootstrap2(1)

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 函數了