uboot代码分为两部分 uboot-spl和uboot,uboot-spl为上一节所说的BL2。使用三星提供的工具,可以把uboot-spl.bin转化成BL2。
通过编译uboot-2018我们可以产生uboot-spl镜像,由于BL1会负责把BL2拷贝到0x02023400处,所以需要定义uboot-spl的链接地址为0x02023400
#define CONFIG_SPL_TEXT_BASE 0x02023400
下面是spl部分的简要流程
1.vectors.S (arch/arm/lib)
.macro ARM_VECTORS //macro 用来定义一段宏,可以类比成c语言的内联函数
b reset
ldr pc, _undefined_instruction
....
.endm //结束宏
_start: //最开始执行的地方
ARM_VECTORS //跳到定义处,在定义处可以看出 是b reset
2.start.S (arch/arm/cpu/armv7)
reset:
b save_boot_params
/*
* 如果不是处于HYP模式,则失能FIQ和IRQ,设置cpu为SVC32模式
* ARM的主要工作模式
* 处理器工作模式 说明
* 用户模式(usr) ARM处理器正常的程序执行状态
* 系统模式(sys) 运行具有特权的操作系统任务
* 快中断模式(fiq) 支持高速数据传输或通道处理
* 管理模式(svc) 操作系统保护模式
* 数据访问终止模式(abt) 用于虚拟存储器及存储器保护
* 中断模式(irq) 用于通用的中断处理
* 用户模式和系统模式之外的5种模式称为异常模式
*/
mrs r0, cpsr // mrs:把程序状态寄寄存器的数据传输到通用寄存器中
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode // bits bic=bic+ne=if teq not equal,excute bic
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0 // msr:把通用寄存器的数据传输到程序状态寄存器中
bl cpu_init_cp15 ----->
----> ENTRY(cpu_init_cp15)
/*
* 失能 L1 I/D
* Cache
* 1) Cache 是位于 CPU与主存储器DRAM之间一个模块,为了解决cpu和内存之间的速度匹配问题
* Cache又分为I-cache(用来存指令)和D-cache(用来存数据)
* 为什么要让Cache失效?
* 2)我们在使用cache的时候要经过一系列的配置,在没配置之前是不能使用的。所以我们要关闭cache,但是在关闭
* cache之前cache里面可能已经有数据了,为了不影响我们 的代码,所以要先让其失效,在进行关闭
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
/*
* 关闭mmu
* 关闭mmu后cpu发出来的内存地址不会被mmu捕获,相当于直通内存地址到设备,
* 这样我们可以直接定义物理地址,比如定义uboot的加载地址:
* CONFIG_SYS_TEXT_BASE=0x43e00000
* 0x43e00000处于外部ram的地址范围内
* 外部ram的地址空间为0x4000_0000 到 0xA000_0000
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
......
mcr p15, 0, r0, c1, c0, 0
mov r5, lr @ Store my Caller //前面的bl指令在跳转的时候会保存地址到链接寄存器lr
mov pc, r5 @ back to my caller //赋值给pc寄存器,这样下一条指令就会跳回去调用处了
ENDPROC(cpu_init_cp15)
<------
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
/*
* arm-linux-objdump -S u-boot-spl > u-boot-spl.S 查看反汇编代码
* 没有看到有bl cpu_init_crit相关的机器码,说明这个宏定义定义了,不会执行cpu_init_crit
* ldr r0, =_start
* 2023470: e59f006c ldr r0, [pc, #108] ; 20234e4
* mcr p15, 0, r0, c12, c0, 0 @Set VBAR
* 2023474: ee0c0f10 mcr 15, 0, r0, cr12, cr0, {0}
* #ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
* bl cpu_init_crit
* #endif
* #endif
* bl _main
* 2023478: eb000528 bl 2024920 <_main>
*/
bl cpu_init_crit
#endif
bl _main
3.crt0.S (arch/arm/lib)
ENTRY(_main)
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR) //等于0x2040000
bic r0, r0, #7 //8字节对其
mov sp, r0 //保存到栈指针寄存器sp 下面可以使用c语言了
bl board_init_f //跳到spl_boot.c d的void board_init_f(unsigned long bootflag)
4.spl_boot.c (arch/arm/mach-exynos)
void board_init_f(unsigned long bootflag){
do_lowlevel_init() --->
----> ......
system_clock_init(); //配置时钟树
debug_uart_init(); //设置窗口
_debug_uart_init(); \
s5p_serial_init(uart);
/* enable FIFOs, auto clear Rx FIFO */
writel(0x3, &uart->ufcon);
writel(0, &uart->umcon);
/* 8N1 */
writel(0x3, &uart->ulcon);
/* No interrupts, no DMA, pure polling */
writel(0x245, &uart->ucon);
copy_uboot_to_ram();---->
----> .....
copy_bl2_from_emmc = get_irom_func(EMMC44_INDEX);//获取三星固话的拷贝函数,不开源的直接使用就行
//这里把uboot归为bl2了,命名不用管它。
//#define CONFIG_SYS_TEXT_BASE 0x43E00000
copy_bl2_from_emmc(0x400, CONFIG_SYS_TEXT_BASE); //拷贝uboot所在的emmc地址到0x43E00000,大小为0x400 block
uboot = (void *)CONFIG_SYS_TEXT_BASE; //运行uboot
(*uboot)();
}
通过编译uboot-2018我们可以产生uboot镜像,前面的uboot-spl会把uboot拷贝到指定地址,我这里指定的地址为外部ram的地址0x43e00000
CONFIG_SYS_TEXT_BASE=0x43e00000
上面个的代码(*uboot)();会运行uboot,下面是uboot部分的简要流程。
1.vectors.S (arch/arm/lib)
.macro ARM_VECTORS //macro 用来定义一段宏,可以类比成c语言的内联函数
b reset
ldr pc, _undefined_instruction
....
.endm
_start: //最开始执行的地方
ARM_VECTORS //跳到定义处,在定义处可以看出 是b reset
2.start.S (arch/arm/cpu/armv7)
reset:
b save_boot_params
..... @ set SVC mode ...
bl _main
3.crt0.S (arch/arm/lib)
ENTRY(_main)
.....
bl board_init_f //跳到board_f.c的void board_init_f(ulong boot_flags)
#if ! defined(CONFIG_SPL_BUILD)
ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
/* 下面三句代码是 很有意思的,我们来重点分析一下 */
/* adr是个位置无关的加载指令,及无论 here的链接地址是多少,adr加载的都是当前pc + 偏移(adr lr,here到here的偏移)
然后得到新的规划的uboot和旧的uboot之家的偏移量给r0
之后把偏移和旧的uboot的here加起来,最终得到新的uboot里面的here的地址,然后放进lr
等进入下面的relocate_code函数,返回的时候就直接返回到新的uboot的代码段运行了
*/
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off看下面的图 */
add lr, lr, r0
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr,给relocate的传参是将要重定位的起始地址 */
/* 这里会调到relocate_code是重定位代码,然后再return */
b relocate_code /* 注意这里必须使用b跳转,这样返回时才能用前面设置的lr中设置的here地址 */
here:
ldr pc, =board_init_r /* this is auto-relocated! 跳到board_init_r */
4.Board_r.c (common)
//这一部分就是去调用各种各样的初始化函数,网上有很多博客去分析这些代码,这里我就不分析了,具体可以参考文章尾部的参考
void board_init_r(gd_t *new_gd, ulong dest_addr){
initcall_run_list(init_sequence_r)--->
----> static init_fnc_t init_sequence_r[] = {
initr_trace,
initr_reloc,
log_init,
initr_bootstage,
board_init
......
}
}
下面这张图仅用来参考reloc这个操作(地址不对应本文的地址)。其实从这张图就可以直观的看出重定位代码是做了什么操作,重定位代码就是把uboot搬移到高地址,并且在搬移的过程中要保障搬移后的uboot执行不会出现跳转异常之类的内存错误。
更加具体的流程我就不分析,可以参考这几篇文章,这几篇文章分析的不错,有很大的参考意义:
u-boot v2018.01 启动流程分析.
从零开始之uboot、移植uboot2017.01(五、board_init_f分析).