u-boot啟動流程分析(2)_板級(board)部分

1. 前言

書接上文(u-boot啟動流程分析(1)_平臺相關部分),本文介紹u-boot啟動流程中和具體版型(board)有關的部分,也即board_init_f/board_init_r所代表的、board有關初始化過程。該過程將持續u-boot的整個生命周期,直到main_loop(即傳說中的命令行)。

注1:由于u-boot后初始化過程,基本上涉及到了所有的軟件模塊,因此本文不能一一分析,基本原則就是撿看著順眼的、熟的下手了~。

2. Generic Board

我們在“u-boot啟動流程分析(1)_平臺相關部分”中,介紹過board_init_f接口,并在“X-003-UBOOT-基于Bubblegum-96平臺的u-boot移植說明”中,通過在SPL image中的board_init_f點亮了一個LED燈。

u-boot的基本策略,就是聲明一系列的API(如low_level_init、board_init_f、board_init_r等等),并在u-boot的核心邏輯中調用它們。平臺的移植和開發者,所需要做的,就是根據實際情況,實現它們。

與此同時,為了減少開發的工作量,u-boot為大部分API提供了通用實現(一般通過CONFIG配置項或者若定義去控制是否編譯)。以board_init_f和board_init_r兩個板級的初始化接口為例,u-boot分別在common/board_f.ccommon/board_r.c兩個文件中提供了通用實現。查看common/Makefile可知:

ifndef CONFIG_SPL_BUILD

# boards

obj-$(CONFIG_SYS_GENERIC_BOARD) += board_f.o

obj-$(CONFIG_SYS_GENERIC_BOARD) += board_r.o

endif # !CONFIG_SPL_BUILD

這兩個通用接口是由CONFIG_SYS_GENERIC_BOARD配置項控制的,并且只會在u-boot image中被編譯。再通過arch/Kconfig中ARM平臺有關的配置可知:

config ARM

bool "ARM architecture"

select CREATE_ARCH_SYMLINK

select HAVE_PRIVATE_LIBGCC if !ARM64

select HAVE_GENERIC_BOARD

select SYS_GENERIC_BOARD

select SUPPORT_OF_CONTROL

ARM平臺自動使能了CONFIG_SYS_GENERIC_BOARD配置,因此u-boot image有關的板級啟動流程,是由Generic Board的代碼實現的,具體可參考后續的分析。

注2:由“u-boot啟動流程分析(1)_平臺相關部分”中的分析可知,SPL image的聲明周期,在自定義的board_init_f執行完成后,便結束了,因此本文將只針對u-boot image。

3. _main

接著“u-boot啟動流程分析(1)_平臺相關部分”的表述,我們從_main函數重新分析(只不過此時不再是SPL build,代碼不再貼出)。執行過程如下:

1)設置初始的堆棧

基址由CONFIG_SYS_INIT_SP_ADDR定義。

2)分配global data所需的空間

將堆棧16 bits對齊之后,調用board_init_f_alloc_reserve接口,從堆棧開始的地方,為u-boot的global data(struct global_data)分配空間。如下:

/* common/init/board_init.c */

ulong board_init_f_alloc_reserve(ulong top)

{

/* Reserve early malloc arena */

#if defined(CONFIG_SYS_MALLOC_F)

top -= CONFIG_SYS_MALLOC_F_LEN;

#endif

/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */

top = rounddown(top-sizeof(struct global_data), 16);

return top;

}

需要注意的是,如果定義了CONFIG_SYS_MALLOC_F_LEN,則會先預留出early malloc所需的空間。

3)初始化global data

global data的空間分配后,調用board_init_f_init_reserve,初始化global data。所謂的初始化,無非就是一些清零操作,不過有幾個地方需要注意:

1)如果不是ARM平臺(!CONFIG_ARM),則可以調用arch_setup_gd接口,進行arch級別的設置。當然,前提是,對應的arch應該實現這個接口。

2)如果定義了CONFIG_SYS_MALLOC_F,則會初始化gd->malloc_base。

4)執行前置的(front)初始化操作

調用board_init_f接口,執行前置的初始化操作,會再后面的章節詳細說明。

5)執行relocation操作,后面會詳細說明。

6)清除BBS段

7)執行后置的(rear)初始化操作

調用board_init_r接口,執行前置的初始化操作,會再后面的章節詳細說明。

4. global data介紹以及背后的思考

4.1 背景知識

要理解global data的意義,需要先理解如下的事實:

u-boot是一個bootloader,有些情況下,它可能位于系統的只讀存儲器(ROM或者flash)中,并從那里開始執行。

因此,這種情況下,在u-boot執行的前期(在將自己copy到可讀寫的存儲器之前),它所在的存儲空間,是不可寫的,這會有兩個問題:

1)堆棧無法使用,無法執行函數調用,也即C環境不可用。

2)沒有data段(或者正確初始化的data段)可用,不同函數或者代碼之間,無法通過全局變量的形式共享數據。

對于問題1,通常的解決方案是:

u-boot運行起來之后,在那些不需要執行任何初始化動作即可使用的、可讀寫的存儲區域,開辟一段堆棧(stack)空間。

一般來說,大部分的平臺(如很多ARM平臺),都有自己的SRAM,可用作堆棧空間。如果實在不行,也有可借用CPU的data cache的方法(不再過多說明)。

對于問題2,解決方案要稍微復雜一些:

首先,對于開發者來說,在u-boot被拷貝到可讀寫的RAM(這個動作稱作relocation)之前,永遠不要使用全局變量。

其次,在relocation之前,不同模塊之間,確實有通過全局變量的形式傳遞數據的需求。怎么辦?這就是global data需要解決的事情。

4.2 global data

為了在relocation前通過全局變量的形式傳遞數據,u-boot設計了一個巧妙的方法:

1)定義一個struct global_data類型的數據結構,里面保存了各色各樣需要傳遞的數據

該數據結構的具體內容,后面用到的時候再一個一個解釋,這里不再詳細介紹。具體可參考:include/asm-generic/global_data.h

2)堆棧配置好之后,在堆棧開始的位置,為struct global_data預留空間(可參考第3章中相關的說明),并將開始地址(就是一個struct global_data指針)保存在一個寄存器中,后續的傳遞,都是通過保存在寄存器中的指針實現

對arm64平臺來說,該指針保存在了X18寄存器中,如下:

/*https://github.com/wowotechX/u-boot/blob/x_integration/arch/arm/lib/crt0_64.S*/

bl????? board_init_f_alloc_reserve

mov???? sp, x0

/* set up gd here, outside any C code */

mov???? x18, x0

bl????? board_init_f_init_reserve

上面board_init_f_alloc_reserve的返回值(x0)就是global data的指針。

/* arch/arm/include/asm/global_data.h */

#ifdef __clang__

#define DECLARE_GLOBAL_DATA_PTR

#define gd????? get_gd()

static inline gd_t *get_gd(void)

{

gd_t *gd_ptr;

#ifdef CONFIG_ARM64

__asm__ volatile("mov %0, x18\n" : "=r" (gd_ptr));

#else

}

#else

#ifdef CONFIG_ARM64

#define DECLARE_GLOBAL_DATA_PTR???????? register volatile gd_t *gd asm ("x18")

#else

#endif

5. 前置的板級初始化操作

global data準備好之后,u-boot會執行前置的板級初始化動作,即board_init_f。所謂的前置的初始化動作,主要是relocation之前的初始化操作,也就是說:

執行board_init_f的時候,u-boot很有可能還在只讀的存儲器中。大家記住這一點就可以了!

注3:大家可能會覺得這里的f(front?)和r(rear?)的命名有點奇怪,我猜這個軟件工程師應該是車迷,是不是借用了前驅和后驅的概念?不得而知啊。

對于ARM等平臺來說,u-boot提供了一個通用的board_init_f接口,該接口使用u-boot慣用的設計思路:

u-boot將需要在board_init_f中初始化的內容,抽象為一系列API。這些API由u-boot聲明,由平臺的開發者根據實際情況實現。具體可參考本章后續的描述。

5.1 board_init_f

位于common/board_f.c中的board_init_f接口的實現非常簡單,如下(省略了一些無用代碼):

void board_init_f(ulong boot_flags)

{

gd->flags = boot_flags;

gd->have_console = 0;

if (initcall_run_list(init_sequence_f))

hang();

}

對global data進行簡單的初始化之后,調用位于init_sequence_f數組中的各種初始化API,進行各式各樣的初始化動作。后面將會簡單介紹一些和ARM平臺有關的、和平臺的移植工作有關的、比較重要的API。其它API,大家可以參考source code自行理解。

注4:下面紅色字體標注的API,是u-boot移植時有很大可能需要的API,大家留意就是。

5.2 fdtdec_setup

#ifdef CONFIG_OF_CONTROL

fdtdec_setup,

#endif

如果打開了CONFIG_OF_CONTROL,則調用fdtdec_setup,配置gd->fdt_blob指針(即device tree所在的存儲位置)。對ARM平臺來說,u-boot的Makefile會通過連接腳本,將dtb文件打包到u-boot image的“__dtb_dt_begin”位置處,因此不需要特別關心。

5.3 trace_early_init

#ifdef CONFIG_TRACE

trace_early_init,

#endif

由CONFIG_TRACE配置項控制,暫且不用關注,后面用到的時候再分析。

5.4 initf_malloc

如果定義了CONFIG_SYS_MALLOC_F_LEN,則調用initf_malloc,初始化malloc有關的global data,如gd->malloc_limit、gd->malloc_ptr。

5.5 arch_cpu_init

cpu級別的初始化操作,可以在需要的時候由CPU有關的code實現。

5.6 initf_dm

driver model有關的初始化操作。如果定義了CONFIG_DM,則調用dm_init_and_scan初始化并掃描系統所有的device。如果定義了CONFIG_TIMER_EARLY,調用dm_timer_init初始化driver model所需的timer。

5.7 board_early_init_f

#if defined(CONFIG_BOARD_EARLY_INIT_F)

board_early_init_f,

#endif

如果定義CONFIG_BOARD_EARLY_INIT_F,則調用board_early_init_f接口,執行板級的early初始化。平臺的開發者可以根據需要,實現board_early_init_f接口,以完成特定的功能。

5.8 timer_init

初始化系統的timer。

該接口應該由平臺或者板級的代碼實現,初始化成功后,u-boot會通過其它的API獲取當前的timestamp,后面用到的時候再詳細介紹。

5.9 get_clocks

獲取當前CPU和BUS的時鐘頻率,并保存在global data中:

gd->cpu_clk

gd->bus_clk

5.10 env_init

初始化環境變量有關的邏輯,不需要特別關注。

5.11 init_baud_rate

gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);

獲取當前使用串口波特率,可以有兩個途徑(優先級從高到低):從"baudrate"中獲??;從CONFIG_BAUDRATE配置項獲取。

5.12 serial_init

初始化serial,包括u-boot serial core以及具體的serial driver。該函數執行后,系統的串口(特別是用于控制臺的)已經可用。

5.13 console_init_f

/* Called before relocation - use serial functions */

int console_init_f(void)

{

gd->have_console = 1;

#ifdef CONFIG_SILENT_CONSOLE

if (getenv("silent") != NULL)

gd->flags |= GD_FLG_SILENT;

#endif

print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);

return 0;

}

初始化系統的控制臺,之后串口輸出可用。大家可留意CONFIG_SILENT_CONSOLE配置項,如果使能,可以通過“silent”環境變量,控制u-boot的控制臺是否輸出。

5.14? fdtdec_prepare_fdt

#ifdef CONFIG_OF_CONTROL

fdtdec_prepare_fdt,

#endif

如果定義了CONFIG_OF_CONTROL,調用fdtdec_prepare_fdt接口,準備device tree有關的內容。后續device tree的分析文章會詳細介紹。

5.15 display_options/display_text_info/print_cpuinfo/show_board_info

通過控制臺,顯示一些信息,可用于debug。

5.16 misc_init_f

#if defined(CONFIG_MISC_INIT_F)

misc_init_f,

#endif

如果使能了CONFIG_MISC_INIT_F,則調用misc_init_f執行misc driver有關的初始化。

5.17 init_func_i2c

#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)

init_func_i2c,

#endif

如果使能了CONFIG_HARD_I2C或者CONFIG_SYS_I2C,則調用init_func_i2c執行i2c driver有關的初始化。

5.18 init_func_spi

#if defined(CONFIG_HARD_SPI)

init_func_spi,

#endif

如果使能了CONFIG_HARD_SPI,則調用init_func_spi執行spi driver有關的初始化。

5.19 announce_dram_init

宣布我們要進行DDR的初始化動作了(其實就是一行打印)。

5.20 dram_init

#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \

defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)

dram_init,????????????? /* configure available RAM banks */

#endif

調用dram_init接口,初始化系統的DDR。dram_init應該由平臺相關的代碼實現。

如果DDR在SPL中已經初始化過了,則不需要重新初始化,只需要把DDR信息保存在global data中即可,例如:

gd->ram_size = …

5.21 testdram

#if defined(CONFIG_SYS_DRAM_TEST)

testdram,

#endif /* CONFIG_SYS_DRAM_TEST */

如果定義了CONFIG_SYS_DRAM_TEST,則會調用testdram執行DDR的測試操作。可以在開發階段打開,系統穩定后關閉。

5.22 DRAM空間的分配

DRAM初始化完成后,就可以著手規劃u-boot需要使用的部分,如下圖:


圖片1 u-boot中DRAM的使用情況

總結如下:

1)考慮到后續的kernel是在RAM的低端位置解壓縮并執行的,為了避免麻煩,u-boot將使用DRAM的頂端地址,即gd->ram_top所代表的位置。其中gd->ram_top是由setup_dest_addr函數配置的。

2)u-boot所使用的DRAM,主要分為三類:各種特殊功能所需的空間,如log buffer、MMU page table、LCD fb buffer、trace buffer、等等;u-boot的代碼段、數據段、BSS段所占用的空間(就是u-boot relocate之后的執行空間),由gd->relocaddr標示;堆棧空間,從gd->start_addr_sp處遞減。

3)特殊功能以及u-boot所需空間,是由reserve_xxx系列函數保留的,具體可參考source code,這里不再詳細分析。

4)reserve空間分配完畢后,堆棧緊隨其后,遞減即可。

5.23 setup_dram_config

調用dram_init_banksize接口(由具體的平臺代碼實現),初始化DDR的bank信息。

5.24 reloc_fdt

如果沒有定義CONFIG_OF_EMBED,則先將device tree拷貝到圖片1 new_fdt所在的位置,也就是device tree的relocation操作。

5.25 setup_reloc

計算relocation有關的信息,主要是 gd->reloc_off,計算公式如下:

gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;

其中CONFIG_SYS_TEXT_BASE是u-boot relocation之前在(只讀)memory的位置(也是編譯時指定的位置),gd->relocaddr是relocation之后的位置,因此gd->reloc_off代表u-boot relocation操作之后的偏移量,后面relocation時會用到。

同時,該函數順便把global data拷貝到了圖片1所示的“new global data”處,其實就是global data的relocation。

6. u-boot的relocation

前面講過,u-boot是有可能在只讀的memory中啟動的。簡單起見,u-boot假定所有的啟動都是這樣,因此u-boot的啟動邏輯,都是針對這種情況設計的。在這種情況下,基于如下考慮:

1)只讀memory中執行,代碼需要小心編寫(不能使用全局變量,等等)。

2)只讀memory執行速度通常比較慢。

u-boot需要在某一個時間點,將自己從“只讀memory”中,拷貝到可讀寫的memory(如SDRAM,后面統稱RAM,注意和SRAM區分,不要理解錯了)中繼續執行,這就是relocation(重定位)操作。

relocation的時間點,可以是“系統可讀寫memory始化完成之后“的任何時間點。根據u-boot當前的代碼邏輯,是在board_init_f執行完成之后,因為board_init_f中完成了很多relocation有關的準備動作,具體可參考第5章的描述。

u-boot relocation的代碼如下(以arm64為例):

/*https://github.com/wowotechX/u-boot/blob/x_integration/arch/arm/lib/crt0_64.S*/

ldr???? x0, [x18, #GD_START_ADDR_SP]??? /* x0 <- gd->start_addr_sp */

bic???? sp, x0, #0xf??? /* 16-byte alignment for ABI compliance */

ldr???? x18, [x18, #GD_BD]????????????? /* x18 <- gd->bd */

sub???? x18, x18, #GD_SIZE????????????? /* new GD is below bd */

adr???? lr, relocation_return

ldr???? x9, [x18, #GD_RELOC_OFF]??????? /* x9 <- gd->reloc_off */

add???? lr, lr, x9????? /* new return address after relocation */

ldr???? x0, [x18, #GD_RELOCADDR]??????? /* x0 <- gd->relocaddr */

b?????? relocate_code

relocation_return:

邏輯比較簡單:

1)從global data中取出relocation之后的堆?;?,16-byte對齊后,保存到sp中。

2)將新的global data的指針,保存在x18寄存器中。

3)計算relocation之后的執行地址(relocation_return處),計算的方法就是當前的relocation_return位置加上gd->reloc_off。

4)以relocation的目的地址(gd->relocaddr)為參數,調用relocate_code執行實際的relocation動作,就是將u-boot的代碼段、data段、bss段等數據,拷貝到新的位置(gd->relocaddr)。

7. 后置的板級初始化操作

relocate完成之后,真正的C運行環境才算建立了起來,接下來會執行“后置的板級初始化操作”,即board_init_r函數。board_init_r和board_init_f的設計思路基本一樣,也有一個很長的初始化序列----init_sequence_r,該序列中包含如下的初始化函數(邏輯比較簡單,這里不再涉及細節,權當列出index吧):

注5:老規矩,紅色字體標注的函數是比較重要的函數。

1)initr_trace,初始化并使能u-boot的tracing system,涉及的配置項有CONFIG_TRACE。

2)initr_reloc,設置relocation完成的標志。

3)initr_caches,使能dcache、icache等,涉及的配置項有CONFIG_ARM。

4)initr_malloc,malloc有關的初始化。

5)initr_dm,relocate之后,重新初始化DM,涉及的配置項有CONFIG_DM。

6)board_init,具體的板級初始化,需要由board代碼根據需要實現,涉及的配置項有CONFIG_ARM。

7)set_cpu_clk_info,Initialize clock framework,涉及的配置項有CONFIG_CLOCKS。

8)initr_serial,重新初始化串口(不太明白什么意思)。

9)initr_announce,宣布已經在RAM中執行,會打印relocate后的地址。

10)board_early_init_r,由板級代碼實現,涉及的配置項有CONFIG_BOARD_EARLY_INIT_R。

11)arch_early_init_r,由arch代碼實現,涉及的配置項有CONFIG_ARCH_EARLY_INIT_R。

12)power_init_board,板級的power init代碼,由板級代碼實現,例如hold住power。

13)initr_flash、initr_nand、initr_onenand、initr_mmc、initr_dataflash,各種flash設備的初始化。

14)initr_env,環境變量有關的初始化。

15)initr_secondary_cpu,初始化其它的CPU core。

16)stdio_add_devices,各種輸入輸出設備的初始化,如LCD driver等。

17)interrupt_init,中斷有關的初始化。

18)initr_enable_interrupts,使能系統的中斷,涉及的配置項有CONFIG_ARM(ARM平臺u-boot實在開中斷的情況下運行的)。

19)initr_status_led,狀態指示LED的初始化,涉及的配置項有CONFIG_STATUS_LED、STATUS_LED_BOOT。

20)initr_ethaddr,Ethernet的初始化,涉及的配置項有CONFIG_CMD_NET。

21)board_late_init,由板級代碼實現,涉及的配置項有CONFIG_BOARD_LATE_INIT。

22)等等…

23)run_main_loop/main_loop,執行到main_loop,開始命令行操作。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容