uboot之start.s分析2

注:本文是学习朱老师课程整理的笔记,基于uboot-1.3.4和s5pc11x分析。

前部分的分析见uboot之start.s分析1

lowlevel_init

/* Go setup Memory and board specific bits prior to relocation.*/
    ldr sp, =0xd0036000        /* end of sram dedicated to u-boot */
    sub sp, sp, #12           /* set stack */
    mov fp, #0

    bl  lowlevel_init   /* go setup pll,mux,memory */

这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。

在调用函数前初始化栈,主要原因是BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时,第一层的返回地址就丢了。

关于lowlevel_init函数的分析见start.s之lowlevel_init分析

供电锁存

    /* To hold max8698 output before releasing power on switch,
     * set PS_HOLD signal to high
     */
    ldr r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
    ldr r1, =0x00005301  /* PS_HOLD output high */
    str r1, [r0]

在lowlevel_init中其实已经设置过供电锁存。

  • 再次设置栈
/* get ready to call C functions */
    ldr sp, _TEXT_PHY_BASE  /* setup temp stack pointer */
    sub sp, sp, #12
    mov fp, #0          /* no previous frame, so fp=0 */

之前在调用lowlevel_init程序前设置过1次栈,那时候因为DDR尚未初始化,因此程序执行都是在SRAM中,所以在SRAM中分配了一部分内存作为栈。

本次设置栈是因为DDR已经被初始化了,因此要把栈挪移到DDR中,所以要重新设置栈;这里实际设置的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。

为什么放在uboot代码的下面呢?因为arm是满减栈,放在下面就不会覆盖掉uboot的代码。

为什么要再次设置栈?DDR已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中,栈放在那里要注意不能使用过多的栈否则栈会溢出,我们及时将栈迁移到DDR中也是为了尽可能避免栈使用时候的小心翼翼。

  • 再次判断是否重定位
    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 flash copy   */

在lowlevel_init中判断过一次重定位,这里再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同,上次判断是为了决定是否要执行初始化时钟和DDR的代码,这次判断是为了决定是否进行uboot的relocate。

冷启动时,当前情况是uboot的前一部分(16kb或者8kb),开机自动从SD卡加载到SRAM中运行,uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。

此时uboot的第一阶段已经即将结束了,结束之前要把第二部分加载到DDR中链接地址处(33e00000),这个加载过程就叫重定位。

重定位

/* SD/MMC BOOT */
    cmp     r2, #0xc
    moveq   r3, #BOOT_MMCSD 
    ……
/*将#BOOT_MMCSD写入了INF_REG3寄存器中存储着*/
    ldr r0, =INF_REG_BASE
    str r3, [r0, #INF_REG3_OFFSET]     
    ……
/*再将INF_REG3_OFFSET读出来,再和#BOOT_MMCSD去比较,确定是从MMCSD启动*/
    ldr r0, =INF_REG_BASE
    ldr r1, [r0, #INF_REG3_OFFSET]
    cmp r1, #BOOT_MMCSD
    beq mmcsd_boot
bl   movi_bl2_copy

真正的重定位是通过调用movi_bl2_copy函数完成的,在uboot/cpu/s5pc11x/movi.c中是一个C语言的函数

ch = *(volatile u32 *)(0xD0037488);
    copy_sd_mmc_to_mem copy_bl2 =
        (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
    ……
if (ch == 0xEB000000) {
        ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
            CFG_PHY_UBOOT_BASE, 0);
    ……
if (ch == 0xEB200000) {
        ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
            CFG_PHY_UBOOT_BASE, 0);

0xD0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为0xEB000000;从SD2通道启动时,这个值为0xEB200000。

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)。

  • 使能域访问(cp15的c3寄存器)
after_copy:

#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
    /* enable domain access */
    ldr r5, =0x0000ffff
    mcr p15, 0, r5, c3, c0, 0       @load domain access register

cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc和mcr指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄存器。
c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。

  • 设置TTB(cp15的c2寄存器)
/* Set the TTB register */
    ldr r0, _mmu_table_base
    ldr r1, =CFG_PHY_UBOOT_BASE
    ldr r2, =0xfff00000
    bic r0, r0, r2
    orr r1, r0, r1
    mcr p15, 0, r1, c2, c0, 0

转换表放置在内存中的,放置时要求起始地址在内存中要xx位对齐。转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时会自动去查转换表。
关于_mmu_table_base所指向的内容见lowlevel_init之mmu_table分析

使能MMU单元(cp15的c1寄存器)

/* Enable the MMU */
mmu_on:
    mrc p15, 0, r0, c1, c0, 0
    orr r0, r0, #1
    mcr p15, 0, r0, c1, c0, 0

cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。

MMU就是memory management unit,内存管理单元。MMU实际上是SoC中一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射。

MMU在CP15协处理器中进行控制,也就是说要操控MMU进行虚拟地址映射,方法就是对cp15协处理器的寄存器进行编程。

关于地址映射

  • 访问控制就是:在管理上对内存进行分块,然后每块进行独立的虚拟地址映射,然后在每一块的映射关系中同时还实现了访问控制(对该块可读、可写、只读、只写、不可访问等控制)。段错误(Segmentation fault)实际上就和MMU实现的访问控制有关。当前程序只能操作自己有权操作的地址范围(若干个内存块),如果当前程序指针出错访问了不该访问的内存块则就会触发段错误。
  • cache的工作和虚拟地址映射有关系。
    cache是快速缓存,意思就是比CPU慢但是比DDR块。CPU嫌DDR太慢了,于是乎把一些DDR中常用的内容事先读取缓存在cache中,然后CPU每次需要找东西时先在cache中找。如果cache中有就直接用cache中的;如果cache中没有才会去DDR中寻找。

    关于MMU的分析见arm MMU详解

  • 再次设置栈

stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
    ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
    ldr r0, _TEXT_BASE      /* upper 128 KiB: relocated uboot   */
    sub r0, r0, #CFG_MALLOC_LEN /* malloc area                      */
    sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
#if defined(CONFIG_USE_IRQ)
    sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
    sub sp, r0, #12     /* leave 3 words for abort-stack    */

#endif

这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。

我们实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。

  • 清理bss
clear_bss:
    ldr r0, _bss_start      /* find start of bss segment        */
    ldr r1, _bss_end        /* stop here                        */
    mov     r2, #0x00000000     /* clear                            */

clbss_l:
    str r2, [r0]        /* clear loop...                    */
    add r0, r0, #4
    cmp r0, r1
    ble clbss_l

bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。

跳到第二阶段

ldr pc, _start_armboot

_start_armboot:
    .word start_armboot

start_armboot是在uboot/lib_arm/board.c中。

这是一个C语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。

远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。

到此uboot启动第一阶段的代码分析完毕。

总结:uboot的第一阶段做了哪些工作
(1)构建异常向量表
(2)设置CPU为SVC模式
(3)关看门狗
(4)开发板供电置锁
(5)时钟初始化
(6)DDR初始化
(7)串口初始化并打印”OK”
(8)重定位
(9)建立映射表并开启MMU
(10)跳转到第二阶段

第二阶段代码分析见uboot之start_armboot分析1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值