文章链接

UBoot 启动流程 (1) - 基本流程

UBoot 启动流程 (2) - 平台前期初始化阶段 - board_init_f

UBoot 启动流程 (3) - UBoot 程序重定位 - relocate_code

UBoot 启动流程 (4) - 平台后期初始化阶段 - board_init_r

UBoot 启动流程 (5) - UBoot 运行阶段 - main_loop

UBoot 启动流程 (6) - bootz 命令启动 Linux


目录

1.准备拷贝所需参数

2.将 uboot 程序拷贝至内存 - copy_loop

3.修正动态重定位信息 - fixloop

4.程序返回


// 函数调用栈
reset()
    |--> _main()
        |--> board_init_f() // 初始化核心硬件,如内存、串口等
            |--> relocate_code() // 将 uboot 从内存的低地址处拷贝至内存的高地址中
                |--> board_init_r() // 初始化剩余复杂外设,如显示、以太网等
                    |--> run_main_loop() // 启动 uboot 命令行

board_init_f() 进行基本外设 (串口、内存等) 初始化时,在内存中为栈、MMU、uboot 程序等分配了内存

接下来调用 relocate_code() 将 uboot 从内存的低地址处拷贝至内存的高地址中:

// arch/arm/lib/crt0.S
ENTRY(_main)
    ...
    bl  board_init_f

    // 代码重定位 (调用 relocate_code() 将 uboot 程序从内存的低地址处拷贝至内存的高地址中)
    ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */
    sub r9, r9, #GD_SIZE        /* new GD is below bd */
    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 */
    b   relocate_code
  • ldr sp, [r9, #GD_START_ADDR_SP] 将剩余可用内存的起始地址 gd->start_addr_sp 读取至 SP 栈寄存器:

    • R9 中保存的是全局数据结构体 gd 的地址;

    • GD_START_ADDR_SP 为剩余内存起始地址 start_addr_sp 在 gd 中的偏移量;

    • [r9, #GD_START_ADDR_SP] 表示获取 gd->start_addr_sp;

  • bic sp, sp, #7 对 SP 中的栈地址进行 8 字节对齐 (清除低 3 位);

  • ldr r9, [r9, #GD_BD] 将开发板信息结构体的地址 gd->bd 保存至 R9 寄存器;

  • sub r9, r9, #GD_SIZE 将 R9 中的值减去一个全局数据结构体的大小,即获取 bd 的结束地址 (作为新 gd 的起始地址);

  • adr lr, here 将返回地址 here 读取至 LR 寄存器;

  • ldr r0, [r9, #GD_RELOC_OFF] 将 uboot 程序在内存中的偏移量读取至 R0 寄存器:

    • R9 中的地址目前指向 bd 的末尾;

    • [r9, #GD_RELOC_OFF] 表示将该地址加上 uboot 程序在内存中的偏移量;

    • 因此,uboot 程序在内存中位于旧 bd 的末尾,旧 bd 在内存中位于 uboot 程序之前;

  • add lr, lr, r0 将 here 的地址加上 uboot 程序在内存中的偏移量,获取 here 在内存中的新地址;

  • ldr r0, [r9, #GD_RELOCADDR] 将 uboot 在内存中的起始地址 gd->relocaddr 加载至 R0 寄存器;

  • b relocate_code 调用 relocate_code() 将 uboot 程序拷贝至内存的高地址处;

relocate_code() 负责将 uboot 程序从内存的低地址拷贝至内存的高地址中 (uboot 程序重定位):

// arch/arm/lib/relocate.S
ENTRY(relocate_code)

    ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
    subs    r4, r0, r1      /* r4 <- relocation offset */
    beq relocate_done       /* skip relocation */
    ldr r2, =__image_copy_end   /* r2 <- SRC &__image_copy_end */
copy_loop:
    ldmia   r1!, {r10-r11}      /* copy from source address [r1]    */
    stmia   r0!, {r10-r11}      /* copy to   target address [r0]    */
    cmp r1, r2          /* until source end address [r2]    */
    blo copy_loop

    /*
     * fix .rel.dyn relocations
     */
    ldr r2, =__rel_dyn_start    /* r2 <- SRC &__rel_dyn_start */
    ldr r3, =__rel_dyn_end  /* r3 <- SRC &__rel_dyn_end */
fixloop:
    ldmia   r2!, {r0-r1}        /* (r0,r1) <- (SRC location,fixup) */
    and r1, r1, #0xff
    cmp r1, #23         /* relative fixup? */
    bne fixnext
    /* relative fix: increase location by offset */
    add r0, r0, r4
    ldr r1, [r0]
    add r1, r1, r4
    str r1, [r0]
fixnext:
    cmp r2, r3
    blo fixloop

relocate_done:
    ...
    bx  lr

ENDPROC(relocate_code)

1.准备拷贝所需参数

执行拷贝前,需要获取 uboot 程序在内存中的起始地址、结束地址以及偏移量:

// arch/arm/lib/relocate.S
ENTRY(relocate_code)
    ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
    subs    r4, r0, r1          /* r4 <- relocation offset */
    beq relocate_done           /* skip relocation */
    ldr r2, =__image_copy_end   /* r2 <- SRC &__image_copy_end */
  • ldr r1, =__image_copy_start 将 uboot 程序在内存中的原起始地址,读取至 R1 寄存器:

    • __image_copy_start 为 uboot 程序在内存中的起始地址,即链接地址 0x87800000;

  • subs r4, r0, r1 将 uboot 程序的偏移量保存至 R4 寄存器:

    • R0 中保存的是 uboot 程序在内存中的原起始地址;

    • R0 - R1 为 uboot 在内存中的新起始地址 - uboot 在内存中的原起始地址;

  • beq relocate_done 检查 R0 - R1 是否为 0,若为 0 则表示 uboot 程序已位于内存高地址中,无需执行拷贝;

  • ldr r2, =__image_copy_end 将 uboot 程序在内部 RAM 中的结束地址,保存至 R2 寄存器;

2.将 uboot 程序拷贝至内存 - copy_loop

copy_loop 循环将 uboot 拷贝至内存的高地址中,每次只拷贝 2 个 32-bit 的数据:

// arch/arm/lib/relocate.S
ENTRY(relocate_code)
    ...
copy_loop:
    ldmia   r1!, {r10-r11} /* copy from source address [r1]    */
    stmia   r0!, {r10-r11} /* copy to   target address [r0]    */
    cmp r1, r2             /* until source end address [r2]    */
    blo copy_loop
  • ldmia r1!, {r10-r11} 表示从寄存器 R1 中保存的地址开始,读取 64-bit 的数据,分别保存至 R10 和 R11 寄存器,然后递增 R1 中的地址:

    • R1 的初始值为 uboot 程序在内存中的原起始地址;

    • ! 表示每次读取数据完成后,都会将 R1 中的地址递增 8 字节 (64-bit),即 R1 = R1 + 8;

    • 因此,这行指令表示从内存低地址中读取 uboot 程序 (每次读取 8 字节,低 4 字节保存在 R10 中);

  • stmia r0!, {r10-r11} 表示将 R10 和 R11 寄存器中的值,保存至 R0 中的地址中,每次保存 64-bit,然后递增 R0 中的地址:

    • R0 的初始值为 uboot 程序在内存中的新起始地址;

    • 因此,这行指令表示将 uboot 程序写入/拷贝至内存;

  • cmp r1, r2 检查拷贝是否完成,R2 寄存器保存 uboot 程序在内存中的结束地址;

  • blo copy_loop 若 R1 和 R2 中的值不相等,跳转至 copy_loop 处再次执行;

综上,这段程序负责将 uboot 拷贝至内存,每次只拷贝 8 字节数据

3.修正动态重定位信息 - fixloop

一般程序在链接时指定了地址,取数据和取指令均使用固定的地址,这种方式无法实现程序重定位

uboot 在编译时使用了 -pie 选项,表示生成位置无关的代码:

// arch/arm/config.mk
# needed for relocation
LDFLAGS_u-boot += -pie

这种方式生成的代码,指令和数据寻址使用基地址加偏移量的方式,因此 uboot 程序可以在任意地址运行,如内部 RAM、内存等

使用 -pie 选项编译代码时,会生成动态重定位段 .rel.dyn,其中包含了数据和指令的重定位信息 (偏移量)

动态重定位段 .rel.dyn 中包含多个标签 Label,每个 Label 为 2 个 4 字节 (32-bit) 的数据,表示一个重定位信息,其中低 4 字节为 Label 的地址 (偏移量),高 4 字节为 Label 的标识 (符号信息),转换为 C 语言可表示为如下形式:

struct relocation_label {
    uint32_t   label_offset;   /* 需要重定位的地址 (偏移量) */
    uint32_t   label_info;     /* 重定位类型和符号信息 */
};

uboot 程序拷贝至内存的高地址中后,需要修正动态重定位段中的 Label,使程序可以正确访问数据:

// arch/arm/lib/relocate.S
ENTRY(relocate_code)
    ...
    /*
     * fix .rel.dyn relocations
     */
    ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
    ldr r3, =__rel_dyn_end   /* r3 <- SRC &__rel_dyn_end */
fixloop:
    ldmia   r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
    and r1, r1, #0xff
    cmp r1, #23          /* relative fixup? */
    bne fixnext

    /* relative fix: increase location by offset */
    add r0, r0, r4
    ldr r1, [r0]
    add r1, r1, r4
    str r1, [r0]
fixnext:
    cmp r2, r3
    blo fixloop
  • ldr r2, =__rel_dyn_start 将动态重定位段的起始地址,保存至 R2 寄存器;

  • ldr r3, =__rel_dyn_end 将动态重定位段的结束地址,保存至 R3 寄存器;

  • ldmia r2!, {r0-r1} 从 R2 中保存的地址开始,读取 64-bit 的数据,分别保存至 R0 和 R1 寄存器,然后递增 R2 中的地址 (R2 = R2 + 8);

    • 每次执行这条指令,都会从动态重定位段中递增读取一个单元的数据 (8 字节);

    • R0 保存 Label 地址 (偏移量),R1 保存 Label 标识 (符号信息);

  • and r1, r1, #0xff 将 Label 标识的低 8 位保存至 R1 寄存器;

  • cmp r1, #23 判断 Label 类型是否为相对地址 (0x17);

  • bne fixnext 若不为相对地址类型则跳过本次处理,因此修正动态重定位信息时,只处理相对地址类型的 Label;

  • add r0, r0, r4 计算 Label 地址在内存中的新值:

    • R4 中保存的是 uboot 程序的偏移量 = uboot 在内存中的新起始地址 - uboot 在内存中的原起始地址;

    • R0 中保存动态重定位 Label 的低 4 字节,即 Label 地址 (偏移量);

    • R0 + R4 表示将原本的 Label 地址加上新的偏移量,获取 Label 地址在内存中的新值;

  • ldr r1, [r0] 将 R0 指向的数据读取至 R1 寄存器,即读取该 Label 地址中对应的数据;

    • R0 中的地址完成重定位后,已正确指向内存中 uboot 程序的数据;

    • [r0] 表示从 R0 保存的地址中,读取数据;

  • add r1, r1, r4 将数据加上 uboot 程序的偏移量;

  • str r1, [r0] 将重定位后的变量地址,写入重定位后的 Label 中,即设置新地址所对应的值;

  • cmp r2, r3 检查重定位是否完成 (起始地址和结束地址是否相等);

  • blo fixloop 若重定位未完成,则跳转至 fixloop 再次执行;

4.程序返回

uboot 程序拷贝至内存高地址中,且动态重定位段修正完成后,返回函数/标号 here 处继续执行:

// arch/arm/lib/relocate.S
ENTRY(relocate_code)
    ...
relocate_done:
    ...
    bx  lr

ENDPROC(relocate_code)

执行 relocate_code() 前,已经将程序的返回地址 here 保存至返回地址寄存器 LR 中:

// arch/arm/lib/crt0.S
ENTRY(_main)
    ...
    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 */
    b   relocate_code
here:
    /* now relocate vectors */
     bl  relocate_vectors
     ...

下一篇:UBoot 启动流程 (4) - 平台后期初始化阶段 - board_init_r

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐