此系列Uboot啟動分析基于Uboot2021-04。
下面我們從u-boot-spl.lds連結腳本來分析鏡像結構。
連結腳本的開頭定義了兩段記憶體空間,分别定義了sram和sdram的起始位址和長度。在i.MX8中,這兩段定義對應于CPU内部的sram和外部的ddr。
MEMORY { .sram : ORIGIN = IMAGE_TEXT_BASE,
LENGTH = IMAGE_MAX_SIZE }
MEMORY { .sdram : ORIGIN = CONFIG_SPL_BSS_START_ADDR,
LENGTH = CONFIG_SPL_BSS_MAX_SIZE }
對于i.MX8MP來說,這些值定義在include/config/imx8mp_evk.h
CONFIG_SPL_TEXT_BASE=0x920000
#define CONFIG_SPL_MAX_SIZE (152 * 1024)
#define CONFIG_SPL_BSS_START_ADDR 0x96e000
#define CONFIG_SPL_BSS_MAX_SIZE SZ_8K /* 8 KB */
可對于i.MX8MP來說其定義了spl-uboot兩段空間,一段是從0x920000開始的152K空間,這段空間是内部RAM中的一段。而0x96e000開始的8K空間則是用來存放未初始化的全局變量和未初始化的靜态局部變量的BSS資料段,位于外部存儲即SDRAM如DDR上。
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
然後就是指定輸出的格式,對于Armv8來說,預設輸出的格式就是小端aarch64。Entry用于指定指定入口位址,注意這裡使用的是代碼中定義的_start符号,該符号定義在start.S彙編檔案 最上方,也就是整個SPL-UBOOT的入口,後面會較長的描述這部分的内容。
.text : {
. = ALIGN(8);
*(.__image_copy_start)
CPUDIR/start.o (.text*)
*(.text*)
} >.sram
上面這段定義了一段代碼段,并訓示連結器将其放入到上面定義的.sram記憶體區域中。其中.ALIGN(8)說明首位址8位元組對齊。随後緊接着是存放.__image_copy_start段。
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
從上面這段代碼可以看到,定義了一個0長度也就是不占存儲空間的字元數組,并将其規定放置到.__image_copy_start段中,該段位于.text段之前。然後便是start.o的代碼段以及所有其他檔案的.text段。這裡之是以要将start.o的代碼段單獨拎出來,主要的目的在于確定start.s檔案編譯後的代碼段位于最終生成spl-uboot檔案代碼段的最前面。
.rodata : {
. = ALIGN(8);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
} >.sram
.rodata段用于存放隻讀資料段。通常,連結器會把比對的檔案和段按照發現的順序放置,此外可以使用關鍵字按照一定規則進行順序修改。其中SORT_BY_ALIGNMENT對段的對齊需求使用降序方式排序放入輸出檔案中,大的對齊被放在小的對齊前面,這樣可以減少為了對齊需要的額外空間。而SORT_BY_NAME關鍵字則會讓連結器将檔案或者段的名字按照上升順序排序後放入輸出檔案。這裡SORT_BY_ALIGNMENT(SORT_BY_NAME(wildcard section pattern)). 先按對齊方式排,再按名字排。
.data : {
. = ALIGN(8);
*(.data*)
} >.sram
.data資料段主要用于存放全局已初始化的資料段和已初始化的局部靜态變量。
.u_boot_list : {
. = ALIGN(8);
KEEP(*(SORT(.u_boot_list*)));
} >.sram
u_boot_list段用于存放所有的uboot指令,後面在SPL将uboot搬到外部sdram時,注冊的搬運函數方法也是存放在這個段裡面,我們暫時隻看一下添加uboot指令宏具體是怎麼展開的。
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))
比如bootm指令,
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);
具體展開:
cmd_tbl_t _u_boot_list_2_cmd_2_bootm __aligned(4) __attribute__((unused, \
section(".u_boot_list_2_cmd_2_bootm))) = {"bootm", CONFIG_SYS_MAXARGS, 1, do_bootm, "boot application image from memory", bootm_help_text, NULL}
tips:##表示連接配接符 #表示轉換為字元串
從上面可以看到實際是定義了一個cmd_tbl_t 結構體變量,并将該變量存放在.u_boot_list_2_cmd_2_bootm段中也就是上面連結腳本指定的u_boot_list段處。
.image_copy_end : {
. = ALIGN(8);
*(.__image_copy_end)
} >.sram
.end : {
. = ALIGN(8);
*(.__end)
} >.sram
_image_binary_end = .;
.bss_start : {
. = ALIGN(8);
KEEP(*(.__bss_start));
} >.sdram
.bss : {
*(.bss*)
. = ALIGN(8);
} >.sdram
.bss_end : {
KEEP(*(.__bss_end));
} >.sdram
/DISCARD/ : { *(.dynsym) }
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }