Uboot启动分析笔记-----Stage1(start.S与lowlevel_init.S详解)

ARM 139浏览

Uboot启动分析笔记-----Stage1(start.S与lowlevel_init.S详解)

 

1 u-boot.lds

首先了解uboot的链接脚本board/my2410/u-boot.lds,它定义了目标程序各部分的链接顺序。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

/*指定输出可执行文件为ELF格式,32为,ARM小端*/

OUTPUT_ARCH(arm)

/*指定输出可执行文件为ARM平台*/

ENTRY(_start)

/*起始代码段为 _start*/

SECTIONS

{

    /* 指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置*、

    . = 0x00000000;从 0x0位置开始

    . = ALIGN(4); 4字节对齐

    .text :

    {

        cpu/arm920t/start.o    (.text)

        board/my2440/lowlevel_init.o    (.text)

        *(.text)

    }

    . = ALIGN(4);

    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

    . = ALIGN(4);

    .data : { *(.data) } /* 只读数据段 ,所有的只读数据段都放在这个位置*/

    . = ALIGN(4);

    .got : { *(.got) } /*指定got段, got段式是uboot自定义的一个段, 非标准段*/

    . = .;

    __u_boot_cmd_start = .; /*把__u_boot_cmd_start赋值为当前位置, 即起始位置*/

    .u_boot_cmd : { *(.u_boot_cmd) }

    /* u_boot_cmd段,所有的u-boot命令相关的定义都放在这个位置,因为每个命令定义等长,所以只要以__u_boot_cmd_start为起始地址 进行查找就可以很快查找到某一个命令的定义,并依据定义的命令指针调用相应的函数进行处理用户的任务*/

    __u_boot_cmd_end = .;

    /* u_boot_cmd段结束位置,由此可以看出,这段空间的长度并没有严格限制,用户可以添加一些u-boot的命令,最终都会在连接是存放在这个位置。*/

    . = ALIGN(4);

    __bss_start = .; /*把__bss_start赋值为当前位置,即bss段的开始位置*/

    .bss (NOLOAD) : { *(.bss) . = ALIGN(4); } /*指定bss段,这里NOLOAD的意思是这段不需装载,仅在执行域中才会有这段*/

    _end = .; /*把_end赋值为当前位置,即bss段的结束位置*/

}

第一个链接的是cpu/board/start.o,也即Uboot的入口指令在start中,下面详细分析程序的跳转和函数调用关系。

 

2 Stage1 : cpu/arm920t/start.S

这个汇编程序时UBoot的入口程序,以复位向量开头。

reset

cpu_init_crit

relocate

stack_setup

start_armboot()

init_sequence[]

getenv()

main_loop()

其中前面4步为Stage1

下面来详细分析一下 cpu/arm920t/start.S

这里以ARM9 2410为例,2440移植时需要修改一些配置,具体的再后面的移植中介绍.

/* 这段代码的主要作用:

进入SVC模式

关闭看门狗

屏蔽所有IRG掩码

设置时钟频率 FCLK HCLK PCLK

清楚I/D Cache

禁止MMU和CACHE

配置memory control

重定位:如果代码不在指定的地址上需要把uboot从当前位置copy到RAM指定位置上

建立堆栈,为进入C函数做准备

清0 .bss段

跳入start_armboot函数进入stage2(lib_arm/board.c)*/

.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 /* 快速中断异常向量 */
_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
.balignl 16,0xdeadbeef
/**ARM9支持7种异常。下面是异常的响应过程

*第一个复位异常,它放在0x0的位置,一上电就执行它,而且我们的程序总是从复位异常处理程序

* 开始执行的,因此复位异常处理程序不需要返回。

*其他异常处理的如下:

*当一个异常出现以后,ARM会自动执行以下几个步骤:

*(1) 把下一条指令的地址放到连接寄存器LR(通常是R14),这样就能够在处理异常返回时从正确的位置继续执行。

*(2) 将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。从异常退出的时候,就可以由SPSR来恢复CPSR。

*(3) 根据异常类型,强制设置CPSR的运行模式位。

*(4) PC(程序计数器)被强制成相关异常向量处理函数地址,从而跳转到相应的异常处理程序中。

* 当异常处理完毕后,ARM会执行以下几步操作从异常返回:

*(1)将连接寄存器LR的值减去相应的偏移量后送到PC中

*(2) 将SPSR复制回CPSR中

*(3) 若在进入异常处理时设置了中断禁止位,要在此清除

*

* ARM规定了异常向量的地址:
* b reset ; 复位 0x0

* ldr pc, _undefined_instruction ; 未定义的指令异常 0x4

* ldr pc, _software_interrupt ; 软件中断异常 0x8

* ldr pc, _prefetch_abort ; 预取指令 0xc

* ldr pc, _data_abort ; 数据 0x10

* ldr pc, _not_used ; 未使用 0x14

* ldr pc, _irq ; 慢速中断异常 0x18

* ldr pc, _fiq ; 快速中断异常 0x1c
* 当处理器碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到

* 相应的处理程序,然后再返回到主程序继续执行。

* .balignl 16,0xdeadbeef, 将地址对齐到16的倍数,如果地址寄存器的值(PC)跳过4个字节才是16的倍数,

* 则使用0xdeadbeef填充这4个字节,如果它跳过1、2、3个字节,则填充值不确定。如果地址寄存器的值(PC)是16的倍数,则无需移动。********************/

/*************************************************************************
* Startup Code (reset vector) ………………….

*************************************************************************/
/* 保存变量的数据区 */
_TEXT_BASE:
.word TEXT_BASE ;定义一个字并分配空间 4bytes

.globl _armboot_start
_armboot_start:
.word _start ;声明一个全局的,并用 _start 初始化它, 在u-boot.lds中定义

/* These are defined in the board-specific linker script.*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#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
/* the actual reset code*/
/* 系统的复位代码。系统一上电,就跳到这里运行 */
reset:
mrs r0,cpsr /* 取得当前程序状态寄存器cpsr到r0 */
bic r0,r0,#0x1f /* 这里使用位清除指令,把中断全部清除,只置位模式控制位为中断提供服务通常是 OS设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断*/
orr r0,r0,#0xd3 /* 计算为超级保护模式,并disable IRQ和FIQ */
msr cpsr,r0 /* 设置cpsr为超级保护模式 */
/*****************

*CPSR 的底8位为I         F         T         M4         M3         M2         M1         M0

IRQdisable FIQdisable StateBit

SVC[M4~M0] = 10011

StateBit = set:THUMB state, others:ARM state

* 设置cpu运行在SVC32模式。ARM9共有7种模式:
* 用户模式(usr): arm处理器正常的程序执行状态
* 快速中断模式(fiq): 用于高速数据传输或通道处理
* 外部中断模式(irq): 用于通用的中断处理
* 超级保护模式(svc): 操作系统使用的保护模式
* 数据访问终止模式(abt): 当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护
* 系统模式(sys): 运行具有特权的操作系统任务
* 未定义指令中止模式(und): 当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真
通过设置ARM的CPSR寄存器,让CPU运行在操作系统保护模式,为后面进行其它操作作好准备了。

*********************************************************/

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)

    /* turn off the watchdog */

//这段是关watchdog

# if defined(CONFIG_S3C2400)

# define pWTCON    0x15300000

# define INTMSK    0x14400008    /* Interupt-Controller base addresses */

# define CLKDIVN    0x14800014    /* clock divisor register */

#else

# define pWTCON    0x53000000

# define INTMSK    0x4A000008    /* Interupt-Controller base addresses */

# define INTSUBMSK    0x4A00001C

# define CLKDIVN    0x4C000014    /* clock divisor register */

# endif

    ldr    r0, =pWTCON //具体可以查看手册

    mov    r1, #0x0

    str    r1, [r0]

    /*

     * mask all IRQs by setting all bits in the INTMR - default

     */

    mov    r1, #0xffffffff //禁止所有中断

    ldr    r0, =INTMSK

    str    r1, [r0]

# if defined(CONFIG_S3C2410)

    ldr    r1, =0x3ff

    ldr    r0, =INTSUBMSK

    str    r1, [r0]

# endif

    /* FCLK:HCLK:PCLK = 1:2:4 */

    /* default FCLK is 120 MHz ! */

ldr    r0, =CLKDIVN

    mov    r1, #3

    str    r1, [r0]

#endif /* defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)*/

/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit ;cpu初始化,其中会调用lowlevel_init.S

/******************************************************************************
* BL为相对寻址,以程序计数器PC 的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址
* ARM 指令集中的4条跳转指令可以完成从当前指令向前或向后的32MB 的地址空间的跳转,
* 用的是相对寻址,它们是:B、BL、BLX、BX
*******************************************************************************/
#endif
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
/* 重定位Boot代码到RAM内存,将Boot代码从FLASH移到RAM中 。因为flash中执行速度很慢,而且系统每次复位了都会在0x00000000处执行*/
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */

/**************************************************************************
把_start的相对地址移到r0, 相对寻址以程序计数器PC 的当前值为基地址
* 指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。
* 它是与位置无关的,主要看Boot在哪里运行,也就是PC指针在哪里 (假设_start偏移量为0),
* 示例这段代码在 0x05000000 (FLASH起始地址)运行,即此时PC=0x05000000,

那么 adr r0, _start 得到 r0 = 0x05000000;
* 如果在地址 0x33008000(Boot在RAM中加载地址)运行,即此时PC=0x33008000,那么r0就是 0x33008000 了。

 

*通过adr指令得到当前代码的地址信息:如果U-boot是从RAM开始运行,则从adr,r0,_start得到的地址信息为 *r0=_start=_TEXT_BASE=TEXT_BASE=0x3ff80000;如果U-boot从Flash开始运行,即从处理器对应的地址运 行,则*r0=0x0000,这时将会执行copy_loop标识的那段代码了
**************************************************************************/
ldr r1, _TEXT_BASE/* test if we run from flash or RAM */

/* 把_TEXT_BASE地址处的值TEXT_BASE,也就是BOOT在RAM中运行地址移到r1 */
cmp r0, r1 /* don't reloc during debug */

/* 比较两个地址是否相同,如果相同,就已经在RAM运行,否则就是FLASH中运行 */

beq stack_setup
/* 如果是在FLASH中运行, 则把FLASH中的Boot代码移到RAM中,然后再运行 */
ldr r2, _armboot_start /* 把_armboot_start地址处的值也就是_start绝对地址(也即在内存中的地址,这个绝对
* 地址是在 link 的时候确定的,如0x81008000)移到r2 */
ldr r3, _bss_start /* 把_bss_start地址处的值也就是__bss_start绝对地址(也即在内存中的地址,这个绝对
* 地址是在 link 的时候确定的)移到r3 */
sub r2, r3, r2 /* r2 <- size of armboot */ /* 计算引导代码大小并存到r2 */
add r2, r0, r2 /* r2 <- source end address */ /* 计算引导代码最后相对地址并存入r2 */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */ /* 从源地址[r0]读取32个字节到寄存器,并更新r0 */
stmia r1!, {r3-r10} /* copy to target address [r1] */ /* 拷贝寄存器r3-r10的32个字节值保存到 [r1]指明的地址,并更新r1的值 */
cmp r0, r2 /* until source end addreee [r2] */ /* 循环拷贝,直到把所有引导代码都移到内存 */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
/* Set up the stack */
/* 在内存中建立起堆栈 */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */

/* 初始化内存中bss段中数据为0 */
clear_bss:
ldr r0, _bss_start /* find start of bss segment*/

/* 把_bss_start地址处存储的绝对地址移到r0 */
ldr r1, _bss_end /* stop here */ /* 把_bss_end地址处存储的绝对地址移到r1 */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... STR 指令用于从源寄存器中r2将一个32 位的字数据传送到存储器中[r0]*/
add r0, r0, #4
cmp r0, r1
ble clbss_l /* 小于或等于跳转 */
ldr pc, _start_armboot

/***********************************************************
* 已经准备好了堆栈,就可跳到C写的代码里了,也就是
* 跳到内存中的/u-boot-1.1.6/board.c --> start_armboot中运行了
* 把_start_armboot地址处的值也就是start_armboot绝对地址值移到pc
* 神啊!终于跳到C代码了。
***********************************************************/
_start_armboot:
.word start_armboot
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
/**************************************************************************
* 1、关闭 MMU和CPU 内部指令/数据 (I/D)cache。
* 2、设置 CPU 的速度和时钟频率。
* 3 、RAM 初始化。
****************************************************************************/
cpu_init_crit:
/* flush v4 I/D caches*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
/******************************************************************************************************
* MCR 指令用于将ARM 处理器寄存器中的数据传送到协处理器寄存器中,格式为:
* MCR 协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2。
* 其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,
* 源寄存器为ARM 处理器的寄存器,目的寄存器1 和目的寄存器2 均为协处理器的寄存器。

******************************************************************************************************/
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/ * disable MMU stuff and caches*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 /* clear bits 13, 9:8 (--V- --RS) */
bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
orr r0, r0, #0x00000002 /* set bit 2 (A) Align */
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
mcr p15, 0, r0, c1, c0, 0
/ * Go setup Memory and board specific bits prior to relocation.*/
mov ip, lr /* perserve link reg across call */
bl lowlevel_init /* go setup pll,mux,memory */ /* 位于u-boot-1.1.6/board/xxx(开发板目录名称)/lowlevel_init.S */
mov lr, ip /* restore link */
mov pc, lr /* back to my caller */ /* 从cpu_init_crit子函数返回 */
/*************************************************************************
*
* Interrupt handling
*
*************************************************************************/

/*下面是中断相关的东西,这里略去*/

 

/board/my2410/lowlevel_init.S

.globl lowlevel_init //读取下面标号为SMRDATA处的地址到R0中
    ldr r0
, =SMRDATA
    
/*读取上面标号为_TEXT_BASE处的地址内容到R1中
    
*也就是取得TEXT_BASE的值到R1中*/
    ldr    r1
, _TEXT_BASE
    
/*计算SMRDATA的相对地址保存到R0中
    
*SMRDATA为虚拟地址,而TEXT_BASE为虚拟地址的起始地址
    
*而现在Uboot的起始地址并不为虚拟地址
    
*TEXT_BASE为0x33F8 0000,SMRDATA为0x33F8 06C8
    
*而现在程序运行在起始地址为0x0000 0000的地方
    
*所以需要计算以0x0000 0000为标准的相对地址*/
    sub    r0
, r0, r1
    
//取得带宽与等待状态控制寄存器地址到R1中
    ldr    r1
, =BWSCON    /* Bus Width Status Controller */
    
//一共需要设置13个寄存器,每个寄存器4字节,详见芯片手册
    add r2
, r0, #13*4
0
:
    
//读取R0所指的项的值到R3中后R0自加4字节
    ldr r3
, [r0], #4
    
//将R3中的值保存到R1所指的地址中后R1自加4字节
    str r3
, [r1], #4
    
//比较R0和R2是否相等,相等则说明13个寄存器全部设置完毕
    cmp r2
, r0
    
//不等则跳转到上面标号为0处的地址继续执行
    bne 0b
    
//跳回到返回地址中继续执行
    mov    pc
, lr
    
.ltorg
/* the literal pools origin */
SMRDATA
:
    
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))

//设置每个BWSCON,注意BANK0由硬件连线决定了
    
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
//
设置BANKCON0~BANKCON5    

.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))

//设置BANKCON6~BANKCON7
    
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)

//设置REFRESH,S3C24401117位是保留的,也即(Tchr<<16)无意义
    
.word 0x32

//设置BANKSIZE,对于容量可以设置大写,多出来的空内存会被自动检测出来
    
.word 0x30

//设置MRSRB6
    .word 0x30

//设置MRSRB7