基于IMX6Q的uboot启动流程分析(1):uboot入口函数
基于IMX6Q的uboot启动流程分析(2):_main函数之board_init_f
基于IMX6Q的uboot启动流程分析(3):_main函数之relocate_code与board_init_r
基于IMX6Q的uboot启动流程分析(4):uboot中的串口设备
在分析uboot(uboot2021)之前,需要将uboot源码进行编译,会生成一些分析时需要用到的文件,比如链接文件uboot.lds
和映射文件uboot.map
。通过查看uboot.lds
文件可以得到uboot执行的第一个函数,uboot.lds
文件部分内容如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start) //入口函数
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
arch/arm/cpu/armv7/start.o (.text*)
}
.__efi_runtime_start : {
*(.__efi_runtime_start)
}
.efi_runtime : {
*(.text.efi_runtime*)
*(.rodata.efi_runtime*)
*(.data.efi_runtime*)
}
.__efi_runtime_stop : {
*(.__efi_runtime_stop)
}
.text_rest :
{
*(.text*)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.efi_runtime_rel_start :
{
*(.__efi_runtime_rel_start)
}
.efi_runtime_rel : {
*(.rel*.efi_runtime)
*(.rel*.efi_runtime.*)
}
.efi_runtime_rel_stop :
{
*(.__efi_runtime_rel_stop)
}
. = ALIGN(4);
.image_copy_end :
{
*(.__image_copy_end)
}
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
.end :
{
*(.__end)
}
_image_binary_end = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
...
}
从文件内容得知:
ENTRY(_start):入口函数名,在arch/arm/lib/vectors.S
文件中定义,后面详细介绍;
__image_copy_start:uboot 拷贝的首地址0x17800000,在uboot.map
文件中可查得,通过CONFIG_SYS_TEXT_BASE
来设置。
*(.__image_copy_start)
.__image_copy_start
0x0000000017800000 0x0 arch/arm/lib/built-in.o
0x0000000017800000 __image_copy_start
_start函数在arch/arm/lib/vectors.S
文件中定义,其内容为:
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
ARM_VECTORS //中断向量表的宏
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
_start函数执行的是ARM_VECTORS
,其定义在同文件中,内容为:
.macro ARM_VECTORS //中断向量表的宏定义
#ifdef CONFIG_ARCH_K3
ldr pc, _reset
#else
b reset //中断向量表,跳转到reset
#endif
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
.endm
中断向量表,实际跳转到reset
函数执行。
reset
函数定义在文件arch/arm/cpu/armv7/start.S
中,start.S文件是uboot启动的重要文件。函数内容如下:
reset:
/* Allow the board to save important registers */
b save_boot_params //跳到save_boot_params,可不查看,最终调用下一行的save_boot_params_ret
save_boot_params_ret:
#ifdef CONFIG_ARMV7_LPAE
/*
* check for Hypervisor support
*/
mrc p15, 0, r0, c0, c1, 1 @ read ID_PFR1
and r0, r0, #CPUID_ARM_VIRT_MASK @ mask virtualization bits
cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
beq switch_to_hypervisor
switch_to_hypervisor_ret:
#endif
/*
* 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
/*
* 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 //读取SCTLR寄存器
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
#ifdef CONFIG_HAS_VBAR
/* Set vector address in CP15 VBAR register */
ldr r0, =_start //设置vector地址到CP15 VBAR寄存器
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
#endif
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
#ifdef CONFIG_CPU_V7A
bl cpu_init_cp15 //跳转到cpu_init_cp15
#endif
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
bl cpu_init_crit //跳转到cpu_init_crit
#endif
#endif
bl _main //跳转到_main
以上代码主要的工作是:
设置为SVC模式,禁止FIQ和IRQ;
mrs r0, cpsr //读取cpsr寄存器的值到r0寄存器中(cpsr:bit0~bit4保存处理器工作模式)
and r1, r0, #0x1f //r0的值与0x1f相与,结果保存到r1寄存器
teq r1, #0x1a //判断当前处理器模式是否是HYP模式
bicne r0, r0, #0x1f //如果CPU不处于HYP模式,则清除bit0~bit4
orrne r0, r0, #0x13 //设置为SVC模式
orr r0, r0, #0xc0 //禁止FIQ和IRQ
msr cpsr,r0 //将当前r0寄存器的值回写到cpsr寄存器
将寄存器r0中的值与0X1F进行与运算,结果保存到r1寄存器中,目的就是提 cpsr的bit0~bit4这5位,这5位为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式 ,如下图所示
跳转cpu_init_cp15函数;
跳转cpu_init_crit函数;
跳转_main函数。
cpu_init_cp15函数的主要内容如下:
/*************************************************************************
*
* cpu_init_cp15
*
* Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
* CONFIG_SYS_ICACHE_OFF is defined.
*
*************************************************************************/
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
#if CONFIG_IS_ENABLED(SYS_ICACHE_OFF)
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endif
mcr p15, 0, r0, c1, c0, 0
#ifdef CONFIG_ARM_ERRATA_716044
mrc p15, 0, r0, c1, c0, 0 @ read system control register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c1, c0, 0 @ write system control register
#endif
#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 4 @ set bit #4
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_743622
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 6 @ set bit #6
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
...
以上工作的主要内容是:关闭I\D cache,关闭MMU等,都是与CP15相关。
cpu_init_crit函数的主要内容如下:
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
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 //跳到lowlevel_init,设置pll、mux和memory
ENDPROC(cpu_init_crit)
可以看出函数cpu_init_crit
内部仅仅是调用了函数lowlevel_init
,设置pll、mux和memory,后面详细介绍。
上一节的cpu_init_crit
函数中仅仅调用的lowlevel_init
函数,其位置为arch/arm/cpu/armv7/lowlevel_init.S
,其内容主要为:
WEAK(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =CONFIG_SPL_STACK
#else
ldr sp, =CONFIG_SYS_INIT_SP_ADDR //设置sp指针指向CONFIG_SYS_INIT_SP_ADDR
#endif
bic sp, sp, #7 //sp指针8字节对齐处理
#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 //sp = sp - 248
bic sp, sp, #7 //sp指针8字节对齐处理
mov r9, sp
#endif
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr} //将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/mx6sxsabresd.h
文件中,其定义为:
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM
#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)
大小计算过程如下:
CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000
CONFIG_SYS_INIT_SP_OFFSET = CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE
= IRAM_SIZE - GENERATED_GBL_DATA_SIZE
= 0x00040000 - 256
= 0x0003FF00
CONFIG_SYS_INIT_SP_ADDR = CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET
= 0x00900000 + 0x0003FF00
= 0x0093FF00
经过上述计算后,得到sp的地址为0x0093FF00。
补充:
IRAM_BASE_ADDR
定义在arch/arm/include/asm/arch-mx6/imx-regs.h
IRAM_SIZE
定义在arch/arm/include/asm/arch-mx6/imx-regs.h
,IMX6Q为256k,IMX6U为128k
GENERATED_GBL_DATA_SIZE
定义在include/generated/generic-asm-offsets.h
sp 指针减去GD_SIZE ,将sp传入9寄存器 :
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
其中GD_SIZE
定义在include/generated/generic-asm-offsets.h
,GD_SIZE=248
sp = 0x0093FF00 - 248
= 0x0093FE08
将ip和lr进行压栈
跳转s_init函数
s_init
函数定义在arch/arm/mach-imx/mx6/soc.c
文件中,配置PFD相关,可暂不研究。
s_init
函数执行完成后返回 lowlevel_init
函数,lowlevel_init
函数将之前存储的lr寄存器值恢复到pc,程序返回。接着一路返回到 cpu_init_crit 函数被调用时候的返回地址,也就是start.S汇编,接下跳转_main
函数,下节将详细介绍。