Uboot的Start.S文件分析
1.
Start.S的执行入口。
首先由
u-boot.lds中找到Start.S的入口,整个程序的入口取决于连接脚本中ENTRY声明的地方,ENTRY(_start)的_start符号所在文件就是整个程序的起启文件,_start所处的代码就是整个程序的起启代码。U-boot.lds在uboot中的位置:xxx\uboot\board\samsung\x210
2.
Start.S的头文件包含。
#include <config.h>
此头文件所在的目录为
xxx\uboot\include\linux 这个头文件不是源码本身存在的,而是mkconfig脚本在配置过程中自动生成的,这个头文件内容就只包含了另一个头文件 #include <configs/x210_sd.h>,x210_sd.h头文件里有好多的硬件寄存器的宏,这个头文件是整个uboot移植时的配置文件。
在
mkconfig中的原配置为:
if [ “$APPEND” = “yes” ] #Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file
fi
echo “/* Automatically generated – do not edit */” >>config.h
echo “#include <configs/$1.h>” >>config.h
生成的
config.h头文件内容为:
#include <version.h>
inlclude/version.h中包含了include/version_autogenerated.h,这个头文件是配置过程中自动生成的,里面就是有关的uboot版本的内容:#define U_BOOT_VERSION “U-Boot 1.3.4″,这些版本信息来自Makefile中的配置值。Makefile配置版本信息的源码如下:
VERSION = 1
PATCHLEVEL = 3
SUBLEVEL = 4
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h
#include <asm/proc/domain.h>
Asm目录不是uboot的原生目录,asm目录是配置是创建时一个符号链接,实际指向就是asm-arm,实际的文件是:include/asm-arm/proc-armv/domain.h,由此可以看出,在编译之前事先配置,如果没配置就不会产生这些符号链接,编译就不可能通过,因为找不到头文件。(这样为了移植性考虑)
#
# Create link to architecture specific headers
#
if [ “$SRCTREE” != “$OBJTREE” ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX=”../../include2/asm/”
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
cd ./include
rm -f asm
ln -s asm-$2 asm
fi
#include <regs.h>
这个头文件就是
s5pc11x.h头文件,在mkconfig中配置产生。
# create link for s5pc11x SoC
if [ “$3” = “s5pc11x” ] ; then
rm -f regs.h
ln -s $6.h regs.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch
fi
3.
启动代码的前
16字节
在用
SD卡/Nand启动等整个镜像镜像开头需要16字节的校验头,如果用USB方式下载时不需要此16字节校验头。在裸机中mkv210image.c文件就是用来计算这个校验头。uboot的Start.S文件中在开头位置放了16字节的填充占位,这个占位是留给后面计算校验然后存放校验结果的。
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
4.
异常向量表的构建
异常向量表是由硬件决定的,软件只是参照硬件来设置实现。
uboot中并没有非常细致的处理各种异常。
.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
b reset是复位异常处,此时才是代码有意义的开始。
5.
.balignl 16,0xdeadbeef是什么?
这句话的意思是让当前地址对齐排布,如果当前地址不对齐则自动向后地址走直到对齐。并且向后走的这些内存用
0xdeadbeef来填充(deadbeef就是dead beef,坏牛肉的意思)。为什么要地址对齐?硬件的特殊要求与效率的要求。
6._TEXT_BASE:
.word
TEXT_BASE
此处的
TEXT_BASE就是Makefile配置阶段所决定的,其实就是我们链接时指定的uboot链接地址。在Makefile中配置的TEXT_BASE如下:
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo “TEXT_BASE = 0xc3e00000” > $(obj)board/samsung/x210/config.mk
(源代码和配置
Makefile中很多变量是相互传送的,有些符号的值可以从Makefile中传递到源码)
7._TEXT_PHY_BASE:
.word
CFG_PHY_UBOOT_BASE
此地址就是
uboot在DDR中的地址。
#define MEMORY_BASE_ADDRESS 0x30000000
#define CFG_PHY_UBOOT_BASE MEMORY_BASE_ADDRESS + 0x3e00000
8.
设置
CPU为SVC模式
reset:
/*
* set the cpu to SVC32 mode and IRQ & FIQ disable
*/
@;mrs r0,cpsr
@;bic r0,r0,#0x1f
@;orr r0,r0,#0xd3
@;msr cpsr,r0
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 – SVC
这句话是将
CPU设置为禁止FIO,IRQ,ARM状态,SVC模式。ARM的CPU在复位时默认就会进入SVC模式。
9.
设置
L2、L1cache和MMU
bl disable_l2cache 禁止L2 Cache
bl CoInvalidateDCacheIndex Cache相关初始化
bl enable_l2cache 使能L2 Cache
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 12 (Z—) BTB
mcr p15, 0, r0, c1, c0, 0
关闭
MMU
10.
识别并暂存启动介质
从哪里启动
uboot是由SOC的OM5:OM0这6个引脚的高低电平所决定。怎么判断呢?实际上在210内部有一个寄存器(地址为0XE0000004),这个寄存器的值根据硬件而自动设置,就是存储着启动介质的信息,通过读取这个寄存器的值来确定当前的启动介质是Nand还是SD或者其他的启动介质。
#define PRO_ID_BASE 0xE0000000
/* Read booting information */
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
#define BOOT_MMCSD 0x3
cmp r2, #0xc
moveq r3, #BOOT_MMCSD
给
R3赋值BOOT_MMCSD,是作为以后启动备用。
11.
设置栈(
SRAM中的栈)
为什么设置栈?因为要调用
lowlevel_init函数进行内部初始化,而调用函数就需要栈。(第一次设置栈)这次栈是在内部SRAM中设置的,因为整个代码还在SRAM中执行,此时DDR还未被初始化所以不能用,栈地址0xd0036000是自己指定的,指定的这块空间只给栈用,不会其他占用。在进入lowlevel_init后先将LR入栈,是因为BL只会将返回地址存储到LR中,但是只有一个栈LR,所以在第二层调用函数前将LR入栈,否则函数返回时第一层的返回地址就丢了。
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub sp, sp, #12 /* set stack */
mov fp, #0
bl lowlevel_init /* go setup pll,mux,memory */
12.
lowlevel_init.s
此文件所在的目录为:
uboot/board/samsumg/x210/lowlevel_init.S
(1).检查复位状态
CPU允许多种复位情况。如冷上电、热启动、睡眠(低功耗)状态下的唤醒,在复位时去检查处于应该是哪种状态。这样做的意义:冷上电时DDR是需要初始化才能用的,而热启动或者低功耗状态下则不要在次初始化DDR,减少启动时间。
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
ldr r1, [r0]
bic r1, r1, #0xfff6ffff
cmp r1, #0x10000
beq wakeup_reset_pre
cmp r1, #0x80000
beq wakeup_reset_from_didle
(
2)IO状态复位
ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
ldr r1, [r0]
ldr r2, =IO_RET_REL
orr r1, r1, r2
str r1, [r0]
(3)使能看门狗
ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
mov r1, #0
str r1, [r0]
(4)SRAM和SROM的相关设置。………..
(
5)供电置锁
/* PS_HOLD pin(GPH0_0) set to high */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
ldr r1, [r0]
orr r1, r1, #0x300
orr r1, r1, #0x1
str r1, [r0]
(6)判定是冷启动还是睡眠模式唤醒等。
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq 1f /* r0 == r1 then skip sdram init */
这几行代码的作用就是判定当前代码执行的位置在
SRAM中还是在DDR。为什么要做这个判定?1.BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份 ,因此如果是冷启动那么当前代码应该是在SRAM中运行的BL1,如果是低功耗的复位这时应该是在DDR中运行的。2.判断当前的运行地址,是为指导后面代码的执行。如:判定当前代码的运行地址,为了确定是否执行后面时钟初始化和DDR初始化,如果是冷启动,那么应该初始化,如果不是,就不用再进行一次初始化。
bic r1, pc, r0 这就代码的意义是:将PC值中的某些为清零,剩下一些特殊的位赋值给r1,相等于r1 = PC & ~(ff000fff)然后ldr r2,_TEXT_BASE,加载链接地址到r2,将r2的相应位请零特定位,最后比较r1与r2。
总之,这一段代码是通过读取当前运行地址和连接地址,然后处理两个地址后对比是否相等,来判定当前是在
SRAM中运行还是DDR中运行。
继续:
system_clock_init 初始化时钟,相关配置的值在x210_sd.h中。
bl mem_ctrl_asm_init 该函数的位置/cpu/s5pc11x/s5pc110/cpu_init.S 用来初始化DDR。在uboot中可用的物理地址范围为:0x30000000-0x4FFFFFFF,一共512M,其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。
lowlevel_init.S执行完如果没错那么就会串口打印出”OK”字样
13.返回Start.S
再次设置栈。
/* get ready to call C functions */
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
之前在调用
lowlevel_init程序前已经设置过1次栈,那时候因为DDR尚未初始化,因此程序执行都是在SRAM中执行。本次DDR已经初始化了,就可以办栈挪到DDR中去,这里实际设置的栈地址是33E00000,刚好是在uboot代码段的下面。为什么要再次设置栈?DDR已经初始化了,有了大片内存的使用了,原来SRAM中栈内存空间大小有限,栈放在那里要注意不能超出栈范围,否则栈溢出。
14.再次判断当前地址以决定是否重定位。
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip flash copy */
这次再用相同的代码判断运行地址是在
ARAM中还是DDR中,不过本次的判断目的不同(上次判断是为了是否执行初始化时钟和DDR代码)。这次判断是为了决定是否进行uboot的relocate(重定位)。
冷启动时当前情况是
uboot的前一部分(16kb或者8Kb),开机自动从SD卡加载到SRAM中正在运行,uboot的第二部分还在SD卡中,此时uboot的第一部分结束了(第一阶段该做的事做完了),结束之前要把第二部分加载链接地址处(
0x33e00000),这个加载过程就叫重定位。
/* If BL1 was copied from SD/MMC CH2 */
ldr r0, =0xD0037488
ldr r1, [r0]
ldr r2, =0xEB200000
cmp r1, r2
beq mmcsd_boot
D0037488这个内存地址在SRAM中,这个地址中的值是被硬件地址自动设置的。应将根据实际电路SD卡放在哪个通道中,会将这个地址的值设置为相应的数字。譬如从SD0通道启动时,这个值为EB000000,从SD2通道启动时,这个值为EB200000。
/* SD/MMC BOOT */
cmp r2, #0xc
moveq r3, #BOOT_MMCSD —(1)
ldr r0, =INF_REG_BASE
str r3, [r0, #INF_REG3_OFFSET] —(2)
ldr r0, =INF_REG_BASE
ldr r1, [r0, #INF_REG3_OFFSET] —-(3)
cmp r1, #BOOT_MMCSD —-(4)
beq mmcsd_boot
(1)
中确定了从
MMCSD启动,然后(2)将#BOOT_MMCSD写入了INF_REG3寄存器中存储。然后(3)读出来,(4)再和#BOOT_MMCSD去比较,确定是从MMCSD启动,最终跳到mmcsd_boot去启动。
真正的重定位是通过
movi_bl2_copy函数来完成的。uboot/cpu/s5pc11x/movi.c。
copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
2:表示通道2; MOVI_BL2_POS是uboot第二部分在sd卡的开始扇区,这个扇区数字必须和uboot烧录的位置相同; MOVI_BL2_BLKCNT 是uboot的长度占用的扇区数; CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000)
15.
虚拟地址和物理地址的简单介绍
物理地址就是硬件的实际地址,由硬件编码。虚拟地址意思是软件操作和硬件操作之间增加一个层次,叫做虚拟地址映射层。有了虚拟地址映射后,软件操作只需要给虚拟地址,硬件操作还是用原来地址,映射层就是建立一个虚拟地址到
e物理地址的映射表,我们软件运行的时候,软件中使用的虚拟地址在映射表中查询得到对应的物理地址再发给硬件去执行(虚拟地址到物理地址的映射是不可能通过软件来实现的)
虚拟地址到硬件地址的转换由
MMU来完成。MMU(memory management unit),就是内存管理单元,实际上是一个硬件单元,他的主要功能就是实现虚拟地址到物理地址的映射。
MMU单片在CP15协处理器中进行控制,也就是说操作MMU进行虚拟地址映射,方法就是对CP15协处理器进行控制。
地址映射的额外收益:访问控制和
cache
访问控制:在管理上对内存进行分块,然后每块进行独立的虚拟地址映射,在每一块映射的关系中同时还实现了访问控制(对该块可读、可写、只读、只写、不可访问等控制)。回想在
C语言中编程中经常会出现一个错误:Segmentation fault。实际上这个段错误就和MMU实现的访问控制有关。当前程序只能操作自己有权操作的地址范围(若干个内存块),如果当前程序指针出错访问了不该访问的内存块则就会触发段错误。
Cache:cache的工作和虚拟地址映射有关系。
cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc和mcr指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄存器。
c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。
16.设置TTB(cp15的c2寄存器)
TTB就是translation table base,转换表基地址。转换表是建立一套虚拟地址映射的关键。转换表分2部分,表索引和表项。表索引对应虚拟地址,表项对应物理地址。一对表索引和表项构成一个转换表单元,能够对一个内存块进行虚拟地址转换。整个建立虚拟地址映射的主要工作就是建立这张转换表。
宏观上理解转换表:整个转换表可以看作是一个
int类型的数组,数组中的一个元素就是一个表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。
ARM的段式映射中长度为1MB,因此一个映射单元只能管1MB内存,那我们整个4G范围内需要4G/1MB=4096个映射单元,也就是说这个数组的元素个数是4096.实际上我们做的时候并没有依次单个处理这4096个单元,而是把4096个分成几部分,然后每部分用for循环做相同的处理
17.
再次设置栈
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE – 0x1000)
第三次设置栈。这次设置栈还是在
DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear
清理
bss:清理bss段代码和裸机中讲的一样。注意表示bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。
ldr pc, _start_armboot
start_armboot是uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。这里这个远跳转就是uboot第一阶段和第二阶段的分界线。