uboot启动流程

uboot启动流程如下:

一、首先查看arch/arm/cpu/u-boot.lds链接脚本

二、_start位于arch/arm/cpu/arm920t/start.S

三、_start会跳转到start_code处

四、进入第一个C函数:board_init_f()

五、接下来进入重定位

六、清除bss段

七、跳转到board_init_r()函数

1、首先查看arch/arm/cpu/u-boot.lds链接脚本

在进行uboot的链接启动流程分析之前,我们要先看一下uboot的链接脚本。在C语言中,程序可以分为以下几个部分:

  1. 代码段(.text)

  1. 数据段(.data)

  1. 只读数据段(.rodata)

  1. 未初始化的数据段(.bss)

  1. 堆和栈

其中堆和栈属于动态区域,在程序运行时动态分配和释放。而代码段、数据段、只读数据段在连接之后产生。一个C语言程序分为映像和运行时两种状态:

  1. 映象只包含代码段、数据段、只读数据段;

  1. 运行时还将包含动态形成的堆和栈区域

而链接脚本的作用就是:

  1. 指定代码段和数据段、只读数据段在内存中的存放地址

  1. 指定代码的入口地址

如:uboot-2012.04.01的uboot的lds文件,其部分代码

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x00000000;

    . = ALIGN(4);
    .text :
    {
        __image_copy_start = .;
        CPUDIR/start.o (.text)
                board/samsung/smdk2440/libsmdk2440.o (.text)
        *(.text)
    }  
    
    . = ALIGN(4);
    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

    . = ALIGN(4);
    .data : { 
        *(.data)
    }   

    . = ALIGN(4);

    . = .;
    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;
    
    ...
}

从上面的代码我们可以看到.text段,其指定了所有uboot文件的.text段的存放位置。往下是.rodata段,其指定只读数据的存放位置,紧跟着.text段,并且进行了四字节的对齐操作,再往下是数据段.data的存放位置。其中有一个__u_boot_cmd_start,它充当一个标号的作用,其指定了boot_cmd的起始地址,cmd也就是我们在boot的shell使用的那些指令,而__u_boot_cmd_end指定的是结束地址。

uboot程序的入口地址,在第3行的ENTRY(_start)处指定了

uboot代码追踪

我们可以使用arm-linux-nm u-boot显示我们生成的uboot文件的符号信息

...
33f9964c T xyzModem_stream_open
33f993c0 T xyzModem_stream_read
33f9905c T xyzModem_stream_terminate
...

有了这个命令后,我们就可以很方便的查找到我们的uboot启动代码的地址,通过arm-linux-nm u-boot | grep _start,我们找到我们的起始地址

33fada14 A __bss_start
33fad4bc A __u_boot_cmd_start
33f80044 T _armboot_start
33f80048 T _bss_start
33f80000 T _start
33f80184 t _start_armboot
33fb2cbc b bin_start_address
33fadaf8 b mem_malloc_start
33f9daf8 t setup_start_tag

知道了地址,我们怎么定位文件的位置?可以通过arm-linux-addr2line来定位,其可以将地址转成行号输出显示出来

  -b --target=<bfdname>  Set the binary file format
  -e --exe=<executable>  Set the input file name (default is a.out)
  -i --inlines           Unwind inlined functions
  -j --section=<name>    Read section-relative offsets instead of addresses
  -s --basenames         Strip directory names
  -f --functions         Show function names
  -C --demangle[=style]  Demangle function names
  -h --help              Display this information
  -v --version           Display the program's version

我们可以通过arm-linux-addr2line -e u-boot 33f80000找到我们起始地址所在的文件,以及其所在的文件中的位置

上图是uboot的启动代码流程分析

  1. 首先会先执行start.S,其主要完成的工作将CPU设置为SVC模式(保护模式)、关闭中断、看门狗等

  1. 接着会执行lowlevel_init.S的代码,主要完成一些初始化工作

  1. 接着开始进入C语言程序,在进行C语言程序之前需要先开辟一个栈空间,由ctr0.S来完成

  1. 进入C语言程序后,执行board_init_f()函数,进行自搬移操作前期的内存分配

  1. 自搬移由crt0.S来完成,然后第二次初始化C语言的运行环境,接着程序就完全进入C语言环境执行

  1. board_init_r()会进行MMC和网络初始化,然后进入自启动模式,如果在倒计时之前按下回车就进入main loop循环,也就是我们看到的uboot的shell界面。

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

uboot源码分析

了解了整个uboot大致的启动流程后,我们就可以开始着手进行uboot的源代码分析。我们从start.S文件开始分析,也就是uboot的入口地址_start

...
.globl _start
_start: 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
...

在第3行有一条跳转指令b reset跳转到reset标号处去执行,在reset下面的那些ldr xxxx指令适用于后面设置中断向量表使用的,reset标号处的代码如下

reset:
    bl  save_boot_params
    /*
     * 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

从注释中我们可以看到,其作用就是关闭中断,将CPU设置为SVC32模式

接着_start的代码往下走

/*
 * 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 SCTRL register - for VBAR to point to vector */
    mrc p15, 0, r0, c1, c0, 0   @ Read CP15 SCTRL Register
    bic r0, #CR_V       @ V = 0
    mcr p15, 0, r0, c1, c0, 0   @ Write CP15 SCTRL 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
    bl  cpu_init_crit
#endif

    bl  _main

从注释中可以看到,行2~14大致完成内容是设置SCTRL寄存器,也就是系统控制器以及设置中断向量表,这个中断向量表的内容就是我们前面在reset下面的那些ldr xxxx指令,这个需要借助CPU的协处理器CP15进行设置

完成上面的操作后,会跳转到cpu_init_cp15和cpu_init_crit标号执行,最后转到_main函数执行

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
    
    ...
    
#ifdef CONFIG_ARM_ERRATA_742230
    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
    ...
    
ENDPROC(cpu_init_cp15)

从代码的注释可以看出,该标号的代码抓哟完成关闭缓存、关闭虚拟内存MMU的作用,以及设置了diagnostic寄存器

接着回到reset的cpu_init_crit继续往下追踪

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_crit处的代码很简单,就是调用lowlevel_init进行初始化

lowlevel_init主要完成板级相关的一些初始化工作,如:时钟、内存、网卡、串口的初始化

接着我们回到reset的_main继续往下追踪。_main标号定义在文件arch/arm/lib/crt0.S里面,可以通过arm-linux-nm和arm-linux-address2line进行定位

下面我们对_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
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    sub sp, #GD_SIZE    /* allocate one GD above SP */
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    mov r8, sp      /* GD is above SP */
    mov r0, #0
    bl  board_init_f

#if ! defined(CONFIG_SPL_BUILD)

从注释看出,_main首先初始化了C的运行环境,接着调用board-init_f函数。除此以外进行sp寄存器的设置。然后是board_init_f;

void board_init_f(ulong bootflag)
{
    bd_t *bd;
    init_fnc_t **init_fnc_ptr;
    gd_t *id;
    ulong addr, addr_sp;

    ...
        
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
       if ((*init_fnc_ptr)() != 0) {
           hang ();
        }
    }
    
    ...
}

其中有两个最重要的全局变量bd_t *bd和gd_t *id它们用于存储一些信息,这两个全局变量的存储空间就是在调用board_init_f之前已经在栈中分配了

board_init_f主要完成两个工作,一是对gd_t *id进行初始化,二是调用一系列的函数完成一些工作

    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, [r8, #GD_START_ADDR_SP] /* r8 = gd->start_addr_sp */
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    ldr r8, [r8, #GD_BD]        /* r8 = gd->bd */
    sub r8, r8, #GD_SIZE        /* new GD is below bd */

    adr lr, here
    ldr r0, [r8, #GD_RELOC_OFF]     /* lr = gd->start_addr_sp */
    add lr, lr, r0
    ldr r0, [r8, #GD_RELOCADDR]     /* r0 = gd->relocaddr */
    b   relocate_code

在最后一行有一个b relocate_code跳转指令,就是对代码进行自搬移操作

here:

/* Set up final (full) environment */

    bl  c_runtime_cpu_setup /* we still call old routine here */

    ldr r0, =__bss_start    /* this is auto-relocated! */
    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 */
    strlo   r2, [r0]        /* clear 32-bit BSS word */
    addlo   r0, r0, #4      /* move to next */
    blo clbss_l

    bl coloured_LED_init
    bl red_led_on

    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov r0, r8          /* gd_t */
    ldr r1, [r8, #GD_RELOCADDR] /* dest_addr */
    /* call board_init_r */
    ldr pc, =board_init_r   /* this is auto-relocated! */

    /* we should not return here. */

#endif

搬移结束后,就进入C语言环境运行

从注释可以看到设置了bss段,也就是对bss段进行了清零,标号clbss_1标号处的循环代码。

之后就会通过board_init_r标号,跳转到C语言函数

void board_init_r(gd_t *id, ulong dest_addr)
{
    ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
    ulong flash_size;
#endif
    
    ...
    
    /* main_loop() can return to retry autoboot, if so just run it again. */
    for (;;) {
        main_loop();
    } 
    
    ...
}    

board_init_r会打印一些信息到串口,然后进入到main_loop()循环,如果此时按下回车就会进入到uboot的shell交互界面,否则就会自动引导os系统

uboot的启动如下,假设我们使用SD卡进行启动;

  1. iROM会将SD卡内的BL1加载到iRAM中,然后将控制器交给BL1

  1. BL1会将BL2以及填充,一共16K的代码,加载到iRAM

uboot由两部分组成,一部分是SPL完成硬件相关操作,另一部分是uboot代码
  1. 接着SPL部分会将uboot代码加载到DRAM之中,此时uboot位于DRAM的内存地址0x43E00000处

  1. uboot加载需要加载linux系统的uImage到内存,一般加载到0x4008000处,为了防止把uboot自己给覆盖了,所以uboot还要进行一次自搬移操作

  1. 然后CPU跳转新的uboot地址执行,这是我们就能看到串口大一些信息了

正常宿主机对u-boot完成编译后,要对编译好的uboot进行移植烧录,将一个编译好的u-boot.bin文件烧写到sd卡或者是开发板的flash存储器中

  1. 在开发板上电后,板载CPU来引导internal iROM引导flash开始执行工作,检测boot loader是否有效并将boot loader读取到SRAM

  1. 在SRAM将boot loader启动的同时初始化DRAM控制器以扩展内存,之后再boot loader的引导下将u-boot的镜像读取到DRAM中,u-boot启动开始等待指令输出或者加载启动内核

  1. 所对应的函数存放在arch/arm/cpu/u-boot.lds链接脚本中

参考

https://blog.youkuaiyun.com/m0_55873979/article/details/124852411

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值