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