一、 存储器
SDRAM 同步动态随机存储器。同步时指工作需要时钟;动态是指需要不断刷新来保证数据不丢失;随机说明不是线性存储,而是由指定地址进行数据的读写。如CPU使用的外部内存,即内存条。SRAM Static Ram,是具有静态存取功能的内存,不需要刷电路就能保存存储的数据,比SDRAM速度快,一般用作高速缓冲存储器(Cache)。Norflash 非易失性外部存储介质,支持芯片内执行(XIP)。它有地址总线,CPU可以直接从Norflash中取值,直接在Norflash中运行程序。Nandflash 非易失性,虽然有数据总线,但没有地址总线,CPU不能够直接从发flash取值运行程序。常用于大量存储,类似硬盘。
二、 地址
1、概念
链接地址:链接脚本里指定的内存地址,编译时指定代码运行时应该所处的内存地址,是绝对地址。运行地址:当前代码实际运行时所处的地址,是相对地址(相对于当前程序开始运行时的内存地址)。加载地址:程序存储在flash中的地址。
2、 PIC与PDC
位置无关码(PIC,position independent code):依赖于程序当前运行的PC值(程序计数器),进行相对跳转,与位置(内存地址)无关。不管代码加载到任意地址,都能正常执行。如:B、BL、MOV、ADR……位置有关码:不依赖当前的PC值,是绝对跳转,与位置(内存地址)相关。程序执行时的运行地址和编译链接时给定的链接地址必须相同,才能正常运行。如:LDR PC,=LABEL(LDR伪指令)……总结:
正常情况下,链接地址==运行地址,此时,程序肯定能正常运行。当链接地址!=运行地址时,程序运行也不一定出错。如果是位置有关码必然会出错;要是位置无关码便可正常运行,u-boot第一阶段指令能够正常执行的原因就在于此。
三、SDRAM、Norflash、Nandflash地址分配
如图所示,是存储器的地址分配图,总共有8个BANK和一个4KB大小的SRAM。
SDRAM:只能够接在BANK6和BANK7(接片选线nGCS6、nGCS7)。Norflash:接在nGCS0上,选择BANK0,地址范围从0x0开始,上电时可以直接取0x0地址中的指令执行。Nandflash:
以页为单位读写,要先写命令,再给地址,才能读到NAND数据。Nandflash接在Nandflash控制器上而不是系统总线上,所以没有在8个BANK中分配地址。Nandfalsh控制器有个特殊功能,系统上电后,可以将Nandflash的前4KB引导程序搬移到SRAM内部。此时SRAM已经映射到了BANK0的0x0地址处,CPU对0x0~0x1000地址进行操作,实际上是在操作SRAM。不管是Norflash还是Nandflash启动,ARM都是从0x0地址开始执行。在代码小于4KB,当做单片机裸跑程序的时候,就不需要进行代码的搬移等操作了。
四、u-boot总体启动流程
总共有两个阶段,第一阶段执行start.s汇编代码,最终会跳转到第二阶段C代码入口继续执行。
第一阶段:
硬件设备初始化加载u-boot第二阶段代码到RAM空间设置好栈跳转到第二阶段代码入口
第二阶段:
初始化本阶段使用的硬件设备检测系统内存映射将内核从Flash读取到RAM中为内核设置启动参数调用内核
1、nand flash启动
首先需要将一个正确的bootloader烧写到Nandflash的最低位置,即从0x0开始烧写。当从Nandflash启动时,硬件上将片内SRAM映射到nGCS0片选空间,即0x0位置,并自动将Nandflash的前4kB代码拷贝到CPU片内SRAM中(SRAM-cache),这个内部RAM称为Stepping stone(垫脚石、起步石)。然后,CPU从SRAM的0x0地址处获取第一条指令开始执行。网上很多人说是CPU自动将Nandflash的前4kB拷贝到片内SRAM中,其实不然,实际是由Nandflash控制器(控制状态机)完成的,CPU根本没参与。
2、 nor flash启动
norflash支持片上执行代码(XIP),只需将bootloader烧写到Norflash的开始地址,由于Norflash会被映射到0x0地址处(nGCS0),上电后CPU会从0x0开始执行,也就是在Norflash中执行u-boot第一阶段,直到跳转到SDRAM中,继续执行第二阶段,也就不需要SRAM辅助运行第一阶段代码了。
五、u-boot.lds分析
OUTPUT_FORMAT(
"elf32-littlearm",
"elf32-littlearm",
"elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. =
0x00000000;
. = ALIGN(
4);
.
text :
{
arch/arm/cpu/arm1136_ambarella/start.o (.
text)
*(.
text)
}
. = ALIGN(
4);
.rodata : {
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata
*))) }
. = ALIGN(
4);
.data : {
*(.data) }
. = ALIGN(
4);
.got : {
*(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : {
*(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(
4);
__bss_start = .;
.bss (NOLOAD) : {
*(.bss) . = ALIGN(
4); }
_end = .;
}
ENTRY(_start)表示入口地址,程序运行时第一个被执行到的指令的地址便为_start。ld有多种方式设置程序的入口地址,按如下顺序(编号越前,优先级越高): 1、 ld命令行的-e选项 2、 链接脚本的ENTRY(SYMBOL)命令 3、 如果定义了start符号,使用start符号值 4、 如果存在.text section,使用.text section的首地址 5、 使用0地址
指定了_start的链接地址为0x0,但编译后,查看System.map会发现,_start的链接地址不是.text的当前地址0x0,原因是编译的时候将其更新了。config.mk中有这么几行:
LDFLAGS += -Bstatic -T
$(obj)u-boot.lds
$(PLATFORM_LDFLAGS)
ifne
q ($(TEXT_BASE),)
LDFLAGS += -Ttext
$(TEXT_BASE)
可知,如果TEXT_BASE不为空,在链接时,ld命令会把参数-Ttext指定的地址TEXT_BASE赋给.text。所以指定链接地址的方式有两种:链接脚本u-boot.lds中指定、config.mk中使用ld命令设定,当同时使用时,以ld命令为准。
最终,我们一般指定链接地址为SDRAM的起始地址,因为第二阶段代码会被搬移到SDRAM中执行,需要保证链接地址和运行地址的一致性。但是最初bootloader是被烧写到flash的0x0地址,ARM架构CPU也是从0x0地址取第一条指令执行,这样链接地址就与运行地址(0x0地址)不相等了,程序运行不会出错吗?
这个困扰我很久的问题,其实在本文的第二部分已经解答了。Bootloader第一阶段代码的运行地址确实是0x0地址,与链接地址不相等,但实际上,程序可以正常运行,所以可以断定第一阶段代码都是位置无关码(PIC)。事实也是如此,所有的目标地址寻址都是使用当前PC值加减偏移量的方法(进行相对跳转),因此该阶段代码在任何地址处都可以正常运行。