u-boot 代码分为两个阶段第一阶段是汇编,入口是 arch/arm/cpu/armv7/start.S,第二阶段是 C 语言, 入口是 board.c。
第一阶段:
1. 异常向量表定义
2. 设置 SVC32 模式(ARM 七种工作模式)
3. 调用 cpu_init_crit 进行 cpu 相关初始化
1) 清 TLB(页面缓存)、关 MMU 及 Cache 等
2) 转入低级初始化 lowlevel_init 函数
主要是对系统时钟、片外内存(DDR3)、串口、 nand(这里初始化 nand 主要是为第二阶段搬 uboot 到内存而准备的)等进行初始化。
4. 判断启动开关进行自搬移
5. 跳转到 C 入口 board_init_f( ) 具体过程分析:
1. 异常向量表定义
.globl _start //把_start 声明为全局标号
_start: b reset //跳转到 rest 标号
ldr pc, _undefined_instruction //未定义异常入口
ldr pc, _software_interrupt //软中断异常入口
ldr pc, _prefetch_abort //预取指异常入口
ldr pc, _data_abort //取数据异常入口
ldr pc, _not_used //保留
ldr pc, _irq //irq 中断入口(普通中断)
ldr pc, _fiq //快速中断入口
_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*/
2.进入管理模式
reset:
/* 进入管理模式 ,ARM 芯片进入管理模式代码固定
* set the cpu to SVC32 mode 因为在管理模式下具有超级权限,可以对芯片上的所有功能进行配
置, 以及模式自由切换。
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
3. 调用 cpu_init_crit 函数进行 CPU 底层初始化:
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //CPU 初始化
#endif
4. cpu_init_crit 子函数具体源码分析
进行了 cache 初始化(关 cache),关 MMU, 失效 TLB(页面缓存),最后调用 lowlevel_init 函数对
CPU 进行时钟配置,存储器控制器配置等。
cpu_init_crit:
//cache_init 在 lowlevel_init.S board\samsung\Tiny4412
bl cache_init //初始化高速缓存,其实什么都没有做
/* 使用协处理器指令,使 L1 缓存无效
* Invalidate L1 I/D cache ,mmu,mpu
*/
mov r0, #0 @set up for MCRmcr p15,0,r0,c8,c7,0 @invalidate TLBs
mcr p15,0,r0,c7,c5,0 @invalidate icache
/*
禁止MMU 及caches
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/*
* lowlevel_init 在
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
mov ip, lr @ persevere link reg across call
bl lowlevel_init @ go setup pll,mux,memory
mov lr, ip @ restore link
mov pc, lr @ back to my caller
5. lowlevel_init 源码具体分析
检测 cpu 当前状态,判断是否是由于休眠被唤醒运行到这里的,如果是直接跳转到 wakeup_reset,
否则则要进行代码,时钟配置, ddr 配置,代码复制, 代码重新定位。
1) 检测是否是睡眠被唤醒的。
lowlevel_init.S board\samsung\Tiny4412
.globl lowlevel_init
lowlevel_init:
/* use iROM stack in bl2 */
//设置栈指针指向内部 RAM 的最后地址,因为栈是往下生长的
ldr sp, =0x02060000
push{lr} //把返回地址压入栈中保存
/* check reset status 检查复位状态 */
ldr r0, =(INF_REG_BASE + INF_REG1_OFFSET)
ldr r1, [r0]
/* Sleep wakeup reset 测试是否是睡眠状态唤醒的 */
ldr r2, =S5P_CHECK_SLEEP
cmp r1, r2 //比较两个值是否相同
beq wakeup_reset //如果是睡眠状态下唤醒执行的,直接跳转到
2) 读取启动方式
如果不是休眠唤醒,则继续以下代码:
bl read_om //检测启动方式
/* 检测 u-boot 自己是否已经在 RAM 中运行
* 如果已经在 RAM 中则不需要搬运了。
* 否则要进行存储器参数配置,让 RAM 工作,然后复制 u-boot 到 RAM
* 有一种可能,是通过仿真器把代码直接下载到 DDR 中,这样也不需要 对 DDR 进行初始化和代
码复制。
* 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 after_copy /* r0 == r1 then skip sdram init and u-boot.bin loading */
_TEXT_BASE 是 u-boot 链接地址,也就是 u-boot 应该在 ddr 中的内存地址。 以上代码判断 u-boot 是
否已经在 ddr中,如果在,则不需要进行时钟,存储器配置,以及代码复制工作,直接跳转到 after_copy
函数执行。
如没有在内存,则继续执行以下代码:
通过读取芯片的 ID 寄存器判断芯片型号,从而执行不同的代码, 这一步不是必须的,如果你的 uboot 想支持多个型号的芯片,可以仿照以下代码添加。 在 tiny4412 中不执行下面的代码。
#ifndef CONFIG_SMDKC220
ldr r0, =CHIP_ID_BASE
ldr r1, [r0]
lsr r1, r1, #8
and r1, r1, #3
cmp r1, #2
bne v310_1 //三星公司的 s5pv310 芯片 A8 芯片
#endif
以上代码 4412 没有用。
以下开始进行系统时钟,存储器控制器初始化, 这部分代码是必须的。任何芯片, 任何开发板都是
必须的, 属于主干代码。
/* 初始化系统时钟
* 在 clock_init_tiny4412.S board\samsung\Tiny4412
* 属于芯片级别代码,不同芯片配置方法不同,需要参考具体的芯片手册。 一般根据芯片公司的
演示板代码进行修改,如果时钟按照官方板的时钟运行,则不需要进行任何修改。
*/
bl system_clock_init //调用系统时钟初始化函数。
/* 存储器控制器初始化,目的让开发板的 DDR3 内存可以正常工作起来
* mem_init_tiny4412.S board\samsung\Tiny4412
* 这部分代码属于板级代码。不同开发板使用的内存芯片可能 不同。时序参数就不同,用户需要
根据自己使用的内存芯片参数修改。
*/
bl mem_ctrl_asm_init //初始化存储器控制器
/* 调试串口初始化 , 不是必须的。
* lowlevel_init.S board\samsung\Tiny4412
*/
bl uart_asm_init
由于是 4412 芯片,直接路过 s5pv310 芯片初始化代码。
b 1f
以下的 s5pv310 芯片代码,和 4412 芯片无关, 所以上面直接跳过这部分代码。
v310_1:
/* init system clock */
bl system_clock_init
/* Memory initialize */
bl mem_ctrl_asm_init
进行 Trustzone 初始化(这个不是必须的,不是所有的芯片都具有 Trustzone 功能), 从启动设备中
加载 u-boot.bin 代码到 DDR3 中。
1:
//初始 Trustzone
bl tzpc_init //个别芯片
//跳转到 load_uboot ,这个函数负责加载了 u-boot.bin 到 DDR3 中。
//lowlevel_init.S board\samsung\Tiny4412
b load_uboot //主干,加载 u-boot
6. load_uboot 源码具体分析
这个函数具体实现在 lowlevel_init.S board\samsung\Tiny4412。 这个函数具体工作是根据启动方式,
调用不同的函数来实现代码复制,如下:
load_uboot:
ldr r0, =INF_REG_BASE
ldr r1, [r0, #INF_REG3_OFFSET]
//判断是否是 NAND 启动
cmp r1, #BOOT_NAND
beq nand_boot //如果是则跳转
//判断是否是 ONENAND 启动
cmp r1, #BOOT_ONENAND
beq onenand_boot
//判断是否是 MMCSD 启动
cmp r1, #BOOT_MMCSD
beq mmcsd_boot //跳转到 mmcsd 启动处去复制代码到内存
//判断是否是 EMMC 启动
cmp r1, #BOOT_EMMC
beq emmc_boot
//判断是否是 EMMC 通道 4 启动
cmp r1, #BOOT_EMMC_4_4
beq emmc_boot_4_4
//判断是否是 NOR 启动
cmp r1, #BOOT_NOR
beq nor_boot
//判断是否是第二启动设备
cmp r1, #BOOT_SEC_DEV
beq mmcsd_boot //跳转到 mmcsd 启动处去复制代码到内存
7. mmcsd_boot 源码具体分析
以下以 sd 卡启动方式来例子说明 u-boot 代码复制功能。 根据以上代码,如果是 sd 卡启动,则会调
用 mmcsd_boot 函数进行代码复制, mmcsd_boot 子函数代码如下所示。
mmcsd_boot:
//主干代码,真正的复制,
//把 sd 中的 u-boot 复制 ddr 中的起始位置 0x40000000
//Irom_copy.c arch\arm\cpu\armv7\Exynos
bl movi_uboot_copy //调用 bl1 的代码复制 mmcsd 的位置到 ddr 中。
//复制完成后,程序跳转到 after_copy 函数
b after_copy
先分析 movi_uboot_copy 函数实现,此函数是使用 c 语言实现的, 位 于
arch\arm\cpu\armv7\Exynos\ Irom_copy.c 中,具体源码如下:
void movi_uboot_copy(void)
{
#ifdef CONFIG_RAM_TEST //可以 tiny4412.h 中定义宏开启内存测试功能
uboot_mem_test(); //内存测试,复制前先确定内存是否已经可用
#endif
#ifdef CONFIG_CORTEXA5_ENABLE
SDMMC_ReadBlocks(MOVI_UBOOT_POS, MOVI_UBOOT_BLKCNT, 0x40000000);
#endif
// bl1 的读函数复制 u-boot 到 ddr 中
SDMMC_ReadBlocks(MOVI_UBOOT_POS, MOVI_UBOOT_BLKCNT, CONFIG_PHY_UBOOT_BASE);
#ifdef CONFIG_SECURE_BOOT //检测是否支持安全启动, tiny4412 中没有定义,不执行
if (Check_Signature((SB20_CONTEXT *)SECURE_CONTEXT_BASE,
(unsigned char*)CONFIG_PHY_UBOOT_BASE, PART_SIZE_UBOOT-256,
(unsigned char*)(CONFIG_PHY_UBOOT_BASE+PART_SIZE_UBOOT-256), 256) != 0)
{
while(1);
}
#endif
} 以
上代码中,核心代码是:
SDMMC_ReadBlocks(MOVI_UBOOT_POS, MOVI_UBOOT_BLKCNT, CONFIG_PHY_UBOOT_BASE);
SDMMC_ReadBlocks 是一个宏,定义如下:
#define SDMMC_ReadBlocks(uStartBlk, uNumOfBlks, uDstAddr) \
(((void(*)(u32, u32, u32*))(*((u32 *)EXTERNAL_FUNC_ADDRESS)))(uStartBlk, uNumOfBlks, uDstAddr))
(((void(*)(u32, u32, u32*))(*((u32 *)EXTERNAL_FUNC_ADDRESS)))(uStartBlk, uNumOfBlks, uDstAddr)) 是
一个函数指针, 函数所在地址是 EXTERNAL_FUNC_ADDRESS, 其定义如下:
#define ISRAM_ADDRESS 0x02020000
#define SECURE_CONTEXT_BASE 0x02023000
#define EXTERNAL_FUNC_ADDRESS (ISRAM_ADDRESS + 0x0030)
这个地址就是 0x02020030 ,位于内部 RAM 地址空间中。 这个指针中的值是由 iROM 程序进行设
置的,指向了 iROM 代码中实现的真正 SD 卡读函数代码函数首地址。
所以 SDMMC_ReadBlocks 可以看成是一个 SD 卡读函数, 有 3 个参数,分别是起始扇区号 uStartBlk,
要读取的扇区数量 uNumOfBlks,读取数据后存放到内存的哪个起始地址 uDstAddr。
调用传递的实际参数:
SDMMC_ReadBlocks(MOVI_UBOOT_POS, MOVI_UBOOT_BLKCNT, CONFIG_PHY_UBOOT_BASE);
关于参数定义:
MOVI_UBOOT_POS:
Movi_partition.h arch\arm\include\asm\Arch-exynos
#define eFUSE_SIZE (1 * 512) // 512 Byte eFuse, 512 Byte reserved
#define MOVI_BLKSIZE (1<<9) /* 512 bytes */
#define PART_SIZE_FWBL1 (8 * 1024)
#define MOVI_FWBL1_BLKCNT (PART_SIZE_FWBL1 / MOVI_BLKSIZE)
由此可以计算出 MOVI_FWBL1_BLKCNT 大小是 (8 * 1024)/521 --> 16 个 blk
//由此可以计算出 MOVI_FWBL1_BLKCNT 大小是(16 * 1024) / 512 -->32 个 blk
#define PART_SIZE_BL1 (16 * 1024)
#define MOVI_BL1_BLKCNT (PART_SIZE_BL1 / MOVI_BLKSIZE)
//由此可以计算出 MOVI_UBOOT_POS 值为 512/512 +16 + 32 = 49 -->第 49 个 blk 。 这个值刚刚好是 SD 卡烧写 u-boot.bin 的扇区位置。
#define MOVI_UBOOT_POS ((eFUSE_SIZE / MOVI_BLKSIZE) + MOVI_FWBL1_BLKCNT + MOVI_BL1_BLKCNT)
//由此可以计算出 MOVI_UBOOT_BLKCNT 值为 (328 * 1024)/512 或(512 * 1024)/512
MOVI_UBOOT_BLKCNT:
#ifdef CONFIG_TRUSTZONE
#define PART_SIZE_UBOOT (328 * 1024)
#else
#define PART_SIZE_UBOOT (512 * 1024)
#endif
#define MOVI_UBOOT_BLKCNT (PART_SIZE_UBOOT / MOVI_BLKSIZE) /* 328KB */
CONFIG_PHY_UBOOT_BASE:
#define CONFIG_PHY_UBOOT_BASE CONFIG_SYS_SDRAM_BASE + 0x3e00000
//CONFIG_SYS_SDRAM_BASE 是外扩展的 DDR 的起始地址。
经过这一步,SD卡的u-boot.bin代码被完整的复制到DDR中。接下来函数返回,程序执行:
b after_copy
8.after_copy源码具体分析
这个子函数进入主要工作内容是开启 MMU,设置第二阶段运行标志,进入 C 语言阶段,调用 Board.c arch\arm\Lib\Board.c 文件中 board_init_f,这个函数对板子外设进行初始化。
after_copy: //复制好后指示灯亮
#ifdef CONFIG_SMDKC220 // 有定义
/* set up C2C */
ldr r0, =S5PV310_SYSREG_BASE
ldr r2, =GENERAL_CTRL_C2C_OFFSET
ldr r1, [r0, r2]
ldr r3, =0x4000
orr r1, r1, r3
str r1, [r0, r2]
#endif
//干,使能 mmu,
#ifdef CONFIG_ENABLE_MMU
bl enable_mmu
#endif
/* store second boot information in u-boot C level variable 设置第二阶段运行标志*/
ldr r0, =CONFIG_PHY_UBOOT_BASE //#define CONFIG_SYS_SDRAM_BASE 0x40000000
sub r0, r0, #8
ldr r1, [r0]
ldr r0, _second_boot_info
str r1, [r0]
/* Print 'K' */
ldr r0, =S5PV310_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
//开始进入 C 语言阶段
ldr r0, _board_init_f //C 语言入口
mov pc, r0 //程序执行 board_init_f 函数
_board_init_f:
//Board.c arch\arm\Lib ,这个函数属于架构级别的,一般不需要修改
.word board_init_f //C 语言入口
_second_boot_info:
// 在 tiny4412/tiny4412.c
// 定义的一个变量, unsigned int second_boot_info = 0xffffffff;
.word second_boot_info