u-boot的relocate是新版本u-boot开始有的一个功能,是指u-boot的重定向动作,将自身拷贝到sdram上的另外一个位置的动作。

新版u-boot跟老版u-boot不太一样的地方,新版u-boot不管u-boot的加载地址在哪里,启动后会计算出一个靠近sdram顶端的地址,将自身代码拷贝到该地址,继续运行。

rerocate介绍

relocate是什么

u-boot的relocate操作,是指u-boot的重定向动作

uboot自身镜像(拷贝)到内存中另外一个位置的动作

为什么需要relocate

考虑的问题:

  • 绝大部分嵌入式处理器都是哈佛结构,数据空间和程序空间独立编址;
    u-boot固件一般存放在内部norflash,或者上电时自动映射nandflash、MMC等外部存储设备到iROM;
    数据变量一般使用内部iRAM,或者内部Cache等,一般iRAM等都很小,运行速度慢,也不能满足复杂程序的运行需求;所以,在DDR, SDRAM等初始化完成之后,u-boot将自身relocate到DDR,SDRAM中去运行;

  • 考虑到后续的kernel是在DDR SDRAM的低端位置解压缩并执行的,为了避免麻烦,u-boot将使用DRAM的顶端地址,即gd->ram_top所代表的位置;

  • 考虑到u-boot升级时,relocate之后,让从u-boot中升级u-boot成为可能,不然如果u-boot还是从norflash等中加载程序到iRAM中运行,当升级写入新u-boot固件到norflash中时,会导致程序cash掉,因为在运行时程序文件被修改覆盖;

    REF: why u-boot relocate it self to RAM from

SPL和relocate的关系

SPL的出现,是u-boot为了解决一些处理器,它的iROM很小,没有办法存储完整的u-boot固件。

所以使用SPL处理一些紧急的必须的初始化操作(大部分由汇编完成,少量c代码),初始化内存(SDRAM,DDR等),然后负责将u-boot拷贝到内存中,跳转到内存中运行。

那么使用SPL之后是不是就不需要relocate了呢?

按照我的理解是可以不需要的,不知道对不对?

假定SPL拷贝u-boot的目标地址和relocate的目标地址一样,这样的话,我的理解是不需要的。

那么看u-boot的代码,还是进行relocate的。在u-boot的代码框架里,SPL和u-boot是两个独立运行的程序,虽然他们共享源码的结构,通过宏来区分。我的理解u-boot的设计是为了兼容大部分处理器,所以这里还是进行了relocate,即因为不知道SPL拷贝到内存的地址是否是符合u-boot要求的。

这样看来SPL和relocate是为了解决不同的问题,他们既有一定的联系,又是相对比较独立的。

新版本的u-boot,无论是否拷贝,都将u-boot relocate到sdram顶端地址。

relocate实现需要的条件

需要的条件

At arch level: add linker flag -pie
添加链接标识 -pie用于生成位置无关代码

1
2
3
4
5
6
This causes the linker to generate fixup tables .rel.dyn and .dynsym,
which must be applied to the relocated image before transferring
control to it.

These fixups are described in the ARM ELF documentation as type 23
(program-base-relative) and 2 (symbol-relative)

At cpu level: modify linker file and add a relocation and fixup loop

1
2
3
4
5
6
7
the linker file must be modified to include the .rel.dyn and .dynsym
tables in the binary image, and to provide symbols for the relocation
code to access these tables

The relocation and fixup loop must be executed after executing
board_init_f at initial location and before executing board_init_r
at final location.

At board level:

1
2
3
dram_init(): bd pointer is now at this point NOT accessible, so only
detect the real dramsize, and store it in gd->ram_size. Bst detected
with get_ram_size().

TODO: move also dram initialization there on boards where it is possible.

1
2
Setup of the the bd_t dram bank info is done in the new function
dram_init_banksize() called after bd is accessible.

At lib level:

1
Board.c code is adapted from ppc code

** WARNING **
Boards which are not fixed to support relocation will be REMOVED!


For boards which boot from spl, it is possible to save one copy
对于从SPL启动的,当CONFIG_SYS_TEXT_BASE和relocate目标地址相等时,可以不进行再次拷贝

if CONFIG_SYS_TEXT_BASE == relocation address! This prevents that uboot code
is copied again in relocate_code().

REF: https://github.com/ARM-software/u-boot/blob/master/doc/README.arm-relocation

位置无关代码的原理

生成位置无关代码步骤:

  • 首先是编译源文件的时候,需要将其编译成位置无关代码,主要通过gcc的-fpic选项(也有可能是fPIC,fPIE, mword-relocations选项)

  • 其次是连接时要将其连接成一个完整的位置无关的可执行文件,主要通过ld的-fpie选项

ARM如何生成位置无关代码:

由于使用pic时movt / movw指令会硬编码16bit的地址域,而uboot的relocation并不支持这个,
所以arm平台使用mword-relocations来生成位置无关代码。-fno-pic则表示不使用pic。

1
2
3
4
5
6
7
#arch/arm/config.mk

# The movt / movw can hardcode 16 bit parts of the addresses in the
# instruction. Relocation is not supported for that case, so disable
# such usage by requiring word relocations.
PLATFORM_CPPFLAGS += $(call cc-option, -mword-relocations)
PLATFORM_CPPFLAGS += $(call cc-option, -fno-pic)

.rel .dyn段简单介绍

对于一些绝对地址符号(例如已经初始化的全局变量),会将其以label的形式放在每个函数的代码实现的末端。
在链接的过程中,会把这些label的地址统一维护在.rel.dyn段中,当relocation的时候,方便对这些地址的fix。

这块只是看懂了个粗略的,没有更深入的去分析,这块可以参考:https://blog.css8.cn/post/2583310.html

为了更方便分析汇编代码,这里将优化关掉了:

1
2
3
4
5
6
7
#顶层Makefile

ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS += -O0
else
KBUILD_CFLAGS += -O0
endif

common/board_f.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//全局变量
static const init_fnc_t init_sequence_f[] = {
setup_mon_len,
#ifdef CONFIG_OF_CONTROL
fdtdec_setup,
#endif
#ifdef CONFIG_TRACE
trace_early_init,
#endif
initf_malloc,
}

void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;

asm_puts("will board_init_f()...\n");

//init_sequence_f是一个指针数组,里面存放各初始化函数的函数指针
//是一个已初始化的全局变量
if (initcall_run_list(init_sequence_f))
hang();
}

使用arm-linux-gnueabi-objdump -D -S u-boot >dump反汇编u-boot elf文件, 查找对应的汇编代码

image-20220330002548843 image-20220330004157730
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void board_init_f(ulong boot_flags)
{
#压栈操作
5fe0dbb8: e92d4800 push {fp, lr}
5fe0dbbc: e28db004 add fp, sp, #4
5fe0dbc0: e24dd008 sub sp, sp, #8
5fe0dbc4: e50b0008 str r0, [fp, #-8]

#gd结构的首地址存放在r9
gd->flags = boot_flags;
5fe0dbc8: e1a02009 mov r2, r9
5fe0dbcc: e51b3008 ldr r3, [fp, #-8]
5fe0dbd0: e5823004 str r3, [r2, #4]

gd->have_console = 0;
5fe0dbd4: e1a02009 mov r2, r9
5fe0dbd8: e3a03000 mov r3, #0
5fe0dbdc: e582301c str r3, [r2, #28]

asm_puts("will board_init_f()...\n");
5fe0dbe0: e59f0024 ldr r0, [pc, #36] ; 5fe0dc0c <board_init_f+0x54>
5fe0dbe4: ebffd214 bl 5fe0243c <asm_puts>

#由于ARM的流水线机制,当前PC值为当前地址加8个字节, 即当前PC=0x5fe0 dbf0
#ldr r0,[pc, #32]表示将0x5fe0 dc10地址处的内容赋值给r0,
#而0x5fe5 90c4正是init_sequence_f的地址,所以取得了init_sequence_f的地址
if (initcall_run_list(init_sequence_f))
5fe0dbe8: e59f0020 ldr r0, [pc, #32] ; 5fe0dc10 <board_init_f+0x58>
5fe0dbec: eb00a3ee bl 5fe36bac <initcall_run_list>
5fe0dbf0: e1a03000 mov r3, r0
5fe0dbf4: e3530000 cmp r3, #0
5fe0dbf8: 0a000000 beq 5fe0dc00 <board_init_f+0x48>
hang();
5fe0dbfc: eb00ca87 bl 5fe40620 <hang>
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
!defined(CONFIG_ARC)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}
5fe0dc00: e1a00000 nop ; (mov r0, r0)
5fe0dc04: e24bd004 sub sp, fp, #4
5fe0dc08: e8bd8800 pop {fp, pc}
5fe0dc0c: 5fe51950 svcpl 0x00e51950
#通过5fe0dc10找到init_sequence_f的地址
5fe0dc10: 5fe590c4 svcpl 0x00e590c4

relocate代码分析

gd结构体分析

gd就是指u-boot里面大名鼎鼎的global_data结构体,那么为什么会需要这样一个结构体呢?

要理解global data的意义,需要先理解如下的事实:

1
2
3
4
5
u-boot是一个bootloader,有些情况下,它可能位于系统的只读存储器(ROM或者flash)中,并从那里开始执行。

因此,这种情况下,在u-boot执行的前期(在将自己copy到可读写的存储器之前),它所在的存储空间,是不可写的,这会有两个问题:
1)堆栈无法使用,无法执行函数调用,也即C环境不可用。
2)没有data段(或者正确初始化的data段)可用,不同函数或者代码之间,无法通过全局变量的形式共享数据。

对于问题1,通常的解决方案是:

1
2
3
u-boot运行起来之后,在那些不需要执行任何初始化动作即可使用的、可读写的存储区域,开辟一段堆栈(stack)空间。
一般来说,大部分的平台(如很多ARM平台),都有自己的SRAM,可用作堆栈空间。
如果实在不行,也有可借用CPU的data cache的方法(不再过多说明)。

对于问题2,解决方案要稍微复杂一些:

1
2
首先,对于开发者来说,在u-boot被拷贝到可读写的RAM(这个动作称作relocation)之前,永远不要使用全局变量。
其次,在relocation之前,不同模块之间,确实有通过全局变量的形式传递数据的需求。怎么办?这就是global data需要解决的事情。

u-boot是如何解决的?

1
2
3
4
5
6
为了在relocation前通过全局变量的形式传递数据,u-boot设计了一个巧妙的方法:

1)定义一个struct global_data类型的数据结构,里面保存了各色各样需要传递的数据

2)堆栈配置好之后,在堆栈开始的位置,为struct global_data预留空间,并将开始地址(就是一个struct global_data指针)
保存在一个寄存器(arm使用r9,arm64使用x18)中,后续的传递,都是通过保存在寄存器中的指针实现

以上摘自:wowo的u-boot启动流程分析(2)_板级(board)部分

gd结构体分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//include/asm-generic/global_data.h  (u-boot 2018.09)

typedef struct global_data
{
bd_t *bd; //传递给kernel的board信息
unsigned long flags; //一些标志位,用于指示初始化是否已执行等
unsigned int baudrate; //串口/console波特率
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
unsigned long pci_clk;
unsigned long mem_clk;

#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO) //LCD or video相关
unsigned long fb_base; /* Base address of framebuffer mem */
#endif

#if defined(CONFIG_POST) //上电自检相关(Power-On-Self-Test, POST)
unsigned long post_log_word; /* Record POST activities */
unsigned long post_log_res; /* success of POST test */
unsigned long post_init_f_time; /* When post_init_f started */
#endif

#ifdef CONFIG_BOARD_TYPES
unsigned long board_type;
#endif

unsigned long have_console; /* serial_init() was called */

//在console未初始化前,缓存字符内容,如果定义PRE_CONSOLE_BUFFER
#if CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER)
unsigned long precon_buf_idx; /* Pre-Console buffer index */
#endif

//u-boot环境变量相关
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Environment valid? enum env_valid */
unsigned long env_has_init; /* Bitmask of boolean of struct env_location offsets */
int env_load_prio; /* Priority of the loaded environment */

unsigned long ram_base; /* Base address of RAM used by U-Boot */
unsigned long ram_top; /* Top address of RAM used by U-Boot */
unsigned long relocaddr; /* Start address of U-Boot in RAM;u-boot重定向后的地址 */
phys_size_t ram_size; /* RAM size */
unsigned long mon_len; /* monitor len,对于arm,等于(ulong)&__bss_end - (ulong)_start*/
unsigned long irq_sp; /* irq stack pointer */
unsigned long start_addr_sp; /* start_addr_stackpointer */
unsigned long reloc_off; /* relocate偏移量*/
struct global_data *new_gd; /* relocated global data;重定向后的gd结构体*/

//如果启用了驱动模型
#ifdef CONFIG_DM
//DM中的根设备,也是uboot中第一个创建的udevice,也就对应了dts里的根节点
struct udevice *dm_root; /* Root instance for Driver Model */
//在relocation之前DM中的根设备
struct udevice *dm_root_f; /* Pre-relocation root instance */
//uclass链表,所有被udevice匹配的uclass都会被挂载到这个链表上
struct list_head uclass_root; /* Head of core tree */
#endif

#ifdef CONFIG_TIMER
struct udevice *timer; /* Timer instance for Driver Model */
#endif

const void *fdt_blob; /* Our device tree, NULL if none; 设备的dtb地址*/
void *new_fdt; /* Relocated FDT;重定向后的dtb地址*/
unsigned long fdt_size; /* Space reserved for relocated FDT */
#ifdef CONFIG_OF_LIVE
struct device_node *of_root;
#endif

struct jt_funcs *jt; /* jump table */
char env_buf[32]; /* buffer for env_get() before reloc. */

#ifdef CONFIG_TRACE
void *trace_buff; /* The trace buffer */
#endif

#if defined(CONFIG_SYS_I2C)
int cur_i2c_bus; /* current used i2c bus */
#endif
#ifdef CONFIG_SYS_I2C_MXC
void *srdata[10];
#endif

unsigned int timebase_h;
unsigned int timebase_l;

#if CONFIG_VAL(SYS_MALLOC_F_LEN)
unsigned long malloc_base; /* base address of early malloc() */
unsigned long malloc_limit; /* limit address */
unsigned long malloc_ptr; /* current address */
#endif

#ifdef CONFIG_PCI
struct pci_controller *hose; /* PCI hose for early use */
phys_addr_t pci_ram_top; /* top of region accessible to PCI */
#endif
#ifdef CONFIG_PCI_BOOTDELAY
int pcidelay_done;
#endif

struct udevice *cur_serial_dev; /* current serial device; 当前串口设备号*/
struct arch_global_data arch; /* architecture-specific data */
#ifdef CONFIG_CONSOLE_RECORD
struct membuff console_out; /* console output */
struct membuff console_in; /* console input */
#endif

#ifdef CONFIG_DM_VIDEO
ulong video_top; /* Top of video frame buffer area */
ulong video_bottom; /* Bottom of video frame buffer area */
#endif

#ifdef CONFIG_BOOTSTAGE
struct bootstage_data *bootstage; /* Bootstage information */
struct bootstage_data *new_bootstage; /* Relocated bootstage info */
#endif

#ifdef CONFIG_LOG
int log_drop_count; /* Number of dropped log messages */
int default_log_level; /* For devices with no filters */
struct list_head log_head; /* List of struct log_device */
int log_fmt; /* Mask containing log format info */
#endif
} gd_t;

/*
* Global Data Flags - the top 16 bits are reserved for arch-specific flags
*/
#define GD_FLG_RELOC 0x00001 /* Code was relocated to RAM */
#define GD_FLG_DEVINIT 0x00002 /* Devices have been initialized */
#define GD_FLG_SILENT 0x00004 /* Silent mode */
#define GD_FLG_POSTFAIL 0x00008 /* Critical POST test failed */
#define GD_FLG_POSTSTOP 0x00010 /* POST seqeunce aborted */
#define GD_FLG_LOGINIT 0x00020 /* Log Buffer has been initialized */
#define GD_FLG_DISABLE_CONSOLE 0x00040 /* Disable console (in & out) */
#define GD_FLG_ENV_READY 0x00080 /* Env. imported into hash table */
#define GD_FLG_SERIAL_READY 0x00100 /* Pre-reloc serial console ready */
#define GD_FLG_FULL_MALLOC_INIT 0x00200 /* Full malloc() is ready */
#define GD_FLG_SPL_INIT 0x00400 /* spl_init() has been called */
#define GD_FLG_SKIP_RELOC 0x00800 /* Don't relocate */
#define GD_FLG_RECORD 0x01000 /* Record console */
#define GD_FLG_ENV_DEFAULT 0x02000 /* Default variable flag */
#define GD_FLG_SPL_EARLY_INIT 0x04000 /* Early SPL init is done */
#define GD_FLG_LOG_READY 0x08000 /* Log system is ready for use */


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//include/asm-generic/u-boot.h (u-boot 2018.09)

/*
* Board information passed to Linux kernel from U-Boot
*
* include/asm-ppc/u-boot.h
*/
typedef struct bd_info
{
unsigned long bi_memstart; /* start of DRAM memory */
phys_size_t bi_memsize; /* size of DRAM memory in bytes */
unsigned long bi_flashstart; /* start of FLASH memory */
unsigned long bi_flashsize; /* size of FLASH memory */
unsigned long bi_flashoffset; /* reserved area for startup monitor */
unsigned long bi_sramstart; /* start of SRAM memory */
unsigned long bi_sramsize; /* size of SRAM memory */
#ifdef CONFIG_ARM
unsigned long bi_arm_freq; /* arm frequency */
unsigned long bi_dsp_freq; /* dsp core frequency */
unsigned long bi_ddr_freq; /* ddr frequency */
#endif

#if defined(CONFIG_MPC8xx) || defined(CONFIG_E500) || defined(CONFIG_MPC86xx)
unsigned long bi_immr_base; /* base of IMMR register */
#endif
#if defined(CONFIG_M68K)
unsigned long bi_mbar_base; /* base of internal registers */
#endif
#if defined(CONFIG_MPC83xx)
unsigned long bi_immrbar;
#endif

unsigned long bi_bootflags; /* boot / reboot flag (Unused) */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* OLD: see README.enetaddr */
unsigned short bi_ethspeed; /* Ethernet speed in Mbps */
unsigned long bi_intfreq; /* Internal Freq, in MHz */
unsigned long bi_busfreq; /* Bus Freq, in MHz */

#if defined(CONFIG_CPM2)
unsigned long bi_cpmfreq; /* CPM_CLK Freq, in MHz */
unsigned long bi_brgfreq; /* BRG_CLK Freq, in MHz */
unsigned long bi_sccfreq; /* SCC_CLK Freq, in MHz */
unsigned long bi_vco; /* VCO Out from PLL, in MHz */
#endif
#if defined(CONFIG_M68K)
unsigned long bi_ipbfreq; /* IPB Bus Freq, in MHz */
unsigned long bi_pcifreq; /* PCI Bus Freq, in MHz */
#endif

#if defined(CONFIG_EXTRA_CLOCK)
unsigned long bi_inpfreq; /* input Freq in MHz */
unsigned long bi_vcofreq; /* vco Freq in MHz */
unsigned long bi_flbfreq; /* Flexbus Freq in MHz */
#endif

#ifdef CONFIG_HAS_ETH1
unsigned char bi_enet1addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH2
unsigned char bi_enet2addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH3
unsigned char bi_enet3addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH4
unsigned char bi_enet4addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH5
unsigned char bi_enet5addr[6]; /* OLD: see README.enetaddr */
#endif

ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */

#ifdef CONFIG_NR_DRAM_BANKS
struct
{ /* RAM configuration */
phys_addr_t start;
phys_size_t size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#endif

} bd_t;

对于arm,gd的首地址存放在r9中,u-boot定义了DECLARE_GLOBAL_DATA_PTR宏,通过它即可访问gd结构体:

1
2
3
4
5
6
7
//arch/arm/include/asm/global_data.h

#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
#endif

gd的内存分布:

image-20220330021527690

图来自:https://blog.csdn.net/ooonebook/article/details/53013545

代码流程

1
2
3
4
5
6
* 对relocate进行空间规划
* 计算uboot代码空间到relocation的位置的偏移
* relocate旧的global_data到新的global_data的空间上
* relocate旧的uboot代码空间到新的空间上去
* 修改relocate之后全局变量的label。
* relocate中断向量表

基础:ARM ABI约定

ARM架构程序调用标准(Procedure Call Standard for the ARM Architecture, AAPCS)描述了ARM架构下应用程序二进制接口(Application Binary Interface, ABI)程序调用的标准.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
寄存器      别名        特殊名          程序调用标准中作用
r15 PC 程序计数器.
r14 LR 链接寄存器.
r13 SP 栈指针.
r12 IP 程序调用过程中备份寄存器.
r11 v8 变量寄存器8.
r10 v7 变量寄存器7.
r9 v6 / SB / TR 平台寄存器, 该寄存器意义由平台标准指定.
r8 v5 变量寄存器5.
r7 v4 变量寄存器4.
r6 v3 变量寄存器3.
r5 v2 变量寄存器2.
r4 v1 变量寄存器1.
r3 a4 参数 / 备份寄存器4.
r2 a3 参数 / 备份寄存器3.
r1 a2 参数 / 结果 / 备份寄存器2.
r0 a1 参数 / 结果 / 备份寄存器1.

程序计数器r15–PC——指向正在取址的指令

由于RAM的流水线结构,对于3级流水线结构,指令分为三个阶段执行:取址,译码(识别将要被执行的指令),执行(处理指令并将结果写回寄存器);人们习惯约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第3条指令,对于arm32,每条指令为4byte,所以**PC=当前程序执行位置+8**.

链接寄存器r14–LR——用于保存子程序的返回地址;

栈指针r13–SP——指示当前要出栈或入栈的数据

在子程序中寄存器r13不能用作它用,它的值在进入、退出子程序时必须相等;

总体代码-_main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#arch/arm/lib/crt0.S

ENTRY(_main)

/*
* Set up initial C runtime environment and call board_init_f(0).
*/

#预设堆栈指针,只是预设的堆栈地址,不是最终的
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK) #SPL的话一般使用片内SRAM
#else
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR) #u-boot使用片外SDRAM
#endif
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */

/*
* 将r0作为board_init_f_alloc_reserve()的参数,分配栈空间
* 返回后,r0里存放的是gd的首地址,同时也是新的堆栈地址
* 因为堆栈是向下增长的,而gd的内容是向上的,所以互不冲突
*/
mov sp, r0
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0 #使用r9保存gd的首地址

#初始化gd结构体
bl board_init_f_init_reserve



/*
* board_init_f实现了:
* 1.对relocate进行空间规划
* 2.计算uboot代码空间到relocation的位置的偏移
* 3.relocate旧的global_data到新的global_data的空间上
*/
mov r0, #0
bl board_init_f


#if ! defined(CONFIG_SPL_BUILD)
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/

#新的栈地址
ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0

#新的gd地址,新gd位于旧gd->bd之下
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */

#计算返回地址在新的u-boot空间中的地址。b调用函数返回之后,就跳到了新的u-boot代码空间中
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */

/* relocate_code实现了
* 1. relocate旧的uboot代码空间到新的空间上去
* 2. 修改relocate之后全局变量的label(.rel.dyn段等)
*/
b relocate_code

#以下为新的u-boot代码空间

here:
/*
* now relocate vectors
*/
bl relocate_vectors

/* Set up final (full) environment */

bl c_runtime_cpu_setup /* we still call old routine here */
#endif /*!defined(CONFIG_SPL_BUILD)*/


#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
#对于SPL
#ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
#endif


/*
* 以下作用:清空bss段
*/
ldr r0, =__bss_start /* this is auto-relocated! */
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /* this is auto-relocated! */
mov r1, #0x00000000 /* prepare zero to clear BSS */

subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */

clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
#endif

#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif

/*
* 执行board_init_r()
* 参数1: gd->new_gd
* 参数2: gd->relocaddr
*/
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */

/* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */

@ mov r0, #0xfd
@ bl asm_led_on
#endif

/* we should not return here. */
#endif

ENDPROC(_main)

以下内容,以正在移植的OK6410板子为分析对象。

board_init_f_alloc_reserve()分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//common/init/board_init.c
/*
* Allocate reserved space for use as 'globals' from 'top' address and
* return 'bottom' address of allocated space
*
* Notes:
*
* Actual reservation cannot be done from within this function as
* it requires altering the C stack pointer, so this will be done by
* the caller upon return from this function.
*
* IMPORTANT:
*
* Alignment constraints may differ for each 'chunk' allocated. For now:
*
* - GD is aligned down on a 16-byte boundary
*
* - the early malloc arena is not aligned, therefore it follows the stack
* alignment constraint of the architecture for which we are bulding.
*
* - GD is allocated last, so that the return value of this functions is
* both the bottom of the reserved area and the address of GD, should
* the calling context need it.
*/

/*
* 以手头的OK6410板子分析:
* CONFIG_SYS_TEXT_BASE = 0x5FE00000
* CONFIG_SYS_INIT_SP_ADDR = (CONFIG_SYS_TEXT_BASE - 0x80)
* SYS_MALLOC_F_LEN宏没有定义
*/


ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
//保留出了用于存放gd的空间,并使新sp地址16字节对齐,这里gd_size=208=0xd0
top = rounddown(top-sizeof(struct global_data), 16);

return top;
}

board_init_f_init_reserve分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//common/init/board_init.c

/*
* Initialize reserved space (which has been safely allocated on the C
* stack from the C runtime environment handling code).
*
* Notes:
*
* Actual reservation was done by the caller; the locations from base
* to base+size-1 (where 'size' is the value returned by the allocation
* function above) can be accessed freely without risk of corrupting the
* C runtime environment.
*
* IMPORTANT:
*
* Upon return from the allocation function above, on some architectures
* the caller will set gd to the lowest reserved location. Therefore, in
* this initialization function, the global data MUST be placed at base.
*
* ALSO IMPORTANT:
*
* On some architectures, gd will already be good when entering this
* function. On others, it will only be good once arch_setup_gd() returns.
* Therefore, global data accesses must be done:
*
* - through gd_ptr if before the call to arch_setup_gd();
*
* - through gd once arch_setup_gd() has been called.
*
* Do not use 'gd->' until arch_setup_gd() has been called!
*
* IMPORTANT TOO:
*
* Initialization for each "chunk" (GD, early malloc arena...) ends with
* an incrementation line of the form 'base += <some size>'. The last of
* these incrementations seems useless, as base will not be used any
* more after this incrementation; but if/when a new "chunk" is appended,
* this increment will be essential as it will give base right value for
* this new chunk (which will have to end with its own incrementation
* statement). Besides, the compiler's optimizer will silently detect
* and remove the last base incrementation, therefore leaving that last
* (seemingly useless) incrementation causes no code increase.
*/
void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;

/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/
gd_ptr = (struct global_data *)base;
/* zero the area */
memset(gd_ptr, '\0', sizeof(*gd));
/* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
arch_setup_gd(gd_ptr);
#endif
/* next alloc will be higher by one GD plus 16-byte alignment */
base += roundup(sizeof(struct global_data), 16);

/*
* record early malloc arena start.
* Use gd as it is now properly set for all architectures.
*/

#if CONFIG_VAL(SYS_MALLOC_F_LEN)
/* go down one 'early malloc arena' */
gd->malloc_base = base;
/* next alloc will be higher by one 'early malloc arena' size */
base += CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
}

执行完此函数后,内存分布如下:

image-20220331004909952

board_init_f()分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//common/board_f.c
void board_init_f(ulong boot_flags)
{
//通过r9访问gd结构体
gd->flags = boot_flags;
gd->have_console = 0;

//通过init_sequence_f指针数组执行初始化流程,指针数组中存放各初始化函数首地址
if (initcall_run_list(init_sequence_f))
hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
!defined(CONFIG_ARC)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}

//init_sequence_f数组
static const init_fnc_t init_sequence_f[] = {
setup_mon_len, //设置gd->mon_len,即u-boot的大小
#ifdef CONFIG_OF_CONTROL
fdtdec_setup, //设置gd->fdt_blob,即dts存放的地址
#endif
#ifdef CONFIG_TRACE
trace_early_init,
#endif

dram_init, /* configure available RAM banks */

//省略..... 删除部分宏相关,对arm平台无效代码

//以下是一些和布局相关得函数
/*
* Now that we have DRAM mapped and working, we can
* relocate the code and continue running from DRAM.
*
* Reserve memory at end of RAM for (top down in that order):
* - area that won't get touched by U-Boot and Linux (optional)
* - kernel log buffer
* - protected RAM
* - LCD framebuffer
* - monitor code
* - board info struct
*/
setup_dest_addr,
#ifdef CONFIG_PRAM
reserve_pram,
#endif
reserve_round_4k,
#ifdef CONFIG_ARM
reserve_mmu,
#endif
reserve_video,
reserve_trace,
reserve_uboot,
reserve_malloc,
reserve_board,
setup_machine,
reserve_global_data,
reserve_fdt,
reserve_bootstage,
reserve_arch,
reserve_stacks,
dram_init_banksize,
show_dram_config,
display_new_sp,
#ifdef CONFIG_OF_BOARD_FIXUP
fix_fdt,
#endif
INIT_FUNC_WATCHDOG_RESET
reloc_fdt,
reloc_bootstage,
setup_reloc,
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!CONFIG_IS_ENABLED(X86_64)
jump_to_copy,
#endif
NULL,
};

这里分析一些重要的函数:

  • setup_dest_addr()–目的地址初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    //common/board_f.c
    static int setup_dest_addr(void)
    {
    /*表示u-boot的大小,在uboot代码空间relocate的时候,
    * relocate的size就是由这里决定
    * gd->mon_len = (ulong)&__bss_end - (ulong)_start;
    */
    debug("Monitor len: %08lX\n", gd->mon_len);

    /*
    * Ram is setup, size stored in gd !!
    * 表示ram的大小,对于ok6410,在smdk6410.c中由dram_init()初始化
    * gd->bd->bi_dram[0].start = 0x50000000;
    * gd->bd->bi_dram[0].size = 0x10000000; //256M
    */
    debug("Ram size: %08lX\n", (ulong)gd->ram_size);
    #if defined(CONFIG_SYS_MEM_TOP_HIDE)
    /*
    * Subtract specified amount of memory to hide so that it won't
    * get "touched" at all by U-Boot. By fixing up gd->ram_size
    * the Linux kernel should now get passed the now "corrected"
    * memory size and won't touch it either. This should work
    * for arch/ppc and arch/powerpc. Only Linux board ports in
    * arch/powerpc with bootwrapper support, that recalculate the
    * memory size from the SDRAM controller setup will have to
    * get fixed.
    */
    gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
    #endif

    #ifdef CONFIG_SYS_SDRAM_BASE
    gd->ram_base = CONFIG_SYS_SDRAM_BASE;
    #endif
    /*
    * 计算dram的顶端地址:gd->ram_top = gd->ram_base + gd->ram_size;
    * relocate地址(新u-boot的起始地址):gd->relocaddr = gd->ram_top;
    */
    gd->ram_top = gd->ram_base + get_effective_memsize();
    gd->ram_top = board_get_usable_ram_top(gd->mon_len);
    gd->relocaddr = gd->ram_top;
    debug("Ram top: %08lX\n", (ulong)gd->ram_top);

    #if defined(CONFIG_MP) && (defined(CONFIG_MPC86xx) || defined(CONFIG_E500))
    /*
    * We need to make sure the location we intend to put secondary core
    * boot code is reserved and not used by any part of u-boot
    */
    if (gd->relocaddr > determine_mp_bootpg(NULL)) {
    gd->relocaddr = determine_mp_bootpg(NULL);
    debug("Reserving MP boot page to %08lx\n", gd->relocaddr);
    }
    #endif
    return 0;
    }
  • setup_reloc()–计算u-boot地址偏移量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //common/board_f.c
    static int setup_reloc(void)
    {
    if (gd->flags & GD_FLG_SKIP_RELOC) {
    debug("Skipping relocation due to flag\n");
    return 0;
    }

    #ifdef CONFIG_SYS_TEXT_BASE
    #ifdef ARM
    /*
    * 对于arm
    * 偏移量gd->reloc_off=gd->relocaddr-(unsigned long)__image_copy_start
    * gd->relocaddr为新u-boot的起始地址
    * __image_copy_start在u-boot.lds链接器脚本文件中定义,是旧u-boot的起始地址
    */
    gd->reloc_off = gd->relocaddr - (unsigned long)__image_copy_start;
    #elif defined(CONFIG_M68K)
    /*
    * On all ColdFire arch cpu, monitor code starts always
    * just after the default vector table location, so at 0x400
    */
    gd->reloc_off = gd->relocaddr - (CONFIG_SYS_TEXT_BASE + 0x400);
    #else
    gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
    #endif
    #endif
    //relocate旧的global_data到新的global_data的空间上
    memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));

    debug("Relocation Offset is: %08lx\n", gd->reloc_off);
    debug("Relocating to %08lx, new gd at %08lx, sp at %08lx\n",
    gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd),
    gd->start_addr_sp);

    return 0;
    }

relocate_code分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#ldr    r0, [r9, #GD_RELOCADDR]
#bl relocate_code

/*
* arch/arm/lib/relocate.S
*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an R_ARM_ABS32
* relocation record type, we never refer to linker-defined symbols directly.
* Instead, we declare literals which contain their relative location with
* respect to relocate_code, and at run time, add relocate_code back to them.
*/

ENTRY(relocate_code)
/*
* r0是新的u-boot起始地址, __image_copy_start是原u-boot起始地址
* r4偏移量, r2原u-boot的结束地址
*/
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */

copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop

/*
* relocate之后,根据位置无关代码原理,需要fix.rel .dyn段的地址
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #R_ARM_RELATIVE /*R_ARM_RELATIVE=23=0x17*/
bne fixnext /*比较高4byte是否是0x17,不是说明不是label*/

/*
* label在relocate uboot的时候也已经复制到了新的uboot地址空间了
* 这里要注意,是对新的uboot地址空间label进行修改
*/

/* relative fix: increase location by offset */
/*
* r0存的是旧地址空间的label地址
* 新地址空间的label地址=旧地址空间的label地址+偏移
*/
add r0, r0, r4
ldr r1, [r0] /*从label中获取绝对地址符号的地址,存放在r1中*/
add r1, r1, r4 /*新地址*/
str r1, [r0] /*更新label地址*/
fixnext:
cmp r2, r3
blo fixloop

relocate_done:

#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif

/* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif

ENDPROC(relocate_code)

relocate_vectors分析

arm平台中断向量表定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#arch/arm/lib/vectors.S

/*
*************************************************************************
*
* Indirect vectors table
*
* Symbols referenced here must be defined somewhere else
*
*************************************************************************
*/

.globl _undefined_instruction
.globl _software_interrupt
.globl _prefetch_abort
.globl _data_abort
.globl _not_used
.globl _irq
.globl _fiq

_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq

.balignl 16,0xdeadbeef

在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0x00000000,另一个是0xFFFF0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下:

1
2
3
V=0  0x00000000~0x0000001C

V=1 0xFFFF0000~0xFFFF001C

当u-boot relocate之后,其异常处理函数也发生了变化,所以需要更新中断向量表。

查看objdump反汇编的u-boot文件,可以知道中断向量表在u-boot代码空间的情况:

image-20220401021720267

relocate_vectors代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#arch/arm/lib/relocate.S

/*
* Default/weak exception vectors relocation routine
*
* This routine covers the standard ARM cases: normal (0x00000000),
* high (0xffff0000) and VBAR. SoCs which do not comply with any of
* the standard cases must provide their own, strong, version.
*/

.section .text.relocate_vectors,"ax",%progbits
.weak relocate_vectors

ENTRY(relocate_vectors)

#ifdef CONFIG_CPU_V7M
/*
* On ARMv7-M we only have to write the new vector address
* to VTOR register.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
ldr r1, =V7M_SCB_BASE
str r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
#else
/*
* Copy the relocated exception vectors to the
* correct address
* CP15 c1 V bit gives us the location of the vectors:
* 0x00000000 or 0xFFFF0000.
* cp15协处理器的c1寄存器v标志位来决定向量表位置:0x00000000 or 0xFFFF0000
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr,获取新u-boot起始地址*/

mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
ands r2, r2, #(1 << 13)
ldreq r1, =0x00000000 /* If V=0 */
ldrne r1, =0xFFFF0000 /* If V=1 */

/*
* 没明白????
*/
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
#endif
#endif
bx lr

ENDPROC(relocate_vectors)

待处理问题

u-boot relocate后的内存布局?

参考引用

uboot番外篇–uboot relocation介绍

uboot的ocation原理详细分析

u-boot启动流程

超详细【Uboot驱动开发】(二) uboot启动流程分析