在进行源码分析之前,首先看看u-boot的链接脚本,通过链接脚本可以从整体了解一个u-boot的组成,并且可以在启动分析中知道某些逻辑是在完成什么工作。
在armv8中,u-boot使用arch/arm/cpu/armv8/u-boot.lds进行链接。
u-boot-spl和u-boot-tpl使用arch/arm/cpu/armv8/u-boot-spl.lds进行链接,因为每个board的情况可能不同,所以u-boot可以通过Kconfig来自定义u-boot-spl.lds和u-boot-tpl.lds。
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* (C) Copyright 2013
* David Feng < fenghua@phytium.com.cn >
*
* (C) Copyright 2002
* Gary Jennejohn, DENX Software Engineering, < garyj@denx.de >
*/
#include < config.h >
#include < asm/psci.h >
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start) -------------------------------------------------------------------- (1)
/*
*(1)首先定义了二进制程序的输出格式为"elf64-littleaarch64",
* 架构是"aarch64",程序入口为"_start"符号;
*/
SECTIONS
{
#ifdef CONFIG_ARMV8_SECURE_BASE -------------------------------------------------- (2)
/*
*(2)ARMV8_SECURE_BASE是u-boot对PSCI的支持,在定义时可以将PSCI的文本段,
* 数据段,堆栈段重定向到指定的内存,而不是内嵌到u-boot中。
* 不过一般厂商实现会使用atf方式使其与bootloader分离,这个功能不常用;
*/
/DISCARD/ : { *(.rela._secure*) }
#endif
. = 0x00000000; -------------------------------------------------------------- (3)
/*
*(3)定义了程序链接的基地址,默认是0,通过配置CONFIG_SYS_TEXT_BASE可修改
* 这个默认值。
*/
. = ALIGN(8);
.text :
{
*(.__image_copy_start) --------------------------------------------------- (4)
/*
*(4)__image_copy_start和__image_copy_end用于定义需要重定向的段,
* u-boot是一个分为重定向前初始化和重定向后初始化的bootloader,
* 所以此处会定义在完成重定向前初始化后需要搬运到ddr中数据的起始地址和结束地址;
*
* 大多数时候u-boot是运行在受限的sram或者只读的flash上,
* u-boot为了启动流程统一会在ddr未初始化和重定位之前不去访问全局变量,
* 但是又为了保证u-boot能够正常读写全局变量,内存,调用各类驱动能力,
* 所以u-boot将启动初始化分为了两个部分,重定向前初始化board_f和
* 重定向后初始化 board_r,在重定向之前完成一些必要初始化,
* 包括可能的ddr初始化,然后通过__image_copy_start和__image_copy_end
* 将u-boot搬运到ddr中,并在ddr中进行重定向后初始化,这个时候的u-boot就可以
* 正常访问全局变量等信息了。
*
* 如果想要在board_f过程中读写一些全局变量信息该怎么办呢?
* u-boot通过定义global_data(gd)来完成此功能,
* 后续在分析到时会详细讲解实现方式。
*/
CPUDIR/start.o (.text*) -------------------------------------------------- (5)
/*
*(5)定义了链接程序的头部文本段,armv8就是
* arch/arm/cpu/armv8/start.S,
* start.S中所有文本段将会链接到此段中并且段入口符号就是_start;
*/
}
/* This needs to come before *(.text*) */
.efi_runtime : { ------------------------------------------------------------ (6)
/*
*(6)在定义了efi运行时相关支持时才会出现使用的段,一般不用关心;
*/
__efi_runtime_start = .;
*(.text.efi_runtime*)
*(.rodata.efi_runtime*)
*(.data.efi_runtime*)
__efi_runtime_stop = .;
}
.text_rest : ---------------------------------------------------------------- (7)
/*
*(7)除了start.o,其他的所有文本段将会链接到此段中;
*/
{
*(.text*)
}
#ifdef CONFIG_ARMV8_PSCI -------------------------------------------------------- (8)
/*
*(8)同(2),是PSCI相关功能的支持,一般不会使用;
*/
.__secure_start :
#ifndef CONFIG_ARMV8_SECURE_BASE
ALIGN(CONSTANT(COMMONPAGESIZE))
#endif
{
KEEP(*(.__secure_start))
}
#ifndef CONFIG_ARMV8_SECURE_BASE
#define CONFIG_ARMV8_SECURE_BASE
#define __ARMV8_PSCI_STACK_IN_RAM
#endif
.secure_text CONFIG_ARMV8_SECURE_BASE :
AT(ADDR(.__secure_start) + SIZEOF(.__secure_start))
{
*(._secure.text)
. = ALIGN(8);
__secure_svc_tbl_start = .;
KEEP(*(._secure_svc_tbl_entries))
__secure_svc_tbl_end = .;
}
.secure_data : AT(LOADADDR(.secure_text) + SIZEOF(.secure_text))
{
*(._secure.data)
}
.secure_stack ALIGN(ADDR(.secure_data) + SIZEOF(.secure_data),
CONSTANT(COMMONPAGESIZE)) (NOLOAD) :
#ifdef __ARMV8_PSCI_STACK_IN_RAM
AT(ADDR(.secure_stack))
#else
AT(LOADADDR(.secure_data) + SIZEOF(.secure_data))
#endif
{
KEEP(*(.__secure_stack_start))
. = . + CONFIG_ARMV8_PSCI_NR_CPUS * ARM_PSCI_STACK_SIZE;
. = ALIGN(CONSTANT(COMMONPAGESIZE));
KEEP(*(.__secure_stack_end))
}
#ifndef __ARMV8_PSCI_STACK_IN_RAM
. = LOADADDR(.secure_stack);
#endif
.__secure_end : AT(ADDR(.__secure_end)) {
KEEP(*(.__secure_end))
LONG(0x1d1071c); /* Must output something to reset LMA */
}
#endif
. = ALIGN(8);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } ------------------- (9)
/*
*(9)所有仅读数据将会在这个段中对齐排序存放好;
*/
. = ALIGN(8);
.data : { -------------------------------------------------------------------- (10)
/*
*(10)所有数据段将会链接到此段中;
*/
*(.data*)
}
. = ALIGN(8);
. = .;
. = ALIGN(8);
.u_boot_list : { ------------------------------------------------------------- (11)
/*
*(11)u_boot_list段定义了系统中当前支持的所有命令和设备驱动,此段把散落在各个文件中
* 通过U_BOOT_CMD的一系列拓展宏定义的命令和U_BOOT_DRIVER的拓展宏定义的设备驱动收集到一起,
* 并按照名字排序存放,以便后续在命令行快速检索到命令并执行和检测注册的设备和设备树匹配
* probe设备驱动初始化;(设备驱动的probe只在定义了dm模块化驱动时有效)
*/
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(8);
.efi_runtime_rel : {
__efi_runtime_rel_start = .;
*(.rel*.efi_runtime)
*(.rel*.efi_runtime.*)
__efi_runtime_rel_stop = .;
}
. = ALIGN(8);
.image_copy_end :
{
*(.__image_copy_end)
}
. = ALIGN(8);
.rel_dyn_start : -------------------------------------------------------- (12)
/*
*(12)一般u-boot运行时是根据定义的基地址开始执行,如果加载地址和链接地址
* 不一致则会出现不能执行u-boot的问题。通过一个
* 配置CONFIG_POSITION_INDEPENDENT即可打开地址无关功能,
* 此选项会在链接u-boot时添加-PIE参数。此参数会在u-boot ELF文件中
* 生成rela*段,u-boot通过读取此段中表的相对地址值与实际运行时地址值
* 依次遍历进行修复当前所有需要重定向地址,使其可以实现地址无关运行;
* 即无论链接基地址如何定义,u-boot也可以在任意ram地址
* 运行(一般需要满足最低4K或者64K地址对齐);
*
* 注意此功能只能在sram上实现,因为此功能会在运行时修改文本段数据段中的地址,
* 如果此时运行在片上flash,则不能写flash,导致功能失效无法实现地址无关;
*/
{
*(.__rel_dyn_start)
}
.rela.dyn : {
*(.rela*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
_end = .;
. = ALIGN(8);
.bss_start : { -------------------------------------------------------- (13)
/*
*(13)众所周知的bbs段;
*/
KEEP(*(.__bss_start));
}
.bss : {
*(.bss*)
. = ALIGN(8);
}
.bss_end : {
KEEP(*(.__bss_end));
}
/DISCARD/ : { *(.dynsym) } -------------------------------------------- (14)
/*
*(14)一些在链接时无用需要丢弃的段;
*/
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
#ifdef CONFIG_LINUX_KERNEL_IMAGE_HEADER ----------------------------------- (15)
/*
*(15)在efi加载时会很有用,主要在u-boot的二进制头部添加了一些头部信息,
* 包括大小端,数据段文本段大小等,以便于efi相关的加载器读取信息,
* 此头部信息来自于Linux arm64的Image的头部信息;该头部也不属于u-boot的
* 一部分只是被附加上去的;
*/
#include "linux-kernel-image-header-vars.h"
#endif
}
此链接脚本是标准的spl链接脚本,还包含了u_boot_list段,如果对应自己board不需要命令行或者模块化驱动设备,只作为一个加载器则可以自定义更简略的链接脚本。
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* (C) Copyright 2013
* David Feng < fenghua@phytium.com.cn >
*
* (C) Copyright 2002
* Gary Jennejohn, DENX Software Engineering, < garyj@denx.de >
*
* (C) Copyright 2010
* Texas Instruments, < www.ti.com >
* Aneesh V < aneesh@ti.com >
*/
MEMORY { .sram : ORIGIN = IMAGE_TEXT_BASE, ---------------------------------------- (1)
/*
*(1) >XXX 的形式可以将指定段放入XXX规定的内存中;一般u-boot-spl只有
* 很小的可运行内存块,所以spl中会舍去大量不需要用的段只保留关键的
* 文本段数据段等,并且通过 >.sram的形式将不在ddr初始化前用到的段定义到sdram中,
* 后续只需在完成ddr初始化后将这些段搬运到ddr中即可,而不需要额外的
* 地址修复逻辑,如下:有一个sram 0x18000-0x19000,
* 一个sdram 0x80000000 - 0x90000000,
* 那么通过 >.sram方式则map文件可能如下:
* 0x18000 stext
* ...
* 0x18100 sdata
* ...
* 0x80000000 sbss
* ...
*/
LENGTH = IMAGE_MAX_SIZE }
MEMORY { .sdram : ORIGIN = CONFIG_SPL_BSS_START_ADDR,
LENGTH = CONFIG_SPL_BSS_MAX_SIZE }
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start) -------------------------------------------------------------------- (2)
/*
*(2)同u-boot.lds一致,共用一套逻辑入口_start;
*/
SECTIONS
{
.text : {
. = ALIGN(8);
*(.__image_copy_start) -------------------------------------------------- (3)
/*
*(3)同样的,如果spl需要重定向则会使用此段定义,大多数情况下spl中会用上重定向;
*/
CPUDIR/start.o (.text*)
*(.text*)
} >.sram
.rodata : {
. = ALIGN(8);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
} >.sram
.data : {
. = ALIGN(8);
*(.data*)
} >.sram
#ifdef CONFIG_SPL_RECOVER_DATA_SECTION ---------------------------------------- (4)
/*
*(4)SPL_RECOVER_DATA_SECTION段用于保存数据段数据,
* 一些board在初始化时修改data段数据,并在后续某个阶段
* 从此段中恢复data的原始数据;
*/
.data_save : {
*(.__data_save_start)
. = SIZEOF(.data);
*(.__data_save_end)
} >.sram
#endif
.u_boot_list : {
. = ALIGN(8);
KEEP(*(SORT(.u_boot_list*)));
} >.sram
.image_copy_end : {
. = ALIGN(8);
*(.__image_copy_end)
} >.sram
.end : {
. = ALIGN(8);
*(.__end)
} >.sram
_image_binary_end = .;
.bss_start (NOLOAD) : {
. = ALIGN(8);
KEEP(*(.__bss_start));
} >.sdram -------------------------------------------------------------- (5)
/*
*(5)将bss段数据定义到 >.sdram中,即可在初始化ddr后直接对此段地址清零
* 即可使用全局未初始化变量,并且不会带来副作用。
*/
.bss (NOLOAD) : {
*(.bss*)
. = ALIGN(8);
} >.sdram
.bss_end (NOLOAD) : {
KEEP(*(.__bss_end));
} >.sdram
/DISCARD/ : { *(.rela*) }
/DISCARD/ : { *(.dynsym) }
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}
从上述的链接脚本可以看出,armv8的u-boot的启动是从arch/arm/cpu/armv8/start.S中的_start开始的 ,并在后续初始化中调用了很多链接脚本中定义的地址符号表。
全部0条评论
快来发表一下你的评论吧 !