uboot文件配置 & uboot启动第一阶段

来源:互联网 发布:站桩 知乎 编辑:程序博客网 时间:2024/06/06 02:55
x210官方uboot文件配置:
【1解压】先将BSP(板级支持包)copy在/root目录下,并解压。tar -jxvf qt_x210v3_130807.tar.bz2
【2配置】:【make x210_sd_config】出现Configuring for x210_sd board...表明配置成功。
【3编译】:【make】 得到uboot.bin先cd进入uboot目录下,再vim打开Makefile,查看交叉编译
工具链路径和名字是否相对应。最后make-j4(多线程编译,主机如果是多核心电脑,
可以尝试多线程编译,会快一些)
【4烧录文件生成】:先【cd /x210_bsp/uboot/sd_fusing】进入sd_fusing目录下


第一步:【make clean】清除上次编译的信息
// 第二步:【vi sd_fusing.sh】进入里面输入/uboot_inand.bin,将查到的uboot_inand.bin
改为【u-boot.bin】,保存退出。
第三步:【make】生成烧录文件【u-boot.bin】

【5烧录】: 插入SD卡,【ls /dev/sd*】查看里面多了/dev/sdb & /dev/sdb1
执行【./sd_fusing.sh /dev/sdb】烧录完成

【注意】:再次编译之前,先make distclean清除上次编译,再重复步骤【2】【3】
【个人出现的问题总结】:
由于之前用fastboot命令将Uboot刷机,所以板载Uboot,先破坏Uboot,出现SD Check Sum Error,然后再将SD卡插入+SD2通道,开机,即可。


/##################################################################################/
Uboot的配置过程:
(1)创建的符号链接:
第一个:在include目录下创建asm文件,指向asm-arm。(46-48行)
第二个:在inlcude/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc110
第三个:在include目录下创建regs.h文件,指向include/s5pc110.h删除第二个。
第四个:在inlcude/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc11x
第五个:在include/asm-arm下创建一个proc文件,指向include/asm-arm/proc-armv


总结:一共创建了4个符号链接。这4个符号链接将来在写代码过程中,头文件包含时非常有用。譬如一个头文件包含可能是:#include <asm/xx.h>


【思考】:为什么start.S不直接包含文件[asm-arm/proc-armv/domain.h],而要创建符号链接
[asm/proc/domain.h]?这样的设计主要是为了可移植性。因为如果直接包含,则start.S文件和CPU架构(和硬件)有关了,可移植性就差了。譬如我要把uboot移植到mips架构下,则start.S源代码中所有的头文件包含
全部要修改。我们用了符号链接之后,则start.S中源代码不用改,只需要在具体的硬件移植时
配置不同,创建的符号链接指向的不同,则可以具有可移植性。如果没有这些符号链接则编译时根本通不过,因为找不到头文件。(所以uboot不能在windows的共享文件夹下配置编译,因为windows中没有符号链接)




Ubootde源码分析:
一、u-boot.lds中找到start.S入口
(1)在uboot中因为有汇编阶段参与,因此不能直接找main.c。整个程序的入口取决于链接脚本中ENTRY声明的地方。ENTRY(_start)因此_start符号所在的文件就是整个程序的起始文件,_start所在处的代码就是整个程序的起始代码。
(2)利用SI工具搜索到一共7个_start,然后分析搜索出来的7处,发现有2个是api_example,2个是onenand相关的,都不是我们要找的。剩下3个都在uboot/cpu/s5pc11x/start.S文件中。
(3)然后进入start.S文件中,发现57行中就是_start标号的定义处,于是乎我们就找到了整个uboot的入口代码,就是第57行。

【action】:在SI中,如果我们知道我们要找的文件的名字,但是我们又不知道他在哪个目录下,我们要怎样找到并打开这个文件?方法是在SI中先打开右边的工程项目管理栏目,然后点击最左边那个(这个是以文件为单位来浏览的),然后在上面输入栏中输入要找的文件的名字。我们在输入的时候,SI在不断帮我们进行匹配,即使你不记得文件的全名只是大概记得名字,也能帮助你找到你要找的文件。
二、不简单的头文件包含
(1). start.S中包含的第一个头文件#include<config.h>。
【配置过程中生成,include/configs/x210_sd.h——配置宏文件】
A>.config.h是在include目录下的,这个文件不是源码中本身存在的文件,而是配置过程中自动生成的文件。(详见mkconfig脚本)。
这个文件的内容其实是包含了一个头文件:#include <configs/x210_sd.h>".

B>.经过分析后,发现start.S中包含的第一个头文件就是:include/configs/x210_sd.h,
这个文件是整个uboot移植时的配置文件。这里面是好多宏。因此这个头文件包含将
include/configs/x210_sd.h文件和start.S文件关联了起来。因此之后在分析start.S文件时,
主要要考虑的就是x210_sd.h文件。

(2)start.S中包含的第二个头文件#include <version.h>。
【配置过程中生成,路径:include/version_autogenerated.h——版本号信息】


A>.include/version.h中包含了include/version_autogenerated.h,这个头文件就是配置过程中
自动生成的。里面就一行内容:#define U_BOOT_VERSION "U-Boot 1.3.4"。这里面定义的宏U_BOOT_VERSION的值是一个字符串,字符串中的版本号信息来自于Makefile中的配置值。这个宏在程序中会被调用,在uboot启动过程中会串口打印出uboot的版本号,那个版本号信息就是从这来的。
(3).start.S中包含的第二个头文件 #include <asm/proc/domain.h>。
【配置时创建符号链接:include/asm-arm/proc-armv/domain.h】
A>.asm目录不是uboot中的原生目录,uboot中本来是没有这个目录的。asm目录是配置时创建的一个符号链接,实际指向的是就是asm-arm(详解上一章节分析mkconfig脚本时).

B>.经过分析后发现,实际文件是:include/asm-arm/proc-armv/domain.h

三、启动代码的16字节头部 & 异常向量表的构建 & TEXT_BASE:
(1)启动代码的16字节头部:
A>.裸机中,在SD卡启动/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c中就是为了计算这个校验头)。我们以前做裸机程序时根本没考虑这16字节校验头,因为:1、如果我们是usb启动直接下载的方式启动的则不需要16字节校验头(irom application note);
2、如果是SD卡启动mkv210image.c中会给原镜像前加16字节的校验头。
B>.uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
[.word :定义一个四字节大小的变量]


(2)异常向量表的构建:
A>.异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。异常向量表如下:
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>.异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在uboot中并未非常细致的处理各种异常。
_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 */
.global _end_vect
_end_vect:


.balignl 16,0xdeadbeef

【deadbeef】:
a>.balignl 16,0xdeadbeef.这一句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。
b>.0xdeadbeef这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制数全是abcdef之中的字母,而且这8个字母刚好组成了英文的dead beef这两个单词,字面意思是坏牛肉。

c>.为什么要对齐访问?有时候是效率的要求,有时候是硬件的特殊要求。

C>.复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,
因此reset符号处才是真正的有意义的代码开始的地方。
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(IRQ) & F(FIQ) disable, Mode: 0x13 - SVC


D>.TEXT_BASE & CFG_PHY_UBOOT_BASE等
①.TEXT_BASE
a>.第100行这个TEXT_BASE就是2.2.4节分析Makefile时讲到的那个配置阶段的TEXT_BASE,
其实就是我们链接时指定的uboot的链接地址。(值就是c3e00000)
b>.源代码中和配置Makefile中很多变量是可以互相运送的。简单来说有些符号的值可以从Makefile中传递到源代码中。
_TEXT_BASE:
.word TEXT_BASE
【分析】:这句代码相当于C语言中定义了一个指针变量。
_TEXT_BASE:变量名.word:类型[unsigned int] TEXT_BASE:赋值

②. CFG_PHY_UBOOT_BASE【地址:0x33e00000】
1>. [MEMORY_BASE_ADDRESS = 0x30000000]
#define CFG_PHY_UBOOT_BASEMEMORY_BASE_ADDRESS + 0x3e00000

2>. uboot在DDR里面的虚拟地址:CFG_PHY_UBOOT_BASE0xc3e00000
uboot在DDR里面的物理地址:0x33e00000





四、设置CPU为SVC模式 & 设置L2、L1cache和MMU & 识别并暂存启动介质选择 &
设置栈(SRAM中的栈并调用lowlevel_init
(1). 设置CPU为SVC模式
A>. msr cpsr_c, #0xd3 将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式。

B>. 其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。
整个uboot工作时CPU一直处于SVC模式。


(2). 设置L2、L1cache和MMU
A>. bldisable_l2cache // 禁止L2 cache
bl set_l2cache_auxctrl_cycle// l2 cache相关初始化
bl enable_l2cache// 使能l2 cache
B>. cpu_init_crit
刷新L1 cache的icache和dcache。[icache:指令缓存,dcache:数据缓存]
//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
    
C>.关闭MMU
//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 12 (Z---) BTB
        mcr p15, 0, r0, c1, c0, 0
【总结】:上面这3步都是和CPU的cache和mmu有关的,不用去细看,大概知道即可。

(3)识别并暂存启动介质选择
A>.从哪里启动是由SoC的OM5:OM0这6个引脚的高低电平决定的。
B>.实际上在210内部有一个寄存器(地址是0xE0000004),这个寄存器中的值是硬件根据OM引脚的设置而自动设置值的。这个值反映的就是OM引脚的接法(电平高低),也就是真正的启动介质是谁。C>.我们代码中可以通过读取r2这个寄存器的值然后判断其值来确定当前选中的启动介质是Nand还是SD还是其他的。
/* Read booting information */
    ldr r0, =PRO_ID_BASE//#define PRO_ID_BASE 0xE0000000
    ldr r1, [r0,#OMR_OFFSET]//#define OMR_OFFSET 0x04r1 = 0xE0000004
    bic r2, r1, #0xffffffc1//将r1的0xffffffc1位清零赋给r2

D>.242~264行:对应NAND/ONENAND/SD/MMC等各种启动方式
a>.start.S的225-227行执行完后,在r2寄存器中存储了一个数字,这个数字等于某个特定值
(0xc)时就表示SD启动,等于另一个特定值时表示从Nand启动····

b>.260行中给r3中赋值#BOOT_MMCSD(0x03),这个在SD启动时实际会被执行,因此执行完
这一段代码后。r3中存储了0x03,以后备用。
/* SD/MMC BOOT */
cmp     r2, #0xc
moveq   r3, #BOOT_MMCSD//BOOT_MMCSD = 0x03, r3 = 0x03

(4)设置栈(SRAM中的栈)并调用lowlevel_init【栈地址[自己定的]:0xd0036000】
A>.284-286行第一次设置栈。这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。
//Go setup Memory and board specific bits prior to relocation.
ldr sp, =0xd0036000/* end of sram dedicated to u-boot */
sub sp, sp, #12/* set stack */
mov fp, #0
B>.在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。
bl lowlevel_init/* go setup pll,mux,memory */

c>.使用SourceInsight的Reference功能,找到lowlevel_init函数真正的地方,是在
uboot/board/samsumg/x210/lowlevel_init.S中。
①.push {lr}    //先将lr压栈(保护栈指针),避免函数内部二次调用丢失最初的地址。

五、检查复位状态 & IO状态恢复 & 关看门狗 & SRAM SROM相关GPIO设置 & 供电锁存
(1)检查复位状态
A>.复杂CPU允许多种复位情况。譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒等,
这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。
B>.判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;而热启动或者低功耗状态下的复位则不需要再次初始化DDR。
C>.与主线无关,不用care
/* check reset status  */

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状态恢复
A>.这个和上一个和主线启动代码都无关,因此不用去管他。
/* IO Retention release */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
ldr r1, [r0]
ldr r2, =IO_RET_REL
orr r1, r1, r2
str r1, [r0]
(3)关看门狗
A>.参考裸机中看门狗章节
/* Disable Watchdog */
ldr r0, =ELFIN_WATCHDOG_BASE/* 0xE2700000 */
mov r1, #0
str r1, [r0]
(4)一些SRAM SROM相关GPIO设置
A>.初始化外接的SRAM & SROM,与主线启动代码无关,不用管


(5)供电锁存
A>.lowlevel_init.S的第100-104行,开发板供电锁存。
/* 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//因为0x301是非法立即数,所以要分两步Orr操作。
str r1, [r0]
总结:在前100行,lowlevel_init.S中并没有做太多有意义的事情(除了关看门狗、供电锁存外),然后下面从110行才开始进行有意义的操作。





六、判断当前代码执行位置【DDR or SRAM】
(1).使用SourceInsight的Reference功能,找到lowlevel_init函数真正的地方,是在
uboot/board/samsumg/x210/lowlevel_init.S中。
A>.push {lr}    //先将lr压栈(保护栈指针),避免函数内部二次调用丢失最初的地址。

(2)判断当前代码执行位置:lowlevel_init.S的110-115行。
A>.这几行代码的作用就是判定当前代码执行的位置在SRAM中还是在DDR中。为什么要做这个判定?原因1:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份,因此如果是冷启动那么当前代码应该是在SRAM中运行的BL1,如果是低功耗状态的复位这时候应该就是在DDR中运行的。原因2:我们判定当前运行代码的地址是有用的,可以指导后面代码的运行。譬如在lowlevel_init.S中判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和初始化DDR的代码。如果当前代码是在SRAM中,说明冷启动,那么时钟和DDR都需要初始化;如果当前代码是在DDR中,那么说明是热启动则时钟和DDR都不用再次初始化。


B>.bic r1, pc, r0 这句代码的意义是:将pc的值中的某些bit位清0,剩下一些特殊的bit位赋值给r1(r0中为1的那些位清零)相等于:r1 = pc & ~(ff000fff)
ldr r2, _TEXT_BASE加载链接地址到r2,然后将r2的相应位清0剩下特定位。
C>.最后比较r1和r2.[掐头去尾比中间,比较结果与裸机正好相反;比较方法与裸机不同,裸机比头部,相等为SRAM,不相等为DDR]

总结:这一段代码是通过读取当前运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前运行是在SRAM中(不相等)还是DDR中(相等)。从而决定是否跳过下面的时钟和DDR初始化。
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[相当于此时处于DDR中,热启动]   */
/*1f是分立的。1:表示标号,f:表示向后找*/

/* init system clock 初始化时钟*/
bl system_clock_init


/* Memory initialize 初始化内存*/
bl mem_ctrl_asm_init




(2).system_clock_init
A>.使用SI搜索功能,确定这个函数就在当前文件的205~385行。这个初始化时钟的过程和裸机中初始化的过程一样的,只是更加完整而且是用汇编代码写的。
B>.在x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可。


七、初始化DDRmem_ctrl_asm_init & uart_asm_init等
A>.函数位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S文件中。
B>.该函数和裸机中初始化DDR代码是一样的。实际上裸机中初始化DDR的代码就是从这里抄的。配置值也可以从这里抄,但是当时我自己根据理解+抄袭整出来的一份。
C>.配置值中其他配置值参考裸机中的解释即可明白,有一个和裸机中讲的不一样。DMC0_MEMCONFIG_0,在裸机中配置值为0x20E01323;在uboot中配置为0x30F01313.这个配置不同就导致结果不同。
在 裸机中DMC0的256MB内存地址范围是0x20000000-0x2FFFFFFF; 
在uboot中DMC0的256MB内存地址范围为0x30000000-0x3FFFFFFF。
D>.之前在裸机中时配置为2开头的地址,当时并没有说可以配置为3开头。从分析九鼎移植的uboot可以看出:DMC0上允许的地址范围是20000000-3FFFFFFF(一共是512MB),而我们实际只接了256MB物理内存,SoC允许我们给这256MB挑选地址范围。

【总结】:在uboot中,可用的物理地址范围为:0x30000000-0x4FFFFFFF。一共512MB,其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。
E>.我们需要的内存配置值在x210_sd.h的438行到468行之间。分析的时候要注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值,这个的主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。
F>.在uboot中DMC0和DMC1都工作了,所以在裸机中只要把uboot中的配置值和配置代码全部移植过去,应该是能够让DMC0和DMC1都工作的。


(2).串口初始化uart_asm_init
A>.这个函数用来初始化串口
B>.初始化完了后通过串口发送了一个'O',说明串口初始化成功,调试代码如下:
ldr r1, =0x4f4f4f4f
str r1, [r0, #UTXH_OFFSET]@'O'

(3).tzpc_init
A>.trust zone初始化,没搞过,不管


(4).pop {pc}以返回
A>.返回前通过串口打印'K'

【总结】:
A>.lowlevel_init.S执行完如果没错那么就会串口打印出"OK"字样。这应该是我们uboot中看到的最早的输出信息。
B>.将来在移植代码过程中,如果串口没有打印出“OK”字样,说明代码在lowlevel_init之前有问题,如果打印出来,就说明错误在lowlevel_init之后。


C>.lowlevel_init.S中总共做了哪些事情:
a>.检查复位状态、IO恢复、关看门狗、开发板供电锁存、时钟初始化、DDR初始化、
串口初始化 并打印'O'、tzpc初始化、打印'K'。
b>.其中值得关注的:关看门狗、开发板供电锁存、时钟初始化、DDR初始化、打印"OK"


八、再次设置栈(DDR中的栈)[文件为:start.S]
(1)再次开发板供电锁存[之前在lowlevel_init.S中已经锁存过了]。第一,做2次是不会错的;第二,做2次则第2次无意义;做代码移植时有一个古怪谨慎保守策略就是尽量添加代码而不要删除代码。
ldr r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
ldr r1, =0x00005301/* PS_HOLD output high */
str r1, [r0]

(2)再次设置栈
A>.第一次设置栈,那时候因为DDR尚未初始化,因此程序执行都是在SRAM中,所以在SRAM中分配了一部分内存作为栈。
B>.本次因为DDR已经被初始化了,因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次
(start.S 297-299行);这里实际设置的栈的地址是33E00000,刚好在uboot的代码段
的下面紧挨着。
【总结】: 第一次设置栈:SRAM中的栈(284~287行),栈地址[自己定的]:0xd0036000
第二次设置的栈:DDR中的栈(297~299行),栈地址:0x33E00000

(3)为什么要再次设置栈?
DDR已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中可怜兮兮的了;原来SRAM中内存大小空间有限,栈放在那里要注意不能使用过多的栈否则栈会溢出,我们及时将栈迁移到DDR中也是为了尽可能避免栈使用时候的小心翼翼。

感慨:uboot的启动阶段主要技巧就在于小范围内有限条件下的辗转腾挪。


(4).再次判断当前地址以决定是否重定位
A>.再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同(上次判断是为了决定是否要执行初始化时钟和DDR的代码)这次判断是为了决定是否进行uboot的relocate。
B>.冷启动时当前情况是uboot的前一部分(16kb或者8kb)开机自动从SD卡加载到SRAM中正在运行,uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。此时uboot的第一阶段已经即将结束了(第一阶段该做的事基本做完了),结束之前要把第二部分加载到DDR中链接地址处(33e00000),这个加载过程就叫重定位。

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


九、uboot重定位详解【312~355行】
(1).D0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB000000;从SD2通道启动时,这个值为EB200000


(2).我们在start.S的260行确定了从MMCSD启动,然后又在278行将#BOOT_MMCSD写入了INF_REG3寄存器中存储着。然后又在322行读出来,再和#BOOT_MMCSD去比较,确定是从MMCSD启动。最终跳转到mmcsd_boot函数中去执行重定位动作。
258:/* SD/MMC BOOT */
259: cmp     r2, #0xc
260: moveq   r3, #BOOT_MMCSD


277: ldr r0, =INF_REG_BASE
278: str r3, [r0, #INF_REG3_OFFSET] 
  
321: ldr r0, =INF_REG_BASE
322: ldr r1, [r0, #INF_REG3_OFFSET]


327: cmp     r1, #BOOT_MMCSD
328: beq     mmcsd_boot


(3)真正的重定位是通过调用movi_bl2_copy函数完成的,在uboot/cpu/s5pc11x/movi.c中。是一个C语言的函数【343~350】
mmcsd_boot:
#if DELETE
ldr     sp, _TEXT_PHY_BASE      
sub     sp, sp, #12
mov     fp, #0
#endif
bl      movi_bl2_copy
b       after_copy

(4)copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0);
copy_bl2();该函数是真正重定位函数movi_bl2_copy();里面的最终实现函数。

分析参数:2表示通道2;MOVI_BL2_POS是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时烧录的位置相同;MOVI_BL2_BLKCNT是uboot的长度占用的扇区数,uboot的长度被定义为2k;CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000).


十、after_copy();【使能域访问 & 设置TTB & 使能MMU单元 & 转换表分析】


(1)使能域访问(cp15的c3寄存器)
A>.cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc[读]和mcr[写]指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄存器。

B>.c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0@load domain access register


(2).设置TTB(cp15的c2寄存器)
A>.TTB就是translation table base,转换表基地址。首先要明白什么是TT(translation table转换表),TTB其实就是转换表的基地址。
B>.转换表是建立一套虚拟地址映射的关键。转换表分2部分,表索引和表项。表索引对应虚拟地址,表项对应物理地址。一对表索引和表项构成一个转换表单元,能够对一个内存块进行虚拟地址转换。(映射中基本规定中规定了内存映射和管理是以块为单位的,至于块有多大,要看你的MMU的支持和你自己的选择。在ARM中支持3种块大小,细表1KB、粗表4KB、段1MB)。真正的转换表就是由若干个转换表单元构成的,每个单元负责1个内存块,总体的转换表负责整个内存空间(0-4G)的映射。

C>.整个建立虚拟地址映射的主要工作就是建立这张转换表
D>.转换表放置在内存中的,放置时要求起始地址在内存中要xx位对齐。转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时会自动去查转换表。
/* Set the TTB register */
ldr r0, _mmu_table_base
ldr r1, =CFG_PHY_UBOOT_BASE
ldr r2, =0xfff00000
bic r0, r0, r2
orr r1, r0, r1
mcr p15, 0, r1, c2, c0, 0

(3)使能MMU单元(cp15的c1寄存器)
A>.cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。

/* Enable the MMU */
mmu_on:
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1
mcr p15, 0, r0, c1, c0, 0
nop
nop
nop
nop


(4).转换表分析【虚拟地址映射】:
A>.通过符号查找,确定转换表在lowlevel_init.S文件的593行。
B>.S5PV210的2种虚拟地址管理: 一级映射 & 二级映射
366: ldr r0, _mmu_table_base


417: _mmu_table_base:
418: .word mmu_table【mmu_table--->lowlevel_init.S [539~653行]】


/*******************************mmu_table();*********************************************/
.macro FL_SECTION_ENTRY base,ap,d,c,b
.word (\base << 20) | (\ap << 10) | \
     (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm


//【.macro】:汇编语言定义宏,【.endm】:汇编语言结束宏 ,【base,ap,d,c,b】汇编宏传参


.section .mmudata, "a"
.align 14
// the following alignment creates the mmu table at address 0x4000.
//从0x4000地址,MMU进行14字节对齐。
.globl mmu_table
mmu_table:
.set __base,0

// Access for iRAM
.rept 0x100
FL_SECTION_ENTRY __base,3,0,0,0//每循环一次,就会建立一个表象
.set __base,__base+1
.endr


//【.rept】:相当于C语言里面的for循环,【.endr】结束for循环,【0x100】:循环次数


/****************************************************************************************/
(5)最终建立的虚拟地址映射表如下:
VA PA length
0-10000000 0-10000000256MB
10000000-20000000 0256MB
20000000-60000000 20000000-600000001GB 512-1.5G
60000000-80000000 0512MB 1.5G-2G
80000000-b0000000 80000000-b0000000768MB 2G-2.75G
b0000000-c0000000 b0000000-c0000000256MB 2.75G-3G
c0000000-d0000000 30000000-40000000256MB 3G-3.25G
d-完 d-完 768MB 3.25G-4G




DRAM有效范围:
DMC0: 0x30000000-0x3FFFFFFF
DMC1: 0x40000000-0x4FFFFFFF


【结论】:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。


【思考】:为什么配置时将链接地址设置为c3e00000,因为这个地址将来会被映射到33e00000这个物理地址。c3e00000 & 33e00000这俩地址是等价的。


【总结】:关于MMU和虚拟地址映射的学习
宏观上理解转换表:整个转换表可以看作是一个int类型的数组,数组中的一个元素就是一个
表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。ARM的段式映射中
长度为1MB,因此一个映射单元只能管1MB内存,那我们整个4G范围内需要4G/1MB=4096个映射单元,也就是说这个数组的元素个数是4096.实际上我们做的时候并没有依次单个处理这4096个单元,而是把4096个分成几部分,然后每部分用for循环做相同的处理。




十一、再次设置栈
(1).第三次设置栈。
A>.这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。
B>.我们实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。
#if defined(CONFIG_MEMORY_UPPER_CODE)
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)

(2).清理bss
A>.清理bss段代码和裸机中讲的一样。注意表示bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。
clear_bss:
ldr r0, _bss_start/* find start of bss segment        */
ldr r1, _bss_end/* stop here                        */
mov r2, #0x00000000/* clear                            */

(3).ldr pc, _start_armboot ------>start_armboot();
A>.start_armboot是uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。
B>.远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。

C>.这里这个远跳转就是uboot第一阶段和第二阶段的分界线。
ldr pc, _start_armboot
_start_armboot:
.word start_armboot


2.5.13.4、总结:uboot的第一阶段做了哪些工作
(1)构建异常向量表
(2)设置CPU为SVC模式
(3)关看门狗
(4)开发板供电置锁
(5)时钟初始化
(6)DDR初始化
(7)串口初始化并打印"OK"
(8)重定位
(9)建立映射表并开启MMU
(10)跳转到第二阶段