基于IMX6Q的uboot启动流程分析(1):uboot入口函数

基于IMX6Q的uboot启动流程分析(1):uboot入口函数
基于IMX6Q的uboot启动流程分析(2):_main函数之board_init_f
基于IMX6Q的uboot启动流程分析(3):_main函数之relocate_code与board_init_r
基于IMX6Q的uboot启动流程分析(4):uboot中的串口设备

第1章:uboot入口函数

1.1 uboot.lds文件

在分析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));
 }
...
}

从文件内容得知:

  1. ENTRY(_start):入口函数名,在arch/arm/lib/vectors.S文件中定义,后面详细介绍;

  2. __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
    

1.2 _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函数执行。

1.3 start.S文件的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

以上代码主要的工作是:

  1. 设置为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]这五位用来设置处理器的工作模式 ,如下图所示
    基于IMX6Q的uboot启动流程分析(1):uboot入口函数_第1张图片

  2. 跳转cpu_init_cp15函数;

  3. 跳转cpu_init_crit函数;

  4. 跳转_main函数。

1.4 start.S文件的cpu_init_cp15函数

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相关。

1.5 start.S文件的cpu_init_crit函数

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,后面详细介绍。

1.6 lowlevel_init函数

上一节的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)

以上内容为:

  1. 设置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

基于IMX6Q的uboot启动流程分析(1):uboot入口函数_第2张图片

  1. sp 指针减去GD_SIZE ,将sp传入9寄存器 :

    	sub	sp, sp, #GD_SIZE
    	bic	sp, sp, #7
    	mov	r9, sp
    

    其中GD_SIZE定义在include/generated/generic-asm-offsets.hGD_SIZE=248

    sp = 0x0093FF00 - 248
       = 0x0093FE08
    

    此时sp的位置为:
    基于IMX6Q的uboot启动流程分析(1):uboot入口函数_第3张图片

  2. 将ip和lr进行压栈

  3. 跳转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函数,下节将详细介绍。

1.7 调用流程总结

_start函数调用流程总结为:
基于IMX6Q的uboot启动流程分析(1):uboot入口函数_第4张图片
下一章将介绍uboot中重要的_main函数。

你可能感兴趣的:(uboot,嵌入式,uboot,IMX6Q)