以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
关于阶段的定义
第一阶段,即在内部SRAM运行的阶段,简单地理解为汇编阶段。此阶段主要涉及start.S文件,在cpu/s5pc11x/目录下。第一阶段以ldr pc _start_armboot为结束。
第二阶段,即在DDR中运行的阶段,简单地理解为C语言阶段。此阶段主要涉及start_armboot函数,在uboot/lib_arm/board.c文件的444~908行。
第一阶段完成的任务
- 异常向量表的实现;
- 设置进入特权模式,即SVC模式;
- 检查恢复状态;
- IO状态恢复;
- 关看门狗;
- 一些与SRAM、SROM相关的GPIO设置;
- 开发板的供电锁存;
- 时钟的初始化;
- DDR的初始化;
- 重新设置栈空间;
- uboot的重定位;
- 转换表的建立;
- 使能MMU。
可见,uboot的第一阶段初始化了SoC内部的一些部件,初始化DDR并且重定位。uboot被分割成两部分,即前8kb和整个ubooot,则前8k内容肯定包括第一阶段的操作任务,其中很重要的操作有重定位。
程序入口和SI工具
uboot中整个程序的入口取决于链接脚本中ENTRY声明的地方。因为链接脚本(即board\samsung\x210\u-boot.lds)中有ENTRY(_start),因此_start符号所在的文件就是起始文件,所处的位置就是起始位置。
通过使用SI工具,search—>lookup reference,找到该文件与起始位置。
start.S文件的解析
1、start.S包含的头文件
- config.h文件在mkconfig脚本中生成,此文件内容为“#include <configs/x210.h>”。
- version.h文件中的内容是“ #include "version_autogenerated.h" ”。version_autogenerated.h这个文件是在主Makefile中自动生成的,相关的生成代码如下。version_autogenerated.h文件中内容为“ #define U_BOOT_VERSION "U-Boot 1.3.4" ”。
- 由于定义了宏CONFIG_ENABLE_MMU,因此包含asm/proc/domain.h,实际包含include\asm-arm\proc-arm\domain.h文件。
2、启动代码的16字节头部
- arm7和arm9的arm指令集,一个字类型是32bit,即4个字节。
- 此段代码用16个字节填充占位(这些数字貌似可以任意?字节内容后续计算重新填充?)。这里填充的原因是SD卡需要16字节的校验?
- .word是arm汇编的伪指令,表示“当前地址的值为XX”。比如,.word 0x2000表示当前地址的值为0x2000,.word _start 表示当前地址的值为_start。
3、构建异常向量表
- 异常向量表由硬件决定,软件只是参照硬件设计来实现。
- 此向量表只是虚有其表,并未做非常细致的异常处理。
- 复位异常处理代码是b reset,reset是个函数。
- 上图中的最后一句是让内存16字节对齐,如果不对齐,用0xdeadbeef这个数字填充。
4、一些地址值
- _TEXT_BASE(4字节)这个内存地址存放的内容为TEXT_BASE(即0xc3e00000)。
- _TEXT_PHY_BASE(4字节)这个内存地址存放的内容为CFG_PHY_UBOOT_BASE(uboot的物理基地址)。
- CFG_PHY_UBOOT_BASE(定义在x210_sd.h中)为 MEMORY_BASE_ADDRESS + 0x3e00000。在x210_sd.h中有#define MEMORY_BASE_ADDRESS 0x30000000;因此CFG_PHY_UBOOT_BASE为0x33e00000,这个是uboot在DDR中的物理地址。
- 总结,即标签表示地址,.word后面的表示此内存地址中存储的值。比如_bss_end: .word _end表示地址_bss_end上存放的值是_end。
5、reset中断处理函数
- 因为0xd3=1101 0011,则cpu设置为SVC模式、arm状态,禁止FIQ、IRQ中断。
- 整个uboot工作时,cpu一直处于SVC模式。
6、Cpu的初始化(设置l1,l2cache和MMU等内容)
7、识别并暂存启动介质选择
- 启动介质由SoC的OM0:OM5这6个引脚的高低电平决定
- 由#define PRO_ID_BASE 0xE0000000和#define OMR_OFFSET 0x04得到寄存器0xE000 0004。这个寄存器会根据OM引脚状况,硬件自动设置值。
- 此三行代码后,r2存储了一个数字,后续通过该数字进行启动介质的判断。
![]()
- 通过判断r2中的值,来确定是从哪里启动的。如果r2中的值为0xc,那从SD卡启动,然后把#BOOT_MMCSD(#define BOOT_MMCSD 0x3)赋给r3。
- 最后两行中,因为有#define INF_REG_BASE 0xE010F000和#define INF_REG3_OFFSET 0x0c,则合成寄存器0xE010F00C,然后把r3中的值放入其中。
8、第一次设置栈
- 这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。
- 之所以要初始化栈,是因为接下来调用的lowlevel_init函数中还要调用其他函数。bl只会将返回地址存储到LR中,但是只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。
9、lowlevel_init函数
lowlevel_init函数在uboot\board\samsung\x210\lowlevel_init.S中。
(1)检查复位状态。
- 因为复杂CPU支持多种复位状态(冷上电、休眠复位等),因此在复位代码中检查复位状态,判断到底是哪一种。冷上电时DDR需要初始化,而休眠状态下复位不需要再次初始化DDR。
(2)IO状态恢复。
(3)关看门狗。
(4)一些与SRAM、SROM相关的GPIO设置(下图)。
(5)开发板的供电锁存(下图)。
(6)判断当前代码执行位置(判断在SRAM还是DDR中)。
- 为什么要判断?因为BL1在SRAM中有一份,在DDR中也有一份。如果是冷启动,则当前代码是在SRAM中运行的BL1;如果是低功耗状态的复位,则当前代码是在DDR中运行的。判断的目的是指导后面代码的运行。譬如在lowlevel_init.S中判定当前代码的运行地址,就是为了确定是否执行时钟初始化和初始化DDR的代码。如果当前代码是在SRAM中,说明是冷启动,则需要初始化时钟和DDR;如果当前代码是在DDR中,说明是热启动,则时钟和DDR都不用再次初始化。
(7)时钟初始化:system_clock_init。
(8)内存的初始化:mem_ctrl_asm_init。
- 该函数在uboot/cpu/s5pc11x/s5pc110中。该函数和裸机中初始化DDR代码是一样的。实际裸机中初始化DDR的代码就是从这里抄的。配置值中有一个和裸机中讲的不一样,即DMC0_MEMCONFIG_0。
- 它在裸机中配置值为0x20E01323,在uboot中配置为0x30F01313。
- 在裸机中DMC0的256 MB内存地址范围是0x20000000-0x2FFFFFFF;在uboot中DMC0的256MB内存地址范围为0x30000000-0x3FFFFFFF。
- DMC0上允许的地址范围是20000000-3FFFFFFF(一共是512MB),而实际只接了256MB物理内存,SoC允许我们给这256MB挑选地址范围。
- 在uboot中,可用的物理地址范围为:0x30000000-0x4FFFFFFF,一共512MB,其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。
- 注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值,主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。
(9)串口初始化:打印一个“O”。
(10)pop {pc}返回前通过串口打印“K”。
至此,lowlevel_init函数结束。
10、第二次设置栈
- 第一次设置栈,是在调用lowlevel_init函数前。那时程序在SRAM中执行,所以在SRAM中分配了一部分内存作为栈。
- 第二次因为DDR已经被初始化,因此要把栈挪移到DDR中,所以要重新设置栈。实际设置的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。因为是满减栈,所以栈向下增长。注意uboot基地址在0x33e00000,向上增长。
- 为什么要再次设置栈?因为DDR已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中;原来SRAM中内存大小空间有限,栈放在那里要注意不能使用过多,否则栈会溢出。我们及时将栈迁移到DDR中也是为了尽可能避免使用栈时的诸多不便。
11、再次判断运行地址是在SRAM中还是DDR中
(1)上次判断是为了决定是否要执行初始化时钟和DDR的代码,本次判断是为了决定是否进行uboot的重定位。上图中最后一行代码如果不执行,则说明需要重定位。
- 冷启动时,uboot的前一部分(16kb或者8kb)开机自动从SD卡加载到SRAM中运行,uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。此时uboot的第一阶段即将结束(第一阶段该做的事基本做完了),结束之前要把第二部分加载到DDR中链接地址处(0x33e00000),这个加载过程就叫重定位。
(2)如下图。
- D0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB000000;从SD2通道启动时,这个值为EB200000。
- 我们确定是从MMCSD启动,因此最终跳转到mmcsd_boot函数中去执行重定位动作,即把SD卡中相应的内容复制到内存中。
- 真正的重定位是通过调用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)。
12、配置MMU
- MMU(memory management unit),内存管理单元。它实际上是SOC中一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射。
- MMU单片在CP15协处理器中进行控制,也就是说要操控MMU进行虚拟地址映射,方法就是对cp15协处理器的寄存器进行编程。
13、第三次设置栈
- 这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。
- 我们实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。
14、清理bss
- 注意表示bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。
15、跳转到start_armboot函数
- start_armboot函数在uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。
- “ldr pc _start_armboot”这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。
- 远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。
- 这里这个远跳转就是uboot第一阶段和第二阶段的分界线。