U-Boot start.s学习心得总结

####################
本文由极度寒冰原创,转载请注明出处。
####################

关于通用寄存器:通用寄存器一般都是已R开头,比如arm的通用寄存器,是R[0]--R[7]。除了这些之外,还有指令寄存器spsr和cpsr。
这些寄存器是构成一个处理器的基础。   从内存中取出数据,然后在寄存器中进行运算处理,处理完成后,再将这些这些处理过后的内容保存回内存中。

/*
 *  armboot - Startup Code for ARM920 CPU-core
 *
 *  Copyright (c) 2001 Marius Gröger <[email protected]>
 *  Copyright (c) 2002 Alex Züpke <[email protected]>
 *  Copyright (c) 2002 Gary Jennejohn <[email protected]>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */


#include <config.h>
#include <version.h>

/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */

.globl _start      // _start是整个u-boot代码的入口
_start: b       reset      // b 是跳转指令,在这里,无条件的跳转到reset标号处进行执行。reset在本文件的110行。  
ldr pc, _undefined_instruction   //  指令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     // 关于这句话,首先deadbeef是一个单词组。在cpu/arm920t中没有其他的地方来定义或者使用。  .balignl是什么意思呢?应该算是一个伪操作符,伪操作符的意思是说:在机器码里面,并没有一个汇编指令与其对应,是由编译器来实现其功能的。   .balignl是.balign的变体,.balign的意思是,在以当前地址开始,地址计数器必须是以第一个参数为整数倍的地址为尾,在前面记录一个字节长度的信息,信息内容为第二个参数。而.balignl即用来填写一个长字,即内容长度为长字,即为四个字节的长度。  那么,将0xdeadbeef写入当前地址后面的某个地方是起的什么作用呢?我认为是为内存做个标记,有点儿像双向循环链表的那个哨兵节点,放在那里告诉别人,从这个位置往后,就是干什么的内存,这个位置往前,禁止访问。。。。


/*
 *************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * relocate armboot to ram
 * setup stack
 * jump to second stage
 *
 *************************************************************************
 */

_TEXT_BASE:
.word TEXT_BASE     // 定义整个U-BOOT镜像文件在内存加载的地址。 TEXT_BASE变量是通过连接脚本得到的。是可以根据开发板的情况自己进行修改的。具体的地址需要根据硬件设计来确定。

.globl _armboot_start       
_armboot_start:
.word _start    //  .word是伪操作,用于分配一小段字内存单元

/*
 * 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的堆栈地址。  IRQ为外部中断模式。  比如外部中断请求就是一种异常,而这种异常会导致处理器的模式切换。
IRQ_STACK_START:
.word 0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START   // 定义FIQ 的堆栈地址。快速中断模式,具体作用为高速数据传输和中断处理。
FIQ_STACK_START:
.word 0x0badc0de
#endif

//  _start标号,一开始定义了ARM处理器7个中断向量的向量表,对应arm处理器的7种模式。  由于上电一开始处理器会从地址0执行指令,因此第一个指令直接跳转到reset标号。 reset执行机器初始化的一些操作,此处的跳转指令,无论是冷启动还是热启动开发板都会执行reset标号的代码。 

/*
 * the actual reset code
 */

reset:    //从_start标识符处跳转到这里。在处理器启动的时候,最先被执行。
/*
 * set the cpu to SVC32 mode
 */ 
mrs r0,cpsr         //只有mrs指令可以对cpsr和spsr寄存器进行读操作。读cpsr可以获得当前处理器的工作状态,读spsr寄存器可以获得进入异常前的处理器状态。
bic r0,r0,#0x1f       //清除中断。  bic指令的作用是将r0于0x1f的反码进行逻辑“与”,然后保存到r0中。
orr r0,r0,#0xd3       //orr指令的作用是进行逻辑或。
msr cpsr,r0         //msr指令可以对状态寄存器进行写的操作。这样的话,配合mrs就可以对状态寄存器进行“读出来 -> 修改 -> 存回去的操作。”  这时,设置cpsr已经为超级保护模式了。

/* turn off the watchdog */       //关闭看门狗
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000       //看门狗寄存器的地址
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */      //中断控制器的地址。
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif

// 什么是看门狗定时器?
// watchdog是一个硬件模块,在嵌入式的操作系统中,很多应用情况是系统长时间的运行无人看守,难免出现系统的死机,这个时候,watchdog会帮你重新启动系统。
// 硬件的逻辑是:其硬件上面有一个记录超时功能,然后要求用户需要每隔一段时间去对其进行一定的操作,比如往里面写一些固定的值。那么我发现超时了,过了这么长的时间你还没有给我喂食,我就会认为你已经挂掉了。会对你进行重启的操作。

// 为什么要在Uboot里面禁止看门狗呢?
// 因为我们在uboot里面只是用uboot去初始化必要的硬件资源和系统资源而已,完全用不到这个watchdog机制。需要用到,也是你linux的内核已经跑起来了,是系统关心的事情。和uboot的关系不大,所以肯定此处需要关闭uboot。

//这个if defined主要是根据平台来设置看门狗定时器。具体分析如下:
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr     r0, =pWTCON      //取出当前看门狗控制器寄存器的地址到r0      ldr指令的作用:从一个虚拟地址取一个单个的32位值或者地址到指定寄存器。
mov     r1, #0x0       //设置r1寄存器的值为0
str     r1, [r0]     //str用于将寄存器中的数据保存到内存。     这样的话,写入了看门狗控制寄存器。

/*
 * mask all IRQs by setting all bits in the INTMR - default
 */
mov r1, #0xffffffff      //设置r1
ldr r0, =INTMSK     //取出中断屏蔽寄存器地址到r0
str r1, [r0]               //r1的值写入中断屏蔽寄存器

# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff    
ldr r0, =INTSUBMSK   //INTSUBMSK寄存器为中断屏蔽寄存器 INTMSK为主中断屏蔽寄存器,INTSUBMSK为副中断屏蔽寄存器。INTMSK有效位为32,INTSUBMSK有效位为11,这两个寄存器初始化厚的值为0xFFFFFFFF和0x7FF,                                                 默认情况下所有的中断都是屏蔽的。。
str r1, [r0]            //r1的值写入r0
# endif

/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN   //CLKDIVN是时钟寄存器
mov r1, #3     //设置R1的值
str r1, [r0]    //将R1的值写入r0
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */

/*
 * we do sys-critical inits only at reboot,
 * not when booting from ram!
 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit     //跳转回开发板相关初始化的代码
#endif

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM     */
adr r0, _start /* r0 <- current position of code   */      // 获取当前代码存放地址
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */    // 获取内存存放代码地址
cmp     r0, r1                  /* don't reloc during debug         */   //  检查是否需要加载
beq     stack_setup                 
// 检查当前是否在内存中执行代码,根据结果决定是否需要从FLASH存储器加载代码。程序通过获取_start 和 _TEXT_BASE所在地址比较,如果地址相同说明程序已经在内存中,无须加载。


ldr r2, _armboot_start             // 获取stage2代码存放地址
ldr r3, _bss_start                    // 获取内存代码段的起始地址
sub r2, r3, r2 /* r2 <- size of armboot            */       // 计算stage2代码长度
add r2, r0, r2 /* r2 <- source end address         */      // 计算stage2代码结束地址
// 计算要加载到Stage2代码起始地址和长度。
// 什么是stage2? 一般采用c语言编写实现复杂功能,这样代码则具有更好的可读性和可移植性,主要包括lib_*/board.c,common/main.c中main_loop的内容。  包括设置串口波特率,flash初始化,串口工作方式等内容。

copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0]    */   // 从Flash复制代码到内存
  stmia r1!, {r3-r10} /* copy to   target address [r1]    */
cmp r0, r2 /* until source end addreee [r2]    */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */

/* Set up the stack     */
//  -- 设置堆栈sp指针   从内存中建立堆栈
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    */
//  设置堆栈sp指针  --
//  设置堆栈指针,要做的事情即为让sp等于某个地址值。 逻辑为:首先要明白当前的系统是怎么样去使用堆栈的,堆栈是往上生长还是往下生常。 然后在给sp赋值之前,你要保证对应的地址空间,是专门分配好的,是专门给堆栈用的,保证堆栈的大小相对合适,而不要太小以至于后期函数调用太多,导致堆栈溢出,或者堆栈太大,浪费存储空间。

// 在uboot中, 初始化部分的代码执行完之后,对应的内存空间如何规划,什么地方放置了什么内容。  这部分虽然和start.s没有直接的关系,但是堆栈sp的计算,也是和这部分有关。  其余的部分在cpu.c中也有涉及。


clear_bss:                                       // 初始化内存bss段内容为0
ldr r0, _bss_start /* find start of bss segment        */     // 查找bss段起始地址
ldr r1, _bss_end /* stop here                        */     // 查找bss段结束地址
mov  r2, #0x00000000 /* clear                            */     // 清空bss段内容

clbss_l:str r2, [r0] /* clear loop...                    */
add r0, r0, #4
cmp r0, r1         
ble clbss_l     

#if 0
/* try doing this stuff after the relocation */
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, =INTMR
str r1, [r0]

/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
/* END stuff after relocation */
#endif

ldr pc, _start_armboot      // 设置程序指针为start_armboot()函数地址

_start_armboot: .word start_armboot

// _start_armboot全局变量的值是C语言函数start_armboot()函数的地址,使用这种方式可以在汇编中调用C语言编写的函数。

/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */

//  cpu_init_crit标号处的代码初始化ARM处理器的关键的寄存器。
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
 * flush v4 I/D caches
 */
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */    //  作用是刷新cache。 指令mcr的作用是,将arm处理器的寄存器中的数据传送到协处理器的寄存器中。
// cache是一种高速缓存存储器,用于保存cpu频繁使用的数据。在使用cache技术的处理器上,当一条指令想要访问内存的数据时,首先查询cache缓存中是否有数据以及数据是否过期,如果数据未过期则从cache读出数据。处理器会定期回写cache中的数据到内存。根据程序的局部性原理,使用cache后可以大大加快处理器访问内存数据的速度。
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */      //  作用是刷新TLB。
//  TLB的作用是在处理器访问内存数据的时候做地址转换。 TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。当应用程序访问一个虚拟地址的时候,会从TLB中查询出对应的物理地址,然后访问物理地址。TLB通常是一个分层结构,使用与cache类似的原理。处理器使用一定的算法把最常用的页表放在最先访问的层次。

/*
 * disable MMU stuff and caches     //关闭MMU
 */
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
// 使用MMU技术可以向应用程序提供一个巨大的虚拟地址空间,在U-BOOT初始化的时候,程序看到的地址都是物理地址,无须使用MMU。

/*
 * before relocating, we have to setup RAM timing
 * because memory timing is board-dependend, you will
 * find a lowlevel_init.S in your board directory.
 */
mov ip, lr
bl lowlevel_init    //跳转到lowlevel_init标号,执行与开发板相关的初始化配置。
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */


你可能感兴趣的:(汇编,kernel,kernel,u-boot,u-boot,u-boot,bootloader,start.s)