当前位置:   article > 正文

正点原子嵌入式linux驱动开发——U-boot启动流程详解_嵌入式linux u-boot

嵌入式linux u-boot

在上一篇笔记中详细分析了uboot的顶层Makefile,理清了uboot的编译流程。本章来详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,可以掌握一些外设是在哪里被初始化的,这样当需要修改这些外设驱动的时候就会比较简单。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的

链接脚本u-boot.lds详解

要分析uboot的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过uboot的话链接脚本为arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下uboot,编译完成以后就会在uboot根目录下生成u-boot.lds文件,如下图所示:
链接脚本
只有编译u-boot以后才会在根目录下出现 u-boot.lds文件!

打开u-boot.lds,内容如下(有省略):
示例代码12.1.1 u-boot.lds文件部分代码截图
第3行为代码当前入口点:_start ,_start在文件 arch/arm/lib/vectors.S中有定义,如下示例代码所示(省略注释):
示例代码12.1.2 _start入口
从上述示例代码中可以看出,_start后面就是中断向量表,第2行的“.section “.vectors”,“ax””可以得到,此代码存放在.vectors段里面。

使用如下命令在uboot中查找“__image_copy_start”:

grep -nR "__image_copy_start"

搜索结果如下图所示:
查找结果
打开u-boot.map,找到如下图所示位置:
u-boot.map
u-boot.map是uboot的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从上图的1629行可以看到__image_copy_start为0Xc0100000,而.text的起始地址也是0Xc0100000

继续回到上述示例代码中,第11行是vectors段,vectors段保存中断向量表,从上图中我们知道了vectors.S的代码是存在vectors段中的。从上图可以看出,vectors段的起始地址也是0Xc0100000,说明整个uboot的起始地址就是0Xc0100000

上述示例代码中第12行将arch/arm/cpu/armv7/start.s编译出来的代码放到中断向量表后面。

u-boot.lds中有一些跟地址有关的“变量”需要注意一下,后面分析u-boot源码的时候会用到,这些变量要最终编译完成才能确定的!!!比如教程中编译完成以后这些“变量”的值如下图所示:
uboot相关变量表
上图中的“变量”值可以在u-boot.map文件中查找,上图中除了 __image_copy_start以外,其他的变量值每次编译的时候可能会变化,如果修改了uboot代码、修改了uboot配置、选用不同的优化等级等等都会影响到这些值。所以,一切以实际值为准!

U-Boot启动流程详解

reset函数源码详解

从u-boot.lds中已经看出入口点是arch/arm/lib/vectors.S文件中的_start,回到上面的示例代码。

第4行_start开始的是中断向量表,其中11-18行就是中断向量表,和裸机例程里面一样。第11行跳转到reset函数里面,reset函数在arch/arm/cpu/armv7/start.S里面,代码如下:
示例代码12.2.1.1 start.S代码段截图
第31行就是reset函数。

第40行从reset函数跳转到了save_boot_params函数,而save_boot_params函数同样定义
在start.S里面,定义如下:
示例代码12.2.1.2 start.S代码段截图
save_boot_params函数也是只有一句跳转语句,跳转到save_boot_params_ret函数,save_boot_params_ret函数代码如下:
示例代码12.2.1.3 start.S代码段截图
第42-51行,CONFIG_ARMV7_LPAE没有定义所以不会运行。

第56行,读取寄存器cpsr中的值,并保存到r0寄存器中。

第57行,将寄存器r0中的值与0X1F进行与运算,结果保存到r1寄存器中,目的就是提取cpsr的 bit0-bit4这5位,这5位为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式,如下图所示:
Cortex-A7工作模式
第58行,判断r1寄存器的值是否等于0X1A(0b11010),也就是判断当前处理器模式是否处于Hyp模式。

第59行,如果r1和0X1A不相等,也就是CPU不处于Hyp模式的话就将r0寄存器的bit0-5进行清零,其实就是清除模式位。

第60行,如果处理器不处于Hyp模式的话就将r0的寄存器的值与0x13进行或运算,0x13=0b10011,也就是设置处理器进入SVC模式。

第61行,r0寄存器的值再与0xC0进行或运算,那么r0寄存器此时的值就是0xD3,cpsr的I为和F位分别控制IRQ和FIQ这两个中断的开关,设置为1就关闭了FIQ和IRQ。

第62行,将r0寄存器写回到cpsr寄存器中。完成设置CPU处于SVC32模式,并且关闭FIQ和IRQ这两个中断

继续看下面代码:
示例代码12.2.1.4 start.S代码段截图
第69行,如果没有定义CONFIG_OMAP44XX和CONFIG_SPL_BUILD的话条件成立,此处条件成立。

第71行读取CP15中c1寄存器的值到r0寄存器中,读取SCTLR寄存器的值。

第72行,CR_V在arch/arm/include/asm/system.h中有如下所示定义:

#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */

因此这一行的目的就是清除SCTLR寄存器中的 bit13,SCTLR寄存器结构如下图所示:
SCTLR寄存器结构图
从上图中可以看出,bit13为V位,此位是向量表控制位,当为0的时候向量表基地址为0X00000000,软件可以重定位向量表。为1的时候向量表基地址为0XFFFF0000,软件不能重定位向量表。这里将V清零,目的就是为了接下来的向量表重定位。

第73行将r0寄存器的值重新写入到寄存器SCTLR中。

第77行设置r0寄存器的值为_start,_start就是整个uboot的入口地址,其值为0XC0100000,相当于uboot的起始地址,因此0Xc0100000也是向量表的起始地址

第78行将r0寄存器的值(向量表起始地址)写入到CP15的c12寄存器中,也就是VBAR寄存器。因此第71-78行就是设置向量表重定位的。

代码继续往下执行:
示例代码12.2.1.5 start.S代码段截图
第83行如果没有定义CONFIG_SKIP_LOWLEVEL_INIT的话条件成立。我们没有定义CONFIG_SKIP_LOWLEVEL_INIT,因此条件成立,执行下面的语句。

第84行,.config文件里定义CONFIG_CPU_V7A,所以第85行会执行。

第87行,没有定义CONFIG_SKIP_LOWLEVEL_INIT_ONLY,所以第88行会执行。

上述示例代码中的内容比较简单,就是分别调用函数cpu_init_cp15、 cpu_init_crit和_main

函数cpu_init_cp15用来设置CP15相关的内容,比如关闭MMU啥的,此函数同样在start.S文件中定义的,代码如下:
示例代码12.2.1.6 start.S代码段截图
函数cpu_init_cp15都是一些和CP15有关的内容,我们不用关心。

函数cpu_init_crit也在是定义在start.S文件中,函数内容如下:
示例代码12.2.1.7 start.S代码段截图
可以看出函数cpu_init_crit内部仅仅是调用了函数lowlevel_init,接下来就是详细的分析一
下lowlevel_init和_main这两个函数。

lowlevel_init函数详解

函数lowlevel_init在文件arch/arm/cpu/armv7/lowlevel_init.S中定义,内容如下:

示例代码12.2.2.1 lowlevel_init.S代码段 
13 #include <asm-offsets.h> 
14 #include <config.h> 
15 #include <linux/linkage.h> 
16 
17 .pushsection .text.s_init, "ax" 
18 WEAK(s_init) 
19     bx lr 
20 ENDPROC(s_init) 
21 .popsection 
22 
23 .pushsection .text.lowlevel_init, "ax" 
24 WEAK(lowlevel_init) 
25     /* 
26     * Setup a temporary stack. Global data is not available yet. 
27     */ 
28 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) 
29     ldr sp, =CONFIG_SPL_STACK 
30 #else 
31     ldr sp, =CONFIG_SYS_INIT_SP_ADDR 
32 #endif 
33     bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ 
34 #ifdef CONFIG_SPL_DM 
35     mov r9, #0 
36 #else 
37     /* 
38     * Set up global data for boards that still need it. This will
39     * be removed soon. 
40     */ 
41 #ifdef CONFIG_SPL_BUILD 
42     ldr r9, =gdata 
43 #else 
44     sub sp, sp, #GD_SIZE 
45     bic sp, sp, #7 
46     mov r9, sp 
47 #endif 
48 #endif 
49     /* 
50     * Save the old lr(passed in ip) and the current lr to stack 
51     */ 
52     push {ip, lr} 
53 
54     /* 
55     * Call the very early init function. This should do only the 
56     * absolute bare minimum to get started. It should not: 
57     * 
58     * - set up DRAM 
59     * - use global_data 
60     * - clear BSS 
61     * - try to start a console 
62     * 
63     * For boards with SPL this should be empty since SPL can do all 
64     * of this init in the SPL board_init_f() function which is 
65     * called immediately after this. 
66     */ 
67     bl s_init 
68     pop {ip, pc} 
69 ENDPROC(lowlevel_init)
  • 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

第31行设置sp指向CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR在
include/configs/stm32mp1.h文件中,在stm32mp1.h中有如下所示定义:
stm32mp1.h代码段截图
CONFIG_SYS_TEXT_BASE的定义在我们.config文件中定义,如下图所示:
CONFIG_SYS_TEXT_BASE定义
从上图中可得知CONFIG_SYS_INIT_SP_ADDR 的值为0XC0100000。此时sp指向0XC0100000

继续回到文件lowlevel_init.S,第33行对sp指针做8字节对齐处理!

第44行,sp指针减去GD_SIZE,GD_SIZE的定义在include/generated/generic-asm-offsets.h文件中,如下图所示:
示例代码12.2.2.3 generic-asm-offsets.h代码段截图
第11行,GD_SIZE大小为240

继续回到文件lowlevel_init.S,第45行对sp做8个字节对齐,此时sp的地址为0XC0100000-240=0XC00FFDC0。

第46行将sp地址保存在r9寄存器中。

第52行将ip和lr压栈。

第67行调用函数s_init,此函数没啥作用。

第88行将第36行入栈的ip和lr进行出栈,并将lr赋给pc。

lowlevel_init运行完后,就返回函数cpu_init_crit,函数cpu_init_crit也执行完成了,最终返回到save_boot_params_ret,函数调用路径如下图所示:
uboot函数调用路径
从上图可知,接下来要执行的是save_boot_params_ret中的_main函数,接下来分析_main函数。

_main函数详解

_main函数定义在文件arch/arm/lib/crt0.S中,函数内容如下:

示例代码代码12.2.3.1 crt0.S代码段 
91 ENTRY(_main) 
92 
93 /* 
94 * Set up initial C runtime environment and call board_init_f(0). 
95 */ 
96 
97 #if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK) 
98     ldr r0, =(CONFIG_TPL_STACK) 
99 #elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) 
100     ldr r0, =(CONFIG_SPL_STACK) 
101 #else 
102     ldr r0, =(CONFIG_SYS_INIT_SP_ADDR) 
103 #endif 
104     bic r0, r0, #7 /* 8-byte alignment for ABI compliance */ 
105     mov sp, r0 
106     bl board_init_f_alloc_reserve 
107     mov sp, r0 
108     /* set up gd here, outside any C code */ 
109     mov r9, r0 
110     bl board_init_f_init_reserve 
111 
112 #if defined(CONFIG_SPL_EARLY_BSS) 
113     SPL_CLEAR_BSS 
114 #endif 
115 
116     mov r0, #0 
117     bl board_init_f 
118 
119 #if ! defined(CONFIG_SPL_BUILD) 
120 
121 /* 
122 * Set up intermediate environment (new sp and gd) and call 
123 * relocate_code(addr_moni). Trick here is that we'll return 
124 * 'here' but relocated. 
125 */ 
126
127     ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ 
128     bic r0, r0, #7 /* 8-byte alignment for ABI compliance */ 
129     mov sp, r0 
130     ldr r9, [r9, #GD_NEW_GD] /* r9 <- gd->new_gd */ 
131 
132     adr lr, here 
133     ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ 
134     add lr, lr, r0 
135 #if defined(CONFIG_CPU_V7M) 
136     orr lr, #1 /* As required by Thumb-only */ 
137 #endif 
138     ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ 
139     b relocate_code 
140 here: 
141 /* 
142 * now relocate vectors 
143 */ 
144 
145     bl relocate_vectors 
146 
147 /* Set up final (full) environment */ 
148 
149     bl c_runtime_cpu_setup /* we still call old routine here */ 
150 #endif 
151 #if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK) 
152 
153 #if !defined(CONFIG_SPL_EARLY_BSS) 
154     SPL_CLEAR_BSS 
155 #endif 
156 
157 # ifdef CONFIG_SPL_BUILD 
158     /* Use a DRAM stack for the rest of SPL, if requested */ 
159     bl spl_relocate_stack_gd 
160     cmp r0, #0 
161     movne sp, r0 
162     movne r9, r0 
163 # endif 
164 
165 #if ! defined(CONFIG_SPL_BUILD) 
166     bl coloured_LED_init 
167     bl red_led_on 
168 #endif 
169     /* call board_init_r(gd_t *id, ulong dest_addr) */
170     mov r0, r9 /* gd_t */ 
171     ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ 
172     /* call board_init_r */ 
173 #if CONFIG_IS_ENABLED(SYS_THUMB_BUILD) 
174     ldr lr, =board_init_r /* this is auto-relocated! */ 
175     bx lr 
176 #else 
177     ldr pc, =board_init_r /* this is auto-relocated! */ 
178 #endif 
179     /* we should not return here. */ 
180 #endif 
181 
182 ENDPROC(_main) 
183
  • 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

代码太长了很难截图,就直接把源码拷贝过来了,行数的话主要还是对着讲解的内容过一遍。

第102行,设置sp指针为CONFIG_SYS_INIT_SP_ADDR,也就是sp指向0XC0100000

第104行,sp做8字节对齐。

第105行,读取sp到寄存器r0里面,此时r0=0XC0100000。

第106行,调用函数board_init_f_alloc_reserve,此函数有一个参数,参数为r0中的值,也就是0XC0100000,此函数定义在文件common/init/board_init.c中,内容如下:
示例代码12.2.3.2 board_init.c代码段截图
函数board_init_f_alloc_reserve主要是留出早期的malloc内存区域和gd内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X3000(在文件include/generated/autoconf.h中定义),另外sizeof(struct global_data)=240,也就是GD_SIZE值。内存结构如下图所示:
内存结构图
函数board_init_f_alloc_reserve是有返回值的,返回值为新的top值,此时top=0XC00FCF10(地址刚好是16字节对齐)。

继续回到之前的示例代码中,第107行,将r0写入到sp里面,r0保存着函数board_init_f_alloc_reserve的返回值,所以这一句也就是设置sp=0XC00FCF10

第109行,将r0寄存器的值写到寄存器r9里面,因为r9寄存器存放着全局变量gd的地址,在文件arch/arm/include/asm/global_data.h中有如下图所示宏定义:
DECLARE_GLOBAL_DATA_PTR宏定义
从上图可以看出,uboot中定义了一个指向gd_t结构体类型的指针:gd,gd存放在寄存器r9里的,因此gd是个全局变量。gd_t是个结构体,在include/asm-generic/global_data.h里面有定义,gd_定义如下:
示例代码12.2.3.3 global_data结构体
因此这一行代码就是设置gd所指向的位置,也就是gd指向0XC00FCF10

继续回到示例代码12.2.3.1中,第110行调用函数board_init_f_init_reserve,此函数在文件common/init/board_init.c中有定义,函数内容如下:
示例代码12.2.3.4 board_init_f_init_reserve函数
可以看出,此函数用于初始化gd,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0XC00FCF10+240=0XC00FD000,这个也就是early malloc的起始地址

继续回到上述示例代码中,第116行设置R0为0。

第117行,调用board_init_f函数,此函数定义在文件common/board_f.c中,主要用来初始化平台相关的API,定时器,完成代码拷贝等等,此函数后面在详细的分析。

第127行,重新设置环境(sp和gd),获取gd->start_addr_sp的值赋给sp,在函数board_init_f中会初始化gd的所有成员变量,最终gd->start_addr_sp=0XF3AEDD20,所以这里相当于设置sp=gd->start_addr_sp=0XF3AEDD20。 GD_START_ADDR_SP=80,之前有一个示例代码已经有过分析,这里不再赘述。

第128行,sp做 8字节对齐。

第130行,获取gd->new_gd的地址赋给r9,此时r9存放的是老的gd,这里通过获取gd->new_gd的地址来计算出新的gd的位置。 GD_BD=0,之前的示例代码已经有过分析。

第132行,设置lr寄存器为here,这样后面执行其他函数返回的时候就返回到了第122行的here位置处

第133行,读取gd->reloc_off的值复制给r0寄存器,GD_RELOC_OFF=84,参考之前的示例代码。

第134行,lr寄存器的值加上r0寄存器的值,重新赋值给lr寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的uboot存放的起始地址为0XC0100000,下面要将uboot拷贝到DDR最后面的地址空间出,将0XC0100000开始的内存空出来),其中就包括here,因此lr中的here要使用重定位后的位置。

第138行,读取gd->relocaddr的值赋给r0寄存器,此时r0寄存器就保存着uboot要拷贝的目的地址,为0XF5C46000。GD_RELOCADDR=40,参考之前的示例代码。

第139行,调用函数relocate_code,也就是代码重定位函数,此函数负责将uboot拷贝到新的地方去,此函数定义在文件arch/arm/lib/relocate.S中稍后会详细分析此函数。

继续回到之前的示例代码,第145行,调用函数relocate_vectors,对中断向量表做重定位,此函数定义在文件arch/arm/lib/relocate.S中,稍后会详细分析此函数。

继续回到示例代码12.2.3.1第149行,调用函数c_runtime_cpu_setup,此函数定义在文件arch/arm/cpu/armv7/start.S中 ,函数内容如下:
示例代码12.2.3.5 start.S代码段截图
第170行,设置函数board_init_r的两个参数,函数board_init_r声明如下:

board_init_r(gd_t *id, ulong dest_addr)

第一个参数是gd,因此读取r9保存到r0里面。

第171行,设置函数board_init_r的第二个参数是目的地址,因此r1=gd->relocaddr。

第177行、调用函数board_init_r,此函数定义在文件common/board_r.c中,稍后会详细的分析此函数。

这个就是_main函数的运行流程,在_main函数里面调用了board_init_f、relocate_code、relocate_vectors和board_init_r这4个函数,接下来依次看一下这4个函数的作用

board_init_f函数详解

_main中会调用board_init_f函数,board_init_f函数主要有两个工作:

  1. 初始化一系列外设,比如串口、定时器,或者打印一些消息等。
  2. 初始化gd的各个成员变量,uboot会将自己重定位到DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止Linux kernel覆盖掉uboot,将DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。 这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。

board_init_f函数定义在文件common/board_f.c中定义,代码如下:
示例代码12.2.4.1 board_f.c代码段截图
第1023行,初始化gd->flags=boot_flags。

第1024行,设置gd->have_console=0。

重点在第1026行!通过函数initcall_run_list来运行初始化序列init_sequence_f里面的一系列函数,init_sequence_f里面包含了一系列的初始化函数,init_sequence_f也定义在文件common/board_f.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_f定义如下:

示例代码12.2.4.2 board_f.c代码段 
/*****************去掉条件编译语句后的 init_sequence_f***************/ 
1 static const init_fnc_t init_sequence_f[] = { 
2     setup_mon_len, 
3     fdtdec_setup, 
4     trace_early_init, 
5 
6     initf_malloc, 
7     log_init, 
8     initf_bootstage, /* uses its own timer, so does not need DM */ 
9     bloblist_init, 
10    setup_spl_handoff, 
11    initf_console_record, 
12    arch_fsp_init, 
13    arch_cpu_init, /* basic arch cpu dependent setup */ 
14    mach_cpu_init, /* SoC/machine dependent CPU setup */ 
15    initf_dm, 
16    arch_cpu_init_dm, 
17    board_early_init_f, 
18    get_clocks, /* get CPU and bus clocks (etc.) */ 
19    timer_init, /* initialize timer */ 
20    board_postclk_init, 
21    env_init, /* initialize environment */ 
22    init_baud_rate, /* initialze baudrate settings */ 
23    serial_init, /* serial communications setup */ 
24    console_init_f, /* stage 1 init of console */ 
25    display_options, /* say that we are here */ 
26    display_text_info, /* show debugging info if required */ 
27    checkcpu, 
28    print_resetinfo, 
29    print_cpuinfo, /* display cpu info (and speed) */ 
30    embedded_dtb_select, 
31    show_board_info, 
32    INIT_FUNC_WATCHDOG_INIT 
33    misc_init_f, 
34    INIT_FUNC_WATCHDOG_RESET 
35    init_func_i2c, 
36    init_func_vid, 
37    announce_dram_init, 
38    dram_init, /* configure available RAM banks */ 
39    post_init_f,
40    INIT_FUNC_WATCHDOG_RESET 
41    testdram, 
42    INIT_FUNC_WATCHDOG_RESET 
43    init_post, 
44    INIT_FUNC_WATCHDOG_RESET 
45    /* 
46    * Now that we have DRAM mapped and working, we can 
47    * relocate the code and continue running from DRAM. 
48    * 
49    * Reserve memory at end of RAM for (top down in that order): 
50    * - area that won't get touched by U-Boot and Linux (optional) 
51    * - kernel log buffer 
52    * - protected RAM 
53    * - LCD framebuffer 
54    * - monitor code 
55    * - board info struct 
56    */ 
57    setup_dest_addr, 
58    reserve_pram, 
59    reserve_round_4k, 
60    reserve_mmu, 
61    reserve_video, 
62    reserve_trace, 
63    reserve_uboot, 
64    reserve_malloc, 
65    reserve_board, 
66    setup_machine, 
67    reserve_global_data, 
68    reserve_fdt, 
69    reserve_bootstage, 
70    reserve_bloblist, 
71    reserve_arch, 
72    reserve_stacks, 
73    dram_init_banksize, 
74    show_dram_config, 
75    setup_board_part1, 
76    INIT_FUNC_WATCHDOG_RESET 
77    setup_board_part2, 
78    display_new_sp, 
79    fix_fdt, 
80    INIT_FUNC_WATCHDOG_RESET 
81    reloc_fdt, 
82    reloc_bootstage,
83    reloc_bloblist, 
84    setup_reloc, 
85    copy_uboot_to_ram, 
86    do_elf_reloc_fixups, 
87    clear_bss, 
88    clear_bss, 
89    jump_to_copy, 
90    NULL, 
91 };
  • 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

接下来分析以上函数执行完以后的结果:

第2行,setup_mon_len函数设置gd的mon_len成员变量,此处为__bss_end-_start,也就是整个代码的长度。0XC01B966C-0XC0100000=0XB966C,这个就是代码长度

第3行,fdtdec_setup函数设置gd的fdt_dlob指针变量,fdt_blob保存着设备树(.dtb)文件地址。此处为_image_binary_end,也就是我们的设备地址为0XC01C2938

第4行,CONFIG_TRACE_EARLY宏没有定义不会运行此函数。

第6行,initf_malloc函数初始化gd中跟malloc有关的成员变量,比如malloc_limit,此函数会设置gd->malloc_limit =CONFIG_SYS_MALLOC_F_LEN=0X3000。 malloc_limit表示malloc内存池大小

第7行,log_init 函数将struct log_driver结构体加入到gd->log_head的循环链表中,并初始化gd->default_log_level。

第8行,initf_bootstage函数为gd->bootstage分配空间,并初始化gd->bootstage。

第9行,CONFIG_BLOBLIST宏没有定义,此函数不执行。

第10行,setup_spl_handoff函数和SPL相关,STM32MP1是使用TF-A引导U-BOOT,此函数啥也没有做。

第11行initf_console_record如果定义了宏CONFIG_CONSOLE_RECORD和宏CONFIG_SYS_MALLOC_F_LEN,此函数就会调用函数console_record_init,但是STM32MP1的uboot没有定义宏CONFIG_CONSOLE_RECORD,所以此函数直接返回0。

第12行,如果定义CONFIG_HAVE_FSP宏,就会调用arch_fsp_init函数。STM32MP1没有定义此宏。

第13行,arch_cpu_init函数,初始化架构相关的内容,CPU级别的操作。

第14行,mach_cpu_init函数,初始化SOC相关的内容,CPU级别的操作。

第15行,initf_dm函数,驱动模型有关的初始化操作, 如果定义了CONFIG_DM,则调用dm_init_and_scan初始化并扫描系统所有的device。如果定义了CONFIG_TIMER_EARLY调用dm_timer_init初始化driver model所需的timer。STM32MP1定义了CONFIG_DM,会扫描所有的device。

第16行,arch_cpu_init_dm函数为空函数。

第17行,如果定义CONFIG_BOARD_EARLY_INIT_F,则调用board_early_init_f接口,执行板级的early初始化。这边STM32MP1没有定义。

第18行 ,用于获取一些时钟值。

第19行,timer_init,初始化定时器。主要用来初始化gd->arch.timer_rate_hz,设置定时器频率。

第20行,CONFIG_BOARD_POSTCLK_INIT宏没有定义,所以函数不会调用。

第21行,env_init函数是和环境变量有关的,设置gd的成员变量env_addr,也就是环境变量的保存地址。

第22行,init_baud_rate函数用于初始化波特率,根据环境变量baudrate来初始化gd->baudrate。

第23行,serial_init,初始化串口,设置gd的flags成员变量为GD_FLG_SERIAL_READY开启当前选中的串口。

第24行,console_init_f,设置gd->have_console 为1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。

第25行,display_options,通过串口输出一些信息,比如uboot版本号,编译时间等,如下图所示:
串口信息输出
第26行,display_text_info,打印一些文本信息,如果开启UBOOT的DEBUG功能的话就会输出text_base、bss_start、bss_end。开启DEBUG方法很简单,在对应的板子的配置头文件里面定义DEBUG即可,比如在stm32mp1.h文件中添加下面一行:

#define DEBUG //使能 DEBUG

使能以后重新编译uboot,然后使用新的uboot启动,这个时候会输出大量的debug信息,如下图所示:
文本信息
上图中“U-Boot code: C0100000->C01C66E8 BSS: -> C01D57B4”就是输出的一条debug信息,包含了uboot代码起始地址,BSS段起始地址。还有其他很多debug信息,不建议打开DEBUG,因为 debug信息太多了,严重影响正常开发!

第27行,PPC、X86、SH架构的芯片会执行,ARM架构不会执行

第28行,如果使能了CONFIG_SYSRESET宏的话,会打印一些系统复位信息。

第29行,print_cpuinfo函数用于打印CPU信息,结果如下图所示:
CPU信息
第30行,CONFIG_DTB_RESELECT宏没有定义所以此函数不执行。

第31行,show_board_info函数用于打印板子信息,如果在uboot中使用了设备树,那么此函数会先从设备树里面获取到model属性信息并且打印出出来。然后会调用checkboard函数获取板子信息并打印出来,结果如下图所示:
板子信息
第32行,INIT_FUNC_WATCHDOG_INIT,初始化看门狗 ,对于STM32MP1来说是空函数。

第33行,CONFIG_MISC_INIT_F宏没有定义,所以此函数不会执行。

第34行,INIT_FUNC_WATCHDOG_RESET,复位看门狗,对于STM32MP1来说是空函数。

第35行,CONFIG_SYS_I2C宏没有定义,所以此函数不会执行。

第36行,init_func_vid是一个空函数,返回值为0。

第37行,announce_dram_init,此函数很简单,就是输出字符串。

第38行,dram_init,并非真正的初始化DDR,只是设置gd->ram_size的值,对于正点原子STM32MP1开发板核来说,DDR大小为1G,所以gd->ram_size的值为0X40000000

第39行,CONFIG_POST宏没有定义,此函数不执行。

第41行,testdram,测试DRAM,空函数。

第43行,CONFIG_POST宏没有定义,此函数不执行。

第57行,setup_dest_addr函数,设置目的地址,设置gd->ram_base 、gd->ram_size、gd->ram_top、gd->relocaddr这三个的值。接下来我们会遇到很多跟数值有关的设置,如果直接看代码分析的话就太费时间了,可以修改uboot代码,直接将这些值通过串口打印出来,比如这里打开文件common/board_f.c,找到setup_dest_addr函数,在函数输入如下图所示内容:
添加printf函数打印成员变量值
设置好以后重新编译uboot,然后烧写到SD卡中,选择SD卡启动,重启开发板,打开MobaXterm,uboot会输出如下图所示信息:
信息输出
从上图可以看出:

gd->ram_base = 0XC0000000 //DDR起始地址 0XC0000000
gd->ram_size = 0X40000000 //DDR大小为 0X40000000=1G
gd->ram_top = 0XF6000000 //DDR最高地址为 0XF6000000
gd->relocaddr = 0XF6000000 //重定位后最高地址为 0XF6000000

这里有一些需要讲解,为什么ram_top(也就是DDR)最高地址是0XF6000000,不应该是0XC0000000+0X40000000 -1=0XFFFFFFFF。这是因为0XF6000000-0XFFFFFFFF这段内存分配
给了GPU和OPTEE,打开设备树arch/arm/dts/stm32mp157d-atk.dts文件,找到如下内容:
内存保留配置
结合上面两张图得知,rom最高为0XF600000,剩余的0XA000000预留给GPU和optee使用。其中0XF6000000-0XFDFFFFFF分配给了GPU,0XFE000000-0XFFFFFFFF分配给了OPTEE。

第59行,reserve_round_4k函数用于对gd->relocaddr做4KB对齐,因为gd->relocaddr=0XF6000000,已经是4K对齐了,所以调整后不变。

第60行,reserve_mmu,留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对gd->relocaddr做64K字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr和gd->relocaddr如下图所示:
信息输出
从上图可以看出:

gd->arch.tlb_size = 0X4000 //MMU的TLB表大小
gd->arch.tlb_addr = 0XF5FF0000 //MMU的TLB表起始地址,64KB对齐以后
gd->relocaddr = 0XF5FF0000 //relocaddr地址

第61行,reserve_video函数,留出显示的内存,分配显示内存后gd->relocaddr如如图所示:
信息输出
从上图可以知道,留出显示内存以后,gd->relocaddr=0XF5D00000,显示相关内存大小为0X2F0000。

第62行,reserve_trace函数,留出跟踪调试的内存,STM32MP1没有用到最终功能,因此无需留出对应的内存区域。

第63行,reserve_uboot留出重定位后的uboot所占用的内存区域,uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置gd->start_addr_sp,结果如下图所示:
信息输出
从上图可以看出:

gd->mon_len = 0XB98EC
gd->start_addr_sp = 0XF5C46000
gd->relocaddr = 0XF5C46000

gd->mon_len是当前uboot大小,只要在uboot里面添加了其他代码,那么这个编译出来的大小是会变的,所以如果发现正点原子教程的其他地方mon_len不等于0XB98EC不要以为写错了。

第64行,reserve_malloc,留出malloc区域以及MMU相关内存。malloc区域由宏TOTAL_MALLOC_LEN定义,宏定义如下:

#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN+CONFIG_ENV_SIZE)

.config文件中定义宏CONFIG_SYS_MALLOC_LEN为32MB=0X2000000,宏CONFIG_ENV_SIZE=8KB=0X2000,因此TOTAL_MALLOC_LEN=0X2002000。此时gd->start_addr_sp=0XF5C46000-0X2002000=0XF3C44000。

reserve_malloc还通过reserve_noncached函数来预留出MMU相关内存。经过各种调整以后gd->start_addr_sp如下图所示:
信息输出
从上图可以看出,最终预留出的malloc和MMU相关内存范围为0XF5C46000~0XF3AFFFF,此时start_addr_sp变成了0XF3B00000。

第65行,reserve_board函数,留出板子bd所占的内存区,bd是结构体bd_t bd_t大小为80字节,结果如下图所示:
信息输出
从上图可以看出:

gd->bd=0XF3AFFFB0
gd->start_addr_sp=0XF3AFFFB0

第66行,setup_machine,设置机器ID,linux启动的时候会和这个机器ID匹配,如果匹配的话linux就会启动正常。但是!!STM32MP1不用这种方式了,这是以前老版本的uboot和linux使用的,新版本使用设备树了,因此此函数无效

第67行,reserve_global_data函数,保留出新的gd_t的内存区域,gd_t结构体大小为240B,结果如下图所示:
信息输出
从上图可以看出,此时新的gd地址为0XF3AFFEC0。

gd->start_addr_sp=0XF3AFFEC0 //0XF3AFFFB0-240=0XF3AFFEC0
gd->new_gd=0XF3AFFEC0

第68行,reserve_fdt函数,留出设备树相关的内存区域,首先计算出设备树的空间大小,计算公式如下:

gd->fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob) + 0x1000, 32);

fdt_totalsize函数负责计算出设备树的大小gd->fdt_blob是代码重定位前设备树地址。旧
的地址大小加4KB,在以32字节对齐。reserve_fdt函数运行后的结果如下图所示:
信息输出
从上图可以看出:

gd->fdt_blob = 0XC01C2C50 //设备树的旧位置
gd->fdt_size= 0X11F00 //设备树.dtb大小为 0X11F00
gd->new_fdt= 0XF3AEDFC0 //0XF3AFFEC0-0X11F00 = 0XF3AEDFC0
gd->start_addr_sp = 0XF3AEDFC0

第69行,reserve_bootstage函数,保留bootstage的内存区域。结果如下图所示:
信息输出
从上图可以看出,新的bootstage地址为0XF3AEDD40。

第70行,此函数不执行。

第71行,reserve_arch是个空函数。

第72行,reserve_stacks,留出栈空间。如果使能IRQ的话还要留出IRQ相应的内存,具体工作是由arch/arm/lib/stack.c文件中的函数arch_reserve_stacks完成。结果如下图所
示:
信息输出
至此,uboot内存重定位完成。

第73行,dram_init_banksize函数设置dram信息,就是设置gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size,后面会传递给linux内核,告诉linux DRAM的起始地址和大小。结果如下图所示:
信息输出
从上图可以看出,DRAM的起始地址为0XC0000000,大小为0X40000000(1G)。

第74行,show_dram_config函数,用于显示DRAM的配置,如下图所示:
信息输出
第78行,display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如下图所示:
信息输出
从上图中的gd->start_addr_sp值和我们前面分析的最后一次修改的值一致。

第81行,reloc_fdt函数用于重定位fdt,使用memcpy函数将原来存储在gd->fdt_blob中的设备树文件拷贝到gd->new_fdt处,然后将gd->new_fdt赋值给gd->fdt_blob。经过这一步设备树拷贝到了新地址,而且gd->fdt_blob也保存了新的设备树地址。

第82行,reloc_bootstage函数用于bootstage重定位,使用memcpy函数将gd->bootstage中原来的bootstage拷贝到新的gd->new_bootstage处。然后将gd->new_bootstage赋值给gd->bootstage。

第84行,setup_reloc,设置gd的其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出,如下图所示:
信息输出
从上图可以看出,uboot重定位后的偏移为0X35b46000,重定位后的新地址为0XF5C46000,新的gd首地址为0XF3AFFEC0,最终的sp为0XF3AEDD20。

至此,board_init_f函数就执行完成了,最终的内存分配如下图所示:
最终的内存分配图

relocate_code函数详解

relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下:

示例代码12.2.5.1 relocate.S 代码段
80 ENTRY(relocate_code)
81     ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
82     subs r4, r0, r1 /* r4 <- relocation offset */
83     beq relocate_done /* skip relocation */
84     ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
85 
86 copy_loop: 
87     ldmia r1!, {r10-r11} /* copy from source address [r1] */ 
88     stmia r0!, {r10-r11} /* copy to target address [r0] */ 
89     cmp r1, r2 /* until source end address [r2] */ 
90     blo copy_loop 
91 
92     /* 
93     * fix .rel.dyn relocations 
94     */ 
95     ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ 
96     ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */ 
97 fixloop: 
98     ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */ 
99     and r1, r1, #0xff 
100    cmp r1, #R_ARM_RELATIVE 
101    bne fixnext 
102 
103    /* relative fix: increase location by offset */ 
104    add r0, r0, r4 
105    ldr r1, [r0] 
106    add r1, r1, r4 
107    str r1, [r0] 
108 fixnext: 
109    cmp r2, r3 
110    blo fixloop 
111 
112 relocate_done: 
113 
114 #ifdef __XSCALE__ 
115    /* 
116    * On xscale, icache must be invalidated and write buffers 
117    * drained,even with cache disabled - 4.2.7 of xscale core 
118    * developer's manual */ 
119    mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */ 
120    mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */ 
121 #endif 
122 
123 /* ARMv4- don't know bx lr but the assembler fails to see that */ 
124 
125 #ifdef __ARM_ARCH_4__ 
126    mov pc, lr 
127 #else
128    bx lr 
129 #endif 
130 
131 ENDPROC(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

第81行, r1=__image_copy_start,也就是r1寄存器保存源地址,由之前的图可知,__image_copy_start=0XC0100000。

第82行,r0=0XF5C46000,这个地址就是uboot拷贝的目标首地址。r4=r0-r1=0XF5C46000-0XC0100000=0X35B46000,因此r4保存偏移量。

第83行,如果在第81行中,r0-r1等于 0,说明r0和r1相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了!执行relocate_done函数。

第84行,r2=__image_copy_end,r2中保存拷贝之前的代码结束地址,由最开始的白变量截图可知,__image_copy_end =0XC01ACD98

第85行,函数copy_loop完成代码拷贝工作!从r1,也就是__image_copy_start开始,读取uboot代码保存到r10和r11中,一次就只拷贝这2个32位的数据。拷贝完成以后r1的值会更新,保存下一个要拷贝的数据地址。

第88行,将r10和r11的数据写到r0开始的地方,也就是目的地址。写完以后r0的值会更新,更新为下一个要写入的数据地址。

第89行,比较r1是否和r2相等,也就是检查是否拷贝完成,如果不相等的话说明没有拷贝完成,没有拷贝完成的话就跳转到copy_loop接着拷贝,直至拷贝完成。

接下来的第95行-110行是重定位.rel.dyn段,.rel.dyn是.relocation.dynamic的简写,也就是动态重定位,.rel.dyn段是存放.text段中需要重定位地址的集合。重定位就是uboot将自身拷贝到DRAM的另一个地放去继续运行(DRAM的高地址处)。我们知道,一个可执行的bin文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,这样寻址的时候不会出问题吗?为了分析这个问题,需要在board/st/stm32mp1/stm32mp1.c中输入如下所示内容:

示例代码12.2.5.1 stm32mp1.c新添代码段 
1 static int rel_a = 0; 
2 
3 void rel_test(void) 
4 { 
5     rel_a += 100; 
6     printf("rel_a = %d\r\n", rel_a); 
7 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

最后还需要在stm32mp1.c文件中的board_init函数里面调用rel_test函数,否则rel_reset不会被编译进uboot。修改完成后的stm32mp1.c如下图所示:
加入rel测试相关代码
board_init函数会调用rel_test,rel_test会调用全局变量rel_a。

重新编译uboot,编译完成以后,使用arm-none-inux-gnueabihf-objdump将编译出来的u-boot进行反汇编,得到u-boot.dis这个汇编文件,命令如下:

arm-none-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis

在u-boot.dis文件中找到rel_a、rel_rest和board_init,相关内容如下所示:

示例代码12.2.5.2 汇编文件代码段 
1 c010619c <rel_test>: 
2 c010619c: 4b03 ldr r3, [pc, #12] ; (c01061ac <rel_test+0x10>) 
3 c010619e: 4804 ldr r0, [pc, #16] ; (c01061b0 <rel_test+0x14>) 
4 c01061a0: 6819 ldr r1, [r3, #0] 
5 c01061a2: 3164 adds r1, #100 ; 0x64 
6 c01061a4: 6019 str r1, [r3, #0] 
7 c01061a6: f06d bbe6 b.w c0173976 <printf> 
8 c01061aa: bf00 nop 
9 c01061ac: c01ad378 andsgt sp, sl, r8, ror r3 
10 c01061b0: c018de67 andsgt sp, r8, r7, ror #28 
11 
12 c01061b4 <board_init>: 
13 c01061b4: b530 push {r4, r5, lr} 
14 c01061b6: b089 sub sp, #36 ; 0x24 
15 c01061b8: f7ff fff0 bl c010619c <rel_test> 
16 
17 ...... 
18 
19 c01ad378 <rel_a>: 
20 c01ad378: 00000000 andeq r0, r0, r0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

第15行是borad_init调用rel_test函数,用到了bl指令,而bl指令是位置无关指令,bl指令是相对寻址的(pc+offset),因此uboot中函数调用是与绝对位置无关的。

再来看一下函数rel_test对于全局变量rel_a的调用,第2行设置r3的值为pc+12地址处的值,根据后面的提示,这个地址就是rel_test+0X10,也就是0XC010619C+0X10=0XC01061AC。第9行就是0XC01061AC这个地址,0XC01061AC处的值为0XC01AD378。根据第19行可知,0XC01AD378正是变量rel_a的地址,最终r3=0XC01AD378。

第4行,读取r3地址出的值,保存到r1寄存器面,也就是读取rel_a的值保存到r1里面。

第5行,给r1里面的值加100,也就是给rel_a的值加100。

第6行,将r1内的值写到r3地址处,这就是示例代码代码12.2.5.1中的第5行:rel_a += 100。

总结一下rel_a+=100的汇编执行过程:

  1. 在函数rel_test末尾处有一个地址为0XC01061AC的内存空间 (示例代码12.2.5.2第7行),此内存空间保存着变量rel_a的地址;
  2. 函数rel_test要想访问变量rel_a,首先访问末尾的0XC01061AC来获取变量rel_a的地址,而访问0XC01061AC是通过偏移来访问的,很明显是个位置无关的操作;
  3. 通过0XC01061AC获取到变量rel_a的地址,对变量rel_a进行操作;
  4. 可以看出,函数rel_test对变量rel_a的访问没有直接进行,而是使用了一个第三方偏移地址0XC01061AC,专业术语叫做Label。这个第三方偏移地址就是实现重定位后运行不会出错的重要原因!

uboot重定位后偏移为0X35B46000,那么重定位后函数rel_test的首地址就是0XC010619C+0X35B46000=0XF5C4C19C保存变量rel_a地址的Label就是0XF5C4C19+0X10=0XF5C4C1AC变量rel_a的地址就为0XC01AD378+0X35B46000=0XF5CF3378重定位后函数rel_test要想正常访问变量rel_a就得设置 0XF5C4C1AC(重定位后的 Label)地址处的值为0XF5CF3378 (重定位后的变量 rel_a地址)。这样就解决了重定位后链接地址和运行地址不一致的问题。

可以看出,uboot对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码,在使用ld进行链接的时候使用选项“-pie”生成位置无关的可执行文件。在文件arch/arm/config.mk下有如下代码:

示例代码12.2.5.3 汇编文件代码段 
102 # needed for relocation 
103 LDFLAGS_u-boot += -pie
  • 1
  • 2
  • 3

第103行就是设置uboot链接选项,加入了“-pie”选项。编译链接 uboot 的时候会使用到“-pie”选项,如下图所示:
链接命令
使用“-pie”选项以后会生成一个.rel.dyn段,uboot就是靠这个.rel.dyn来解决重定位问题的,在u-bot.dis的.rel.dyn段中有如下所示内容::

示例代码12.2.5.4 rel.dyn代码段 
1 Disassembly of section .rel.dyn: 
2 
3 c01ad2d8 <__efi_runtime_rel_stop>: 
4 c01ad2d8: c0100020 andsgt r0, r0, r0, lsr #32 
5 c01ad2dc: 00000017 andeq r0, r0, r7, lsl r0 
6 ......
7 c01ade08: c01061ac andsgt r6, r0, ip, lsr #3 
8 c01ade0c: 00000017 andeq r0, r0, r7, lsl r0 
9 c01ade10: c01061b0 ; <UNDEFINED> instruction: 0xc01061b0 
10 c01ade14: 00000017 andeq r0, r0, r7, lsl r0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

先来看一下.rel.dyn段的格式,类似第7行和第8行这样的是一组,也就是两个4字节数据
为一组
高4字节(第7行的0XC01ADE0C)是Label地址标识,值为0X17。低4字节(第7行的0XC01ADE08)就是Label的地址。首先判断Label地址标识是否正确,也就是判断高4字节是否为0X17,如果是的话低4字节就是Label地址值。第7行值为0XC01061AC,第8行为0X00000017,说明第7行的0XC01061AC是个 Label。

继续返回示例代码12.2.5.1中。
第95行,r2=__rel_dyn_start,也就是.rel.dyn段的起始地址。

第96行,r3=__rel_dyn_end,也就是.rel.dyn段的终止地址。

第97行,从.rel.dyn段起始地址开始,每次读取两个4字节的数据存放到r0和r1寄存器中,r0存放低4字节的数据,也就是Label地址;r1存放高4字节的数据,也就是Label标志。

第99行,r1中给的值与0xff进行与运算,其实就是取r1的低8位。

第100行,判断r1中的值是否等于23(R_ARM_RELATIVE经过编译以后为23)。

第101行,如果r1不等于23的话就说明不是描述Label的,执行函数fixnext,否则的话继续执行下面的代码。

第104行,r0保存着 Label值,r4保存着重定位后的地址偏移,r0+r4就得到了重定位后的Label值。此时r0保存着重定位后的Label值,相当于0XC01006F0+0X35B46000=0XF5C466F0。

第105行,读取重定位后Label所保存的变量地址,此时这个变量地址还是重定位前的(相当于rel_a重定位前的地址0XC01006F0),将得到的值放到r1寄存器中。

第106行,r1+r4即可得到重定位后的变量地址,相当于rel_a重定位后的0XC01061AC+0X35B46000=0XF5C4C1AC。

第107行,重定位后的变量地址写入到重定位后的Label中,相等于设置地址0XC01061AC处的值为0XF5C4C1AC。

第109行,比较r2和r3,查看.rel.dyn段重定位是否完成。

第110行,如果r2和r3不相等,说明.rel.dyn重定位还未完成,因此跳到fixloop继续重定位.rel.dyn段。

可以看出,uboot中对.rel.dyn段的重定位方法和猜想的一致。.rel.dyn段的重定位比较复杂一点,有点绕,因为涉及到链接地址和运行地址的问题

relocate_vectors函数详解

relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S 中,代码如
下:

示例代码12.2.6.1 relocate.S代码段 
28 ENTRY(relocate_vectors) 
29 
30 #ifdef CONFIG_CPU_V7M 
31     /* 
32     * On ARMv7-M we only have to write the new vector address 
33     * to VTOR register. 
34     */ 
35     ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
36     ldr r1, =V7M_SCB_BASE 
37     str r0, [r1, V7M_SCB_VTOR] 
38 #else 
39 #ifdef CONFIG_HAS_VBAR 
40     /* 
41     * If the ARM processor has the security extensions, 
42     * use VBAR to relocate the exception vectors. 
43     */ 
44     ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ 
45     mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */ 
46 #else 
47     /* 
48     * Copy the relocated exception vectors to the 
49     * correct address 
50     * CP15 c1 V bit gives us the location of the vectors: 
51     * 0x00000000 or 0xFFFF0000. 
52     */ 
53     ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ 
54     mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */ 
55     ands r2, r2, #(1 << 13) 
56     ldreq r1, =0x00000000 /* If V=0 */ 
57     ldrne r1, =0xFFFF0000 /* If V=1 */ 
58     ldmia r0!, {r2-r8,r10} 
59     stmia r1!, {r2-r8,r10} 
60     ldmia r0!, {r2-r8,r10} 
61     stmia r1!, {r2-r8,r10} 
62 #endif 
63 #endif 
64     bx lr 
65 
66 ENDPROC(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

第30行,如果定义了CONFIG_CPU_V7M的话就执行第30-36行的代码,这是Cortex-M内核单片机执行的语句,因此对于STM32MP157来说是无效的。

第39行,如果定义了CONFIG_HAS_VBAR的话就执行此语句,这个是向量表偏移,Cortex-A7是支持向量表偏移的。而且,在.config里面定义了CONFIG_HAS_VBAR,因此会执行这个分支。

第44行,r0=gd->relocaddr,也就是重定位后uboot的首地址,向量表肯定是从这个地址开始存放的。

第45行,将r0的值写入到CP15的VBAR寄存器中,也就是将新的向量表首地址写入到寄存器VBAR中,设置向量表偏移。

board_init_r函数详解

之前已经讲解了board_init_f函数,在此函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数board_init_r来完成的,board_init_r函数定义在文件common/board_r.c中,代码如下:

示例代码12.2.7.1 board_init_r代码段 
844 void board_init_r(gd_t *new_gd, ulong dest_addr) 
845 { 
...... 
869 
870    if (initcall_run_list(init_sequence_r)) 
871    	   hang(); 
872 
873     /* NOTREACHED - run_main_loop() does not return */ 
874     hang(); 
875 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第870行调用initcall_run_list函数来执行初始化序列init_sequence_r,init_sequence_r是一个函数集合,init_sequence_r也定义在文件common/board_r.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件
编译以后的init_sequence_r定义如下:

示例代码12.2.7.2 init_sequence_r结构体 
1 static init_fnc_t init_sequence_r[] = { 
2     initr_trace, 
3     initr_reloc, 
4     initr_caches, 
5     initr_reloc_global_data, 
6     initr_unlock_ram_in_cache, 
7     initr_barrier, 
8     initr_malloc, 
9     log_init, 
10    initr_bootstage, /* Needs malloc() but has its own timer */ 
11    initr_console_record, 
12    initr_noncached, 
13    initr_of_live, 
14    initr_dm, 
15    board_init, /* Setup chipselects */ 
16    set_cpu_clk_info, /* Setup clock information */ 
17    efi_memory_init, 
18    stdio_init_tables, 
19    initr_serial, 
20    initr_announce, 
21    initr_watchdog, 
22    INIT_FUNC_WATCHDOG_RESET 
23    initr_manual_reloc_cmdtable, 
24    initr_trap,
25    initr_addr_map, 
26    board_early_init_r, 
27    INIT_FUNC_WATCHDOG_RESET 
28    initr_post_backlog, 
29    INIT_FUNC_WATCHDOG_RESET 
30    initr_pci, 
31    arch_early_init_r, 
32    power_init_board, 
33    initr_flash, 
34    INIT_FUNC_WATCHDOG_RESET 
35    cpu_init_r, 
36    initr_nand, 
37    initr_onenand, 
38    initr_mmc, 
39    initr_env, 
40    initr_malloc_bootparams, 
41    INIT_FUNC_WATCHDOG_RESET 
42    initr_secondary_cpu, 
43    mac_read_from_eeprom, 
44    INIT_FUNC_WATCHDOG_RESET 
45    initr_pci, 
46    stdio_add_devices, 
47    initr_jumptable, 
48    initr_api, 
49    console_init_r, /* fully init console as a device */ 
50    console_announce_r, 
51    show_board_info, 
52    arch_misc_init, /* miscellaneous arch-dependent init */ 
53    misc_init_r, /* miscellaneous platform-dependent init */ 
54    INIT_FUNC_WATCHDOG_RESET 
55    initr_kgdb, 
56    interrupt_init, 
57    initr_enable_interrupts, 
58    timer_init, /* initialize timer */ 
59    initr_status_led, 
60    initr_ethaddr, 
61    gpio_hog_probe_all, 
62    board_late_init, 
63    INIT_FUNC_WATCHDOG_RESET 
64    initr_scsi, 
65    initr_bbmii, 
66    INIT_FUNC_WATCHDOG_RESET 
67    initr_net,
68    initr_post, 
69    initr_ide, 
70    INIT_FUNC_WATCHDOG_RESET 
71    last_stage_init, 
72    INIT_FUNC_WATCHDOG_RESET 
73    initr_bedbug, 
74    initr_mem, 
75    run_main_loop, 
76 };
  • 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

第2行,initr_trace函数,如果定义了宏CONFIG_TRACE的话就会调用函数trace_init
初始化和调试跟踪有关的内容。

第3行,initr_reloc函数用于设置gd->flags,标记重定位完成。

第4行,initr_caches函数用于初始化cache,使能 cache。

第5行,initr_reloc_global_data函数,初始化重定位后gd的一些成员变量。

第6行,CONFIG_SYS_INIT_RAM_LOCK宏没有定义,因此函数不执行。

第7行,CONFIG_PPC宏没有定义,因此函数不执行。

第8行,initr_malloc函数,初始化malloc。

第9行,空函数。

第10行,初始化bootstage相关的内容。

第11行,初始化控制台相关内容。

第12行,保留MMU相关的1M内存。

第13行,CONFIG_OF_LIVE宏没有定义,此函数不会运行。

第14行,初始化设备树,主要是解析设备树,创建一个树形结构,根节点挂载在gd->dm_root。

第15行,board_init函数,板级初始化,包括时钟、电压。这里执行的是board/st/stm32mp1/stm32mp1.c文件中的board_init函数。

第16行,CONFIG_CLOCKS宏没有定义,此函数不执行。

第17行,efi相关初始化。

第18行,stdio_init_tables函数,stdio相关初始化。

第19行,initr_serial函数,初始化串口。

第20行,initr_announce函数,与调试有关,通知已经在RAM中运行。

第21行,初始化看门狗,串口输出如下图所示:
看门狗信息输出
第23行,CONFIG_NEEDS_MANUAL_RELOC宏没有定义,此函数不执行。

第24行,此函数不执行。

第25行,CONFIG_ADDR_MAP没有定义,此函数不执行。

第26行,CONFIG_BOARD_EARLY_INIT_R宏没有定义,此函数不执行。

第28行,CONFIG_POST宏没有定义,此函数不执行。

第30行,CONFIG_PCI宏没有定义,此函数不执行。

第31行,CONFIG_ARCH_EARLY_INIT_R宏没有定义,此函数不执行。

第32行,初始化电源,此函数为空函数。

第33行,CONFIG_MTD_NOR_FLASH宏没有定义,此函数不执行。

第35行,此函数不执行。

第36行,initr_nand函数,初始化NAND Flash,STM32MP1支持NAND,并且也定义了CONFIG_CMD_NAND宏,因此此函数会执行。正点原子STM32MP1开发板没有使用NAND,因此识别出来NAND为0MiB,如下图所示:
NAND信息输出
第37行,CONFIG_CMD_ONENAND宏没有定义,此函数不执行。

第38行,initr_mmc函数,初始化EMMC,串口输出如下图所示信息:
EMMC信息输出
从上图可以看出,此时有两个EMCM设备,MMC:0和MMC:1。

第39行,initr_env函数,初始化环境变量,如果在 EMMC中保存了环境变量,那么就会提示从EMMC中读取环境变量,如下图所示:
从EMMC中读取环境变量
如果EMMC中没有保存环境变量,那么就会输出“bad CRC”类的信息。

第40行,CONFIG_SYS_BOOTPARAMS_LEN宏没有定义,此函数不执行。

第42行,initr_secondary_cpu函数,初始化其他 CPU核。

第43行,CONFIG_ID_EEPROM宏没有定义,此函数不执行。

第46行,stdio_add_devices函数,各种输入输出设备的初始化。

第47行,initr_jumptable函数,初始化跳转表。

第48行,CONFIG_API宏没有定义,此函数不执行。

第49行,console_init_r函数,控制台初始化,初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备,如下图所示:
控制台信息
第50-51行,CONFIG_DISPLAY_BOARDINFO_LATE宏没有定义,这两个函数不执行。

第52行,arch_misc_init函数,设置一些其他的东西,函数的路径为:arch/arm/mach-stm32mp/cpu.c。这里重点是从STM32MP1的OTP里面读取MAC地址来设置“ethaddr”环境变量,信息如下图所示:
从OTP获取MAC地址
从上图可以看出,从OTP读取到的MAC地址是错误的,因为我们没有在OTP里面设置MAC地址。

第53行,CONFIG_MISC_INIT_R宏没有定义,此函数不执行。

第55行,CONFIG_CMD_KGDB宏没有定义,此函数不执行。

第56行,interrupt_init函数,初始化中断。

第57行,initr_enable_interrupts函数,使能中断。

第58-59行,函数不执行。

第60行,根据bd->bi_enetaddr变量成员,去设置ethaddr的MAC地址。

第61行,gpio_hog_orobe_all函数,初始化uboot下的gpio驱动框架。

第62行,board_late_init函数,板子后续初始化,此函数定义在文件board/st/stm32mp1/stm32mp1.c中。

第64-65行,此函数不执行。

第67行,网络的初始化,输出的信息如下图所示:
网络初始化
上图中由于没有设置网络相关地址信息,因此网络初始化失败, 提示“No ethernet found”。如果设置了网络地址环境变量,那么就会初始化成功网络,输出信息如下:
网络初始化成功
第68-74行,这里的函数都不执行。

第75行,run_main_loop行,主循环,处理命令。

run_main_loop函数详解

uboot启动以后会进入N(N=1.2.3…)秒倒计时,如果在N秒倒计时结束之前按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内核,这个功能就是由run_main_loop函数来完成的。run_main_loop函数定义在文件common/board_r.c中,函数内容如下:

示例代码12.2.8.1 run_main_loop函数 
645 static int run_main_loop(void) 
646 { 
647 #ifdef CONFIG_SANDBOX 
648     sandbox_main_loop_init(); 
649 #endif 
650     /* main_loop() can return to retry autoboot, if so just run it again */ 
651     for (;;) 
652         main_loop(); 
653     return 0; 
654 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第651行和第652行是个死循环,“for(;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/75878
推荐阅读
相关标签