一、第一部分
要分析 uboot的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。
打开u-boot.lds文件看到第三行,可以发现_start是代码的入口点。
ENTRY(_start)
_start在文件 arch/arm/lib/vectors.S中有定义,定义如下:
.globl _start
/*
*************************************************************************
*
* Vectors have their own section so linker script can map them easily
*
*************************************************************************
*/
.section ".vectors", "ax"
/*
*************************************************************************
*
* Exception vectors as described in ARM reference manuals
*
* Uses indirect branch to allow reaching handlers anywhere in memory.
*
*************************************************************************
*/
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_start后面就是中断向量表,从图中的.section ".vectors", "ax”可以得到,此代码存放在 .vectors段里面。
u-boot.map是 uboot的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,我们将两个文件对比起来看:vectors段的起始地址是 0X87800000,说明整个 uboot的起始地址就是0X87800000,__image_copy_start顾名思义为镜像拷贝的起始地址也为0X87800000。之后是各个文件的代码段链接到整个中断向量表地址之后,
.text :
{
*(.__image_copy_start)
*(.vectors)
arch/arm/cpu/armv7/start.o (.text*)
*(.text*)
}
.text 0x0000000087800000 0x3cd64
*(.__image_copy_start)
.__image_copy_start
0x0000000087800000 0x0 arch/arm/lib/built-in.o
0x0000000087800000 __image_copy_start
*(.vectors)
.vectors 0x0000000087800000 0x300 arch/arm/lib/built-in.o
0x0000000087800000 _start
0x0000000087800020 _undefined_instruction
0x0000000087800024 _software_interrupt
0x0000000087800028 _prefetch_abort
0x000000008780002c _data_abort
0x0000000087800030 _not_used
0x0000000087800034 _irq
0x0000000087800038 _fiq
0x0000000087800040 IRQ_STACK_START_IN
arch/arm/cpu/armv7/start.o(.text*)
.text 0x0000000087800300 0xb0 arch/arm/cpu/armv7/start.o
在 u-boot.lds中有一些跟地址有关的“变量”需要我们注意一下,后面分析 u-boot源码的时候会用到,这些变量要最终编译完成才能确定的!!!比如我编译完成以后这些“变量”的值为:
变量 | 数值 | 描述 |
__image_copy_start | 0x87800000 | uboot拷贝的首地址 |
__image_copy_end | 0x8784f1a4 | uboot拷贝的结束地址 |
__rel_dyn_start | 0x8784f1a4 | .rel.dyn段起始地址 |
__rel_dyn_end | 0x8785794c | .rel.dyn段结束地址 |
_image_binary_end | 0x8785794c | 镜像结束地址 |
__bss_start | 0x8784f1a4 | .bss段起始地址 |
__bss_end | 0x8789a194 | .bss段结束地址 |
.image_copy_end
0x000000008784f1a4 0x0
*(.__image_copy_end)
.__image_copy_end
0x000000008784f1a4 0x0 arch/arm/lib/built-in.o
.rel_dyn_start 0x000000008784f1a4 0x0
*(.__rel_dyn_start)
.__rel_dyn_start
0x000000008784f1a4 0x0 arch/arm/lib/built-in.o
.rel_dyn_end 0x000000008785794c 0x0
*(.__rel_dyn_end)
.__rel_dyn_end
0x000000008785794c 0x0 arch/arm/lib/built-in.o
.end 0x000000008785794c 0x0
*(.__end)
.__end 0x000000008785794c 0x0 arch/arm/lib/built-in.o
0x000000008785794c _image_binary_end = .
0x0000000087858000 . = ALIGN (0x1000)
.bss_start 0x000000008784f1a4 0x0
*(.__bss_start)
.__bss_start 0x000000008784f1a4 0x0 arch/arm/lib/built-in.o
0x000000008784f1a4 __bss_start
0x000000008784f1a4 __bss_base = .
.bss_end 0x000000008789a194 0x0
*(.__bss_end)
.__bss_end 0x000000008789a194 0x0 arch/arm/lib/built-in.o
0x000000008789a194 __bss_end
现在开始分析_start,第一行就跳到reset函数执行,reset函数在 arch/arm/cpu/armv7/start.S里面,代码如下:
.globl reset
.globl save_boot_params_ret
reset:
/* Allow the board to save important registers */
b save_boot_params
——————————————————————————————————————————————————————————
ENTRY(save_boot_params)
b save_boot_params_ret @ back to my caller
——————————————————————————————————————————————————————————
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
进入reset后,又跳到了save_boot_params函数,在save_boot_params函数中,又跳到了save_boot_params_ret中执行。进入此函数后,我们看到了对cpsr寄存器进行操作,我们知道该寄存器的bit0~bit4这5位是用来控制处理器的模式,在此函数中主要用到了两个模式:
M[4:0] | 模式 |
11010 | Hyp(hyp) |
10011 | Supervisor(svc) |
具体的代码分析过程为:读取寄存器 cpsr中的值,并保存到 r0寄存器中。判断 r1寄存器的值是否等于 0X1A(0b11010),也就是判断当前处理器模式是否处于 Hyp模式。如果 r1和 0X1A不相等也就是CPU不处于Hyp模式的话就将r0寄存器的bit0~4进行清零,其实就是清除模式位,且将 r0的寄存器的值与 0x13进行或运算,也就是设置处理器进入SVC模式。 r0寄存器的值再与 0xC0进行或运算,那么 r0寄存器此时的值就是 0xD3。cpsr寄存器的 bit7和bit6分别控制IRQ和 FIQ这两个中断的开关,设置为1就关闭了 FIQ和 IRQ。将 r0寄存器写回到 cpsr寄存器中。完成设置 CPU处于 SVC模式,并且关闭FIQ和 IRQ这两个中断。
继续往下看:
* 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
我们没有定义CONFIG_OMAP44XX和CONFIG_SPL_BUILD条件成立,继续执行。
首先了解一下MCR和MRC指令的格式,格式如下:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
MRC的指令格式和 MCR一样,只不过在 MRC指令中Rt就是目标寄存器,也就是从CP15指定寄存器读出来的数据会保存在 Rt中。而 CRn就是源寄存器,也就是要读取的写处理器寄存器。
接下来的两张图是正点原子关于CRn分别为c1和c12寄存器的表示所指代的寄存器
CR_V在 arch/arm/include/asm/system.h中有如下所示定义:
#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */
第一个MRC指令就是将寄存器 SCTLR写到R0中,接着位清除指令目的就是清除SCTLR寄存器中的 bit13,此位是向量表控制位,当为0的时候向量表基地址为0X00000000,软件可以重定位向量表。为1的时候向量表基地址为 0XFFFF0000,软件不能重定位向量表。这里将该位清零,目的就是为了接下来的向量表重定位,将 r0寄存器的值重新写入到寄存器 SCTLR中。
接着设置 r0寄存器的值为 _start,_start就是整个 uboot的入口地址,其值为 0X87800000相当于 uboot的起始地址,因此 0x87800000也是向量表的起始地址。行将 r0寄存器的值 (向量表值 )写入到 CP15的c12寄存器中,也就是VBAR寄存器 ,对向量表进行重定位。
接着分析:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
bl _main
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
函数 cpu_init_cp15都是一些和 CP15有关的内容,我们不用关心。cpu_init_crit则调用了lowlevel_init,接着我们来重点分析一下lowlevel_init和_main函数。
lowlevel_init函数详解
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)
首先设置sp指向 CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR在include/configs/mx6ullevk.h文件中,定义如下:
#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
#define CONFIG_SYS_INIT_SP_OFFSET \
(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \
(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)
IRAM_BASE_ADDR和 IRAM_SIZE在文件arch/arm/include/asm/arch-mx6/imx-regs.h中有定义, 在 .config中定义了 CONFIG_MX6UL,所以条件不成立,因此 IRAM_SIZE=0X20000=128KB。
#define IRAM_BASE_ADDR 0x00900000
#if !(defined(CONFIG_MX6SX) || defined(CONFIG_MX6UL) || \
defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
#define IRAM_SIZE 0x00040000
#else
#define IRAM_SIZE 0x00020000
#endif
GENERATED_GBL_DATA_SIZE的值在文件 include/generated/generic-asm-offsets.h中有定义,如下:
#define GENERATED_GBL_DATA_SIZE 256 /* (sizeof(struct global_data) + 15) & ~15 @ */
#define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */
#define GD_SIZE 248 /* sizeof(struct global_data) @ */
#define GD_BD 0 /* offsetof(struct global_data, bd) @ */
#define GD_MALLOC_BASE 188 /* offsetof(struct global_data, malloc_base) @ */
#define GD_RELOCADDR 44 /* offsetof(struct global_data, relocaddr) @ */
#define GD_RELOC_OFF 64 /* offsetof(struct global_data, reloc_off) @ */
#define GD_START_ADDR_SP 60 /* offsetof(struct global_data, start_addr_sp) @ */
综上所述:
CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 - 256 = 0x1FF00。
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0X1FF00 = 0X0091FF00
可以看到,一个变量的赋值就疯狂套娃,所以大家最好跟着文档一起找一遍(虽然过程很烦)。
先8字节对齐,接着sp指针减去GD_SIZE,GD_SIZE 同样在generic-asm-offsets.h 中定义了,大小为248字节。接着对sp做8字节对齐,此时sp 的地址为0X0091FF00-248=0X0091FE08。附上正点的图。
接着将sp地址保存在r9寄存器中。将ip和lr压栈并调用函数s_init,函数调用完毕后将入栈的ip和lr进行出栈,并将lr赋给pc。
对于I.MX6UL/I.MX6ULL 来说,s_init就是个空函数。从s_init函数退出以后进入函数lowlevel_init,
但是lowlevel_init 函数也执行完成了,返回到了函数cpu_init_crit,函数cpu_init_crit也执行完成了,最终返回到save_boot_params_ret,准备调用_main函数。篇幅原因,这个放在下一篇文章分析(其实是要学晕了)。
开个小番外,我们看到这里很多地方用到了字节对齐。需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,原本我们只需要一次数据读写就可以完成操作,但现在因为没有数据对齐就需要多次用不同变量类型读取。
bic sp, sp, #7
我们发现文中有这样一行代码,它能做8字节对齐的原因是它将该值低三位清零,其实就是将该值对8的余数进行了抹去。
标签:r0,arch,流程,Boot,start,init,详解,寄存器,CONFIG From: https://blog.csdn.net/qq_64033704/article/details/145166083