U-BOOT小全(四):BootLoader源码(SPL-UBoot 1)

简介: U-BOOT小全(四):BootLoader源码(SPL-UBoot 1)

对于概念性我们有了一定的了解以后,来瞅瞅UBoot的源码。

不过在这之前,整个图来回以一下前面讲的UBoot的启动跳转和硬件布局。

BootRom引导SPL,SPL引导U-Boot,然后U-Boot再引导内核。

下面我们来看看这个源码是什么样子的?

SPL代码追踪–start.S

从前面分析的u-boot-spl.lds链接文件可知,启动代码就是arch/arm/cpu/armv7/start.S。start.S主要做的事情就是初始化系统的各个方面。

从大的方面分,可以分成这几个部分:

1)设置CPU模式。
2)关闭看门狗。
3)关闭中断。
4)设置堆栈sp指针。
5)清除bss段。
6)异常中断处理。
/*
 * armboot - Startup Code for OMAP3530/ARM Cortex CPU-core
 *
 * Copyright (c) 2004        Texas Instruments <r-woodruff2@ti.com>
 *
 * Copyright (c) 2001        Marius Gr ger <mag@sysgo.de>
 * Copyright (c) 2002        Alex Züpke <azu@sysgo.de>
 * Copyright (c) 2002        Gary Jennejohn <garyj@denx.de>
 * Copyright (c) 2003        Richard Woodruff <r-woodruff2@ti.com>
 * Copyright (c) 2003        Kshitij <kshitij@ti.com>
 * Copyright (c) 2006-2008 Syed Mohammed Khasim <x0khasim@ti.com>
 *
 * SPDX-License-Identifier:        GPL-2.0+
 */
#include <asm-offsets.h>
#include <config.h>
#include <version.h>
#include <asm/system.h>
#include <linux/linkage.h>
.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
#ifdef CONFIG_SPL_BUILD
_undefined_instruction:        .word _undefined_instruction
_software_interrupt:        .word _software_interrupt
_prefetch_abort:        .word _prefetch_abort
_data_abort:        .word _data_abort
_not_used:        .word _not_used
_irq:        .word _irq
_fiq:        .word _fiq
_pad:        .word 0x12345678 /* now 16*4=64 */
#else
.globl _undefined_instruction
_undefined_instruction:      .word undefined_instruction
.globl _software_interrupt
_software_interrupt:         .word software_interrupt
.globl _prefetch_abort
_prefetch_abort:             .word prefetch_abort
.globl _data_abort
_data_abort:                 .word data_abort
.globl _not_used
_not_used:                   .word not_used
.globl _irq
_irq:                        .word irq
.globl _fiq
_fiq:                        .word fiq
_pad:                        .word 0x12345678 /* now 16*4=64 */
#endif        /* CONFIG_SPL_BUILD */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef

首先使用.globl_start声明_start变量,并且告诉链接器此变量是全局的,外部可以访问,所以在arch/arm/cpu/armv7/sunxi/u-boot-spl.lds中,有用到此变量:

ENTRY(_start)

1、ENTRY(_start) 入口

即指定入口为_start,_start就是整个start.S的最开始,也是U-Boot代码的最开始。

_start: b    reset

_start后面加上一个冒号“:”,表示一个标号,类似于C语言goto语句中的标号。

_start: b   reset /* b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号处执行程序*/
    ldr pc, _undefined_instruction /*未定义指令异常向量,ldr的作用是,
    将符号_undefined_instruction指向的地址的内容加载到pc*/
    ldr pc, _software_interrupt /*软件中断向量*/
    ldr pc, _prefetch_abort     /*预取指令异常向量*/
    ldr pc, _data_abort         /*数据操作异常向量*/
    ldr pc, _not_used           /*未使用*/
    ldr pc, _irq                /*irq中断向量*/

这几行定义的内容对应于ARM处理器的7种异常处理模式。

第一行就是重启复位异常的处理。

#ifdef CONFIG_SPL_BUILD
_undefined_instruction:         .word _undefined_instruction
_software_interrupt:            .word _software_interrupt
_prefetch_abort:                .word _prefetch_abort
_data_abort:                    .word _data_abort
_not_used:                      .word _not_used
_irq:                           .word _irq
_fiq:                           .word _fiq
_pad:                           .word 0x12345678 /* now 16*4=64 */
#else
.globl _undefined_instruction
_undefined_instruction:         .word undefined_instruction
.globl _software_interrupt
_software_interrupt:            .word software_interrupt
.globl _prefetch_abort
_prefetch_abort:                .word prefetch_abort
.globl _data_abort
_data_abort:                    .word data_abort
.globl _not_used
_not_used:                      .word not_used
.globl _irq
_irq:                           .word irq
.globl _fiq
_fiq:                           .word fiq
_pad:                           .word 0x12345678 /* now 16*4=64 */
#endif        /* CONFIG_SPL_BUILD */

如果是编译SPL,那么CONFIG_SPL_BUILD生效:

ldr pc, _undefined_instruction
_undefined_instruction: .word _undefined_instruction

那么这里是生效的,但是ldr pc,_undefined_instruction并无实际的执行动作。

如果是编译U-Boot,那么CONFIG_SPL_BUILD不生效:

ldr pc, _undefined_instruction
.globl _undefined_instruction
_undefined_instruction: .word undefined_instruction

而在后面的代码中可以看到:

/*
* exception handlers
*/
.align 5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction

.word的用法是.word expr,用于分配一个字大小的内存单元,并用expr初始化字内存单元。

这里就相当于在_undefined_instruction处分配了一个32位的地址空间,里面放置着undefined_instruction。类似于C语言中的:

_undefined_instruction = &undefined_instruction

那么ldr pc,_undefined_instruction就相当于PC指到了undefined_instruction处,直接跳到异常处理函数了。

.global _end_vect
_end_vect:
    .balignl 16,0xdeadbeef

这里定义了** _end_vect**,标志着向量表的结束。

接着使用.balignl做16位字节对齐。

不足之处是,用0xdeadbeef填充。其实用任何数据进行字节对齐的填充都是可以的,

这里选用0xdeadbeef是因为在十六进制下,只有abcdef这六个字母可用,从而表示出来的单词就非常少了,因此选择了这么一个有一定意思的单词。

接下来我们看实际的复位代码。

2、reset:

_start: b   reset /* b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号处执行程序*/

将CPU设置为SVC模式

/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * setup Memory and board specific bits prior to relocation.
 * relocate armboot to ram
 * setup stack
 *
 *************************************************************************/
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
    .word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
/* IRQ stack memory (calculated at run-time) + 8 bytes */
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
.word 0x0badc0de
/*
 * the actual reset code
 */
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

首先我们**定义了IRQ_STACK_START_IN,**这里存放的是0x0badc0de,这个其实是表示bad code,为了在程序运行时引起用户注意。

3、save_boot_params

reset:
bl save_boot_params

接下来执行bl save_boot_params跳转指令。

在<arch/arm/cpu/armv7/start.S>中找到的save_boot_params的定义如下:

ENTRY(save_boot_params)
         bx      lr                      @ back to my caller
ENDPROC(save_boot_params)
         .weak   save_boot_params

可以看到save_boot_params有一个.weak限定符,它表明save_boot_params是一个弱符号,也就是说如果没有重新定义save_boot_params,则**使用<arch/arm/cpu/armv7/start.S>中的save_boot_params。**如果其他地方有定义,则使用新的定义。在这里并没有其他的定义,所以save_boot_params是一个空函数。函数定义如下:

ENTRY(save_boot_params)
         bx      lr                      @ back to my caller
ENDPROC(save_boot_params)
         .weak   save_boot_params

空函数之后的几行代码利用r0充当临时变量,以设定CPSR寄存器,将处理器模式设为SVC模式并禁用FIQ和IRQ。之所以将处理器模式设为SVC模式,有两个方面的考虑:

1)我们先简单地分析一下ARM处理器的7种模式:

  • ·中止ABT和未定义UND模式。
  • 中止ABT和未定义UND模式都是不太正常的模式,此处程序是正常运行的,所以不应该设置CPU为其中的任何一种模式,因此可以排除这两种模式。
  • ·快中断FIQ和中断IRQ模式。
  • 对于快中断FIQ和中断IRQ来说,U-Boot初始化的时候,也还没有中断要处理和能够处理的,而且即使是注册了中断服务程序后,能够处理中断,那么这两种模式也是自动切换过去的,因此,此处也不应该将CPU设置为其中的任何一种模式。
  • ·用户USR模式。
  • 虽然从理论上来说,可以设置CPU为用户USR模式,但是由于此模式无法直接访问很多的硬件资源,而U-Boot初始化时,就必须要去访问这类资源,所以此处可以排除,不能设置CPU为用户USR模式。
  • ·系统SYS模式VS管理SVC模式。
  • 首先,SYS模式和USR模式相比,所用的寄存器组都是一样的,但是增加了一些访问USR模式下不能访问的资源。而SVC模式本身就属于特权模式,本身就可以访问那些受控资源,而且相比SYS模式还多了些自己模式下的影子寄存器,所以,相对SYS模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。

从理论上来说,虽然CPU可以设置为SYS模式和SVC模式的任一种,但是从U-Boot方面考虑,其要做的事情是初始化系统的相关硬件资源,因此需要获取尽量多的权限,以方便操作硬件,初始化硬件。

从U-Boot的目的是初始化硬件的角度来说CPU设置为SVC模式,更有利于其工作。因此,此处将CPU设置为SVC模式。

2)U-Boot作为一个BootLoader来说,最终的目的是启动Linux的内核,在做好准备工作(即初始化硬件,准备好内核和rootfs等)跳转到内核之前,本身就要满足一些条件,其中一个条件就是要求CPU处于SVC模式的。

因此,U-Boot在最初的初始化阶段,就将CPU设置为SVC模式也是最正确的。之所以禁用IRQ和FIQ,是因为在U-Boot引导阶段不需要使用IRQ和FIQ,禁用后可排除中断的影响。

/*
 * 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

这一段代码用于**设置协处理器中的寄存器SCTRL。**在《Cortex-A7 MPCore Technical Reference Manual》数据手册中有对SCTLR的介绍。SCTLR提供系统的顶层控制,包括内存系统。

该小节提供了访问SCTLR寄存器的方法:

MRC p15, 0, <Rt>, c1, c0, 0 ; Read System Control Register
MCR p15, 0, <Rt>, c1, c0, 0 ; Write System Control Register

在代码中,使用的是r0:

bic r0, #CR_V    @ V = 0中是设置SCTLR中的V位。

CR_V的定义在./arch/arm/include/asm/system.h中:

#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000        */

V位(bit13)的说明如下:

  • ·向量位。该位选择异常向量的基地址。
  • ·0:异常向量的基地址为0x00000000。软件可以使用VBAR来重新映射该基地址。
  • ·1:高位的异常向量的基地址为0xFFFF0000。该基地址不能被重映射。

因此,我们先将异常向量的基地址设定为0x00000000,接下来重新映射该基地址:

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

在《ARM®Architecture Reference Manual ARMv7-A and ARMv7-R edition》数据手册中能查到读写VBAR的信息:

MRC p15, 0, <Rt>, c12, c0, 0 ; Read VBAR into Rt
MCR p15, 0, <Rt>, c12, c0, 0 ; Write Rt to VBAR

这里我们将基地址映射为_start的地址。接着有几个函数的调用:

/* 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

4、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
#ifdef CONFIG_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
#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
#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
#ifdef CONFIG_ARM_ERRATA_751472
mrc p15, 0, r0, c15, c0, 1             @ read diagnostic register
orr r0, r0, #1 << 11             @ set bit #11
mcr p15, 0, r0, c15, c0, 1             @ write diagnostic register
#endif
mov pc, lr                             @ back to my caller
ENDPROC(cpu_init_cp15)

1、Invalidate L1 I/D

该函数的第一部分是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

在《Cortex-A7 MPCore Technical Reference Manual》中还可以查到mcr p15,0,r0,c8,c7,0是为了使TLB失效的,其中该操作会忽略Rt寄存器中的值,因此可以不往r0寄存器中写入0。

同样地,数据手册中的mcr p15,0,r0,c7,c5,0是为了使icache失效的;mcr p15,0,r0,c7,c5,6是为了使分支预测器失效的;mcr p15,0,r0,c7,c10,4执行Data Synchronization Barrier(数据同步屏障)操作,保证完成内存访问。mcr p15,0,r0,c7,c5,4执行**Instruction Synchronization Barrier(指令同步屏障)**操作,保证指令流的正确执行。

2、disable MMU stuff and caches

该函数的第二部分是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_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

mrc p15,0,r0,c1,c0,0用于读取SCTLR寄存器的值到r0中。bic r0,r0,#0x00002000指令将bit[13]V位清零,bic r0,r0,#0x00000007指令将bit[2:0]清零,然后orr r0,r0,#0x00000002指令将bit[1]置1。这样bit[2]C位的值为0,表示禁用D-cache。bit[1]A位的值为1,表明启用对齐的错误检查功能。bit[0]M位的值为0,**表示禁用地址转换功能,即禁用MMU。orr r0,r0,#0x00000800设置了bit[11]Z位,表示启动了程序流程的预测功能。bit[12]I位是开关I-cache的,对于引导ARM体系下的Linux,对I-cache的开关没有要求。

综合来看,通过对协处理器CP15的配置,可禁用MMU和Cache。

通过cpu_init_cp15,我们分析一下汇编语言中是如何实现函数调用的。

bl cpu_init_cp15实现函数调用,最后mov pc,lr实现函数的返回。

因为bl指令除了实现跳转到cpu_init_cp15之外,在跳转之前,还将下一条指令地址存放到lr寄存器中,这样,跳转的函数在执行完毕后,将lr再赋值给pc,就可以实现函数的返回,从而继续执行后续指令。

cpu_init_crit的定义如下。

5、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                @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

该函数直接调用lowlevel_init函数,lowlevel_init函数在arch/arm/cpu/armv7/lowlevel_init.S中,代码如下。

ENTRY(lowlevel_init)
    /*
     * Setup a temporary stack
     */
    ldr sp, =CONFIG_SYS_INIT_SP_ADDR
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_BUILD
    ldr r9, =gdata
#else
    sub sp, sp, #GD_SIZE
    bic sp, sp, #7
    mov r9, sp
#endif
    /*
     * Save the old lr(passed in ip) and the current lr to stack
     */        
    push {ip, lr}
    /*
     * go setup pll, mux, memory
     */
    bl s_init
    pop {ip, pc}
ENDPROC(lowlevel_init)

第19~23行,首先设定栈指针SP为CONFIG_SYS_INIT_SP_ADDR,然后确保SP是8字节对齐。

CONFIG_SYS_INIT_SP_ADDR在include/configs/sunxi-common.h中的定义如下。

47 /* DRAM Base */
 48 #define CONFIG_SYS_SDRAM_BASE           0x40000000
 49 #define CONFIG_SYS_INIT_RAM_ADDR        0x0
 50 #define CONFIG_SYS_INIT_RAM_SIZE        0x8000  /* 32 KiB */
 51    
 52 #define CONFIG_SYS_INIT_SP_OFFSET \
 53         (CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
 54 #define CONFIG_SYS_INIT_SP_ADDR \
 55         (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

其中,GENERATED_GBL_DATA_SIZE在include/generated/generic-asm-offsets.h中的定义如下:

10 #define GENERATED_GBL_DATA_SIZE 160 /* (sizeof(struct global_data) + 15) & ~15  @ */

因此CONFIG_SYS_INIT_SP_ADDR的值为0x8000–160=0x7F60。然后CONFIG_SPL_BUILD在arch/arm/lib/spl.c中定义了ldr r9,=gdata。

gd_t gdata __attribute__ ((section(".data")));

从定义中可以看出,gdata是一个结构体变量,存放在.data段中。所以这里我们将结构体的地址存放在r9中。在后面的处理中我们将看到gdata的位置。

接下来用下面的代码:

push    {ip, lr}
    /*
     * go setup pll, mux, memory
     */
    bl s_init
    pop {ip, pc}

在调用s_init函数之前做了push{ip,lr}压栈和pop{ip,pc}出栈操作。这里之所以做栈操作,是因为存在多级函数调用,需要保护现场。如果在C语言中,函数跳转前后要做的事情都是由C语言编译器实现的,包括查看汇编代码,还有类似的栈处理。

到这里了,辛苦了,快休息一下,下面接着分析s_init。

参考资料:

内容全部来自《深入理解BootLoader》

目录
相关文章
|
6月前
|
存储 缓存 安全
U-BOOT小全(五):BootLoader源码(SPL-UBoot 2)
U-BOOT小全(五):BootLoader源码(SPL-UBoot 2)
223 0
|
6月前
|
Linux 内存技术
U-BOOT小全(六):BootLoader源码(UBoot-Kernel 1)
U-BOOT小全(六):BootLoader源码(UBoot-Kernel 1)
81 0
|
6月前
|
存储 Linux 存储控制器
BootLoader这个玩意
BootLoader这个玩意
80 0
|
存储 Linux Shell
uboot启动流程简要版(基于armv7)
uboot启动流程简要版(基于armv7)
1037 0
uboot启动流程简要版(基于armv7)
|
Linux 内存技术
Buildroot系列开发(五)bootloader简述
Buildroot系列开发(五)bootloader简述
129 0
Buildroot系列开发(五)bootloader简述
|
内存技术
uboot和spl有什么区别
uboot和spl有什么区别
|
Linux 芯片 SoC
uboot 中的spl 简单认识
uboot 中的spl 简单认识
221 0
|
Linux 芯片 SoC
uboot 中的spl 简单认识
uboot 中的spl 简单认识
524 0
|
Linux
自制操作系统Antz day01——Boot Sector
最近在看操作系统底层方面的东西,最开始的为什么是07c00h这个问题就让我对操作系统有了很大的兴趣。所以准备在看书之余顺便写一个操作系统(Anz)。至于为什么这个系统会被叫做Antz,可以参考Antz Uhl Kone, 日语为アインズ·ウール·ゴウン , 与之对应的还有接下来准备写的自制脚本语言AntzScript。
2429 0