####################
本文由极度寒冰原创,转载请注明出处。
####################
关于通用寄存器:通用寄存器一般都是已R开头,比如arm的通用寄存器,是R[0]--R[7]。除了这些之外,还有指令寄存器spsr和cpsr。
这些寄存器是构成一个处理器的基础。 从内存中取出数据,然后在寄存器中进行运算处理,处理完成后,再将这些这些处理过后的内容保存回内存中。
/*
* armboot - Startup Code for ARM920 CPU-core
*
*
* 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 */