0%

Linux驱动学习日记(7) Uboot学习(二)

程序入口

  分析u-boot.lds链接脚本(图1)可以得知,程序的入口是一个名为_start的函数,哲理的_start指的是文件arch/arm/lib/vector.S中的函数,如图2所示。可以看到,_start后面是中断向量表,从图中的“.section “.vectors”, “ax””可以看出此代码存放在.vectors段中。

图 1 u-boot.lds
图 2 vector.S

  u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从图3的 932 行可以看到__image_copy_start 为 0X87800000,而.text 的起始地址也是 0X87800000。

图 3 u-boot.map
这里的__image_copy_start只是一个标记,并不占用实际空间。.vector段的内容包含于.text段。

U-Boot启动流程

  从图2中可以得知_start函数运行后先跳转到了一个名为reset的函数,这个函数定义在arch/arm/cpu/armv7/start.S,代码如下:

1
2
3
reset:
/* Allow the board to save important registers */
b save_boot_params

  reset又跳转到了save_boot_params函数,save_boot_params函数又跳转到了save_boot_params_ret函数,save_boot_params_ret代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
save_boot_params_ret:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0

  这一段的主要功能就是设置处理器进入SCV模式,并且关闭FIQ和IRQ这两个中断,之后再执行下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register

/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif

/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
cpu_init_crit
#endif
bl _main

  这段代码先清理了SCTLR.V位,告诉CPU别再用老的向量表,用VBAR指定的位置,然后设置VBAR为0x87800000,在异常发生时让CPU从0x87800000开始找向量表。接下来就是执行cpu_init_cp15函数,这函数也是在start.S中,这个函数都是一些和CP15有关的内容,通常会进行关闭MMU、配置cache等操作。cpu_init_crit也是定义在start.S中,它仅仅调用了lowlevel_init函数,这个函数定义在arch/arm/cpu/armv7/lowlevel_init.S,内容如下:

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
#include <asm-offsets.h>
#include <config.h>
#include <linux/linkage.h>

ENTRY(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
/*
* Set up global data for boards that still need it. This will be
* removed soon.
*/
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr}

/*
* Call the very early init function. This should do only the
* absolute bare minimum to get started. It should not:
*
* - set up DRAM
* - use global_data
* - clear BSS
* - try to start a console
*
* For boards with SPL this should be empty since SPL can do all of
* this init in the SPL board_init_f() function which is called
* immediately after this.
*/
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)

pc寄存器正在执行的指令
ip是“临时中转寄存器”
lr是保存返回地址的寄存器,无法嵌套调用

  这段代码第一步先建立了一个临时栈,给CPU一个可以用的栈指针;第二步决定要不要用global_data(r9),gd是一个通过r9间接访问的一块内存,再U-Boot启动过程中,gd先被放在临时SRAM里,等DDR初始化完成后进行重定位,gd会被memcpy到DDR,r9会更新为新地址;第三步保存ip和lr;第四步调用s_init()函数;第五步将两个数据出栈,此时的pc指向的是调用lowlevel_init指令之后的那句语句(lr入栈时的数据)。

  s_init定义在文件arch/arm/cpu/armv7/mx6/soc.c中。这个函数中会判断当前 CPU 类型,如果 CPU 为 MX6SX、 MX6UL、 MX6ULL 或 MX6SLL 中的任意一种,那么就会直接返回,相当于 s_init 函数什么都没做。所以对于 I.MX6UL/I.MX6ULL 来说, s_init 就是个空函数。从 s_init 函数退出以后进入函数 lowlevel_init,但是 lowlevel_init 函数也执行完成了,返回到了函数 cpu_init_crit,函数 cpu_init_crit 也执行完成了,最终返回到 save_boot_params_ret。

graph LR
    subgraph Init_Loop[初始化过程]
        save_boot_params_ret --> cpu_init_crit
        cpu_init_crit --> lowlevel_init
        lowlevel_init --> s_init
        s_init --> save_boot_params_ret
    end
    save_boot_params_ret -->|初始化完成| _main

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

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
ENTRY(_main)

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

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve

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 sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */

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 */
b relocate_code
here:
/*
* now relocate vectors
*/

bl relocate_vectors

/* Set up final (full) environment */

bl c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# 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
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
/* 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 defined(CONFIG_SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif

ENDPROC(_main)

  _main多次切栈,多次切gd,做了一次代码的整体移动,最终不返回,它负责把系统从 裸启动态 推进到 可长期运行态
①、建立最初可用的C环境,这是早期SRAM.ORCAM栈,不是最终栈,只为board_init_f服务。

1
2
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7

②、预留早期内存。第一步从当前栈顶向下预留空间,再把gd放进去,最后让r9指向新的gd。这段代码中,board_init_f_alloc_reserve会从当前sp向下切一块内存,预留gd、early malloc、board info后返回新的r0,所以需要更新sp和r9寄存器的值。

1
2
3
4
5
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
mov r9, r0
bl board_init_f_init_reserve

③、board_init_f函数,主要进行初始化时钟 / PLL、初始化 DDR、填充gd->ram_size、gd->relocaddr、gd->reloc_off。

④、切换到真正的DDR栈、新gd。这几句代码将在SRAM的栈和gd转到了DDR中。

1
2
3
4
5
ldr sp, [r9, #GD_START_ADDR_SP]
bic sp, sp, #7

ldr r9, [r9, #GD_BD]
sub r9, r9, #GD_SIZE

⑤、代码重定位。先将lr保存为目前代码的地址,然后将其加上relocation偏移后就变成了here在DDR中应该对应的地址,relocate_code将整个U-Boot从Falsh/Rom复制到了DDR,并跳回lr指向的位置,此时的PC已经在DDR中。

1
2
3
4
5
6
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF]
add lr, lr, r0

ldr r0, [r9, #GD_RELOCADDR]
b relocate_code

⑥、回到here并清BSS。然后跳入最终初始化函数board_init_r。

图 4 最终内存分配图

总结

U-Boot 启动过程中,内存的使用经历了从寄存器到 SRAM、再到 DDR 的逐步解锁过程。通过临时栈、early reserve 区和可迁移的 global_data,U-Boot 在没有 C 运行时的情况下完成了 DDR 初始化和代码重定位,最终建立起完整、稳定的 C 运行内存环境。