这里继续 lowlevel_init 中的内容

这个函数是为了进行底层的一些初始化

lowlevel_init

预备环节

检查复位状态

lowlevel_init 中会对控制复位的寄存器中的值的16位和18位进行检测,判断当前处理器处于 DEEP-IDLE 还是 SLEEP 状态

对于不同的复位状态,有不同的处理方式

好比刚上电需要初始化 ddr,而从睡眠中唤醒可以直接使用 ddr

IO 恢复

关看门狗

避免看门狗在初始化的过程中复位

外部SRAM SROM初始化

供电锁存

判断运行在sram还是ddr中

1
2
3
4
5
6
7
8
9
10
/* when we already run in ram, we don't need to relocate U-Boot.
* and actually, memory controller must be configured before U-Boot
* is running in ram.
*/
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 是用不了的,这种情况需要做初始化;而休眠状态则不用

我们都知道 PC 中存储着下条指令的地址,通过获取 PC 的值,就可以知道当前的运行地址了

为了方便比较,将无关位清0。这里三星官方只留了[3:5]位进行比较

r1 中存储着 PC 清0后的值,r2 中存储着 ram 基地址清0后的值,当 r1 等于 r2 时说明代码还在 ram 中,需要对系统时钟、内存做初始化;不同就跳过初始化

系统时钟的初始化是硬件相关的,为了方便移植,大量使用了宏进行地址展开,通过对这些宏进行地址的设定就可以完成系统时钟的初始化

内存的初始化也是一样的方式,通过在配置文件中使用宏进行相关的地址定义来实现。uboot 中可用的物理地址范围为:0x30000000-0x4FFFFFFF一共512MB,其中0x30000000-0x3FFFFFFF为DM0,0x40000000-0x4FFFFFFF为DM1

之后就是串口的初始化,在串口初始化完成后会发送'O'

然后是对 TrustZone 初始化,TrustZone 是为消费电子产品构建一个安全框架来抵御各种可能的攻击的硬件架构

在检查完复位状态和关闭 ABB 后(ABB在网上也没搜出是什么东西),就会在串口打印‘K'

整个 lowlevel_init 完成的标志就是串口会打印 'OK’

回到 start.s 中

lowlevel_init 结束了,它主要的工作是:关看门狗、开发板供电锁存、系统时钟初始化、内存初始化、其他初始化、打印"OK"

重新设置栈

之前设置栈是在进入 lowlevel_init 前,当时 ddr 还没有初始化,只能使用内部的 sram,所以能使用的空间非常有限

为了准备 c 的运行环境,同时也可以防止栈溢出,重新设置栈,将栈设置在 ddr 中

再次检查运行位置

这里检查程序运行位置是为了判断是否需要将 uboot 重定位,冷启动的情况下,程序还运行在 ram 中,需要重定位到 ddr 中

uboot 启动分为两个部分,第一部分(16kb或8kb)在系统启动的时候,自动从启动介质中加载到处理器内部的 sram 中运行,但是第二部分还在启动介质中;第一部分的工作在 lowlevel_init 内基本完成了,在第一部分的启动程序结束前,需要将第二部分的代码加载到 ddr 中来

uboot 重定位

uboot 先将 0xD0037488 这个地址使用 ldr 装载到 r0 寄存器中,再将 r0 中的值与 0xEB200000 进行比较

0xD0037488 中存储着全局变量,通过硬件设置当前 uboot 的启动通道,如果是 SD0 就是 EB000000,SD1 就是 EB200000

这里是为了判断 uboot 的 BL1 部分是否已经拷贝

没有拷贝的话,下面还要进行启动方式的判断

1
2
ldr	r0, =INF_REG_BASE
ldr r1, [r0, #INF_REG3_OFFSET]

这里判断使用的寄存器中的值,在上面判断判断启动方式的时候已经写入了。这里使用的是 SD 卡启动,所以会跳转到 mmcsd_boot 进行代码的重定位

在 mmcsd_boot 下可以看到,代码的重定位是靠 movi_bl2_copy 完成的

movi_bl2_copy 位于 uboot/cpu/s5pc11x/movi.c 中

MMU 相关设置

MMU 可以实现虚拟地址和物理地址之间的映射

物理地址是设备在设计时赋予的地址,无法修改;虚拟地址是软件定义的地址

MMU 可以在物理地址与虚拟地址之间搭建桥梁,它有很多好处,比如可以控制物理内存的访问权限、给进程分配的内存总和能够大于物理内存大小等等

MMU 是一个硬件结构,全程是 memory management unit ,对它的控制可以通过对 CP15 寄存器的控制完成

下面开始介绍 MMU 启动的过程

访问 CP15 的指令有 MCR MRC,CP15 内部共有16个寄存器,各部分功能如上图

MMU 的启动有以下几个步骤

通过对 c3 寄存器的控制,获取域访问权限

设置 TTB(translation table base) ,通过 TTB 这个表可以映射虚拟地址与物理地址,单独一个地址是不够的,程序不可能只使用一个地址的空间;在映射空间的时候,是以块为单位的,块的大小是由 MMU 的性能和开发者的选择决定的。在ARM中支持3种块大小,细表1KB、粗表4KB、段1MB。获取 MMU 的基地址,写入到 c2 寄存器中就完成了 TTB 的设置。

此时还没有开启 MMU,通过对 c1 寄存器的置1就可以开启 MMU

映射表细节

1
2
3
4
5
6
7
8
/*
* MMU Table for SMDKC110
* 0x0000_0000 -- 0xBFFF_FFFF => Not Allowed
* 0xB000_0000 -- 0xB7FF_FFFF => A:0xB000_0000 -- 0xB7FF_FFFF
* 0xC000_0000 -- 0xC7FF_FFFF => A:0x3000_0000 -- 0x37FF_FFFF
* 0xC800_0000 -- 0xDFFF_FFFF => Not Allowed
* 0xE000_0000 -- 0xFFFF_FFFF => A:0xE000_0000 -- 0XFFFF_FFFF
*/

uboot 内部只做了简单的映射,除了上面的 Not Allowed 的地址,还是可以直接访问物理地址

虽然 uboot 中使用的是比较大的块大小,段的方式映射,但是4G/1MB=4096,如果一个个段进行映射,工作量还是很大的,为了方便映射,在 uboot 中使用了循环的方式进行映射

再次设置栈

ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)

CFG_UBOOT_SIZE 为 2MB,这里将栈设置在了 uboot 上方 2MB - 0x1000 的位置

这样可以避免内存空间被浪费,也可以保证栈空间的安全

清理 bss 段

通过从链接脚本中获得的 _bss_start 和 _bss_end 地址,将 bss 段清0

在将 bss 段清0后,通过将 start_armboot 的地址传入 PC 中,让 uboot 进入启动的第二阶段

uboot 第一阶段主要工作

  • 设置异常向量表
  • 处理器复位
  • lowlevel_init
  • uboot 重定位
  • MMU 建立映射表并启动
  • 准备启动 uboot 第二阶段