Hakula

U-Boot for MIPS SoC
Introduction to Computer Systems II @ Fudan University, s...
扫描右侧二维码阅读全文
10
2020/08

U-Boot for MIPS SoC

Introduction to Computer Systems II @ Fudan University, spring 2020.

封面:「IRIDESCENT」/「Miv4t」

U-Boot for MIPS SoC

Das U-Boot - the Universal Boot Loader 是一个开源的用于嵌入式系统的引导加载程序,支持多种计算机体系结构,包括 ARM, RISC-V, MIPS, x86 等等。本文将在 MIPS 架构下试分析 U-Boot 的启动流程,自知理解尚浅,也没能找到完备的参考资料,如有错误还望指正。

0. 简单启动

对于一个最精简的引导加载程序(bootloader)来说,至少需要完成以下几项工作:[^1]

  1. 初始化硬件
  2. 将内核(kernel)镜像文件加载到 RAM 中
  3. 将启动参数传给内核
  4. 跳转到内核函数入口

如此精简的引导加载程序通常可以直接以汇编的形式保存在 ROM / FLASH 中。(为什么选择 ROM / FLASH?因为 RAM 是易失性存储器,断电就会丢失数据,而硬盘又不能被 CPU 直接访问。)但对于像 U-Boot 这样比较复杂的引导加载程序来说,ROM 的空间大小限制(512 B)难以保存完整的启动代码,因此就需要分为几个阶段:[^1] [^2]

  1. 加载 ROM / FLASH 中保存的汇编形式的 Primary Program Loader,初始化硬件,其中包括 Secondary Program Loader (SPL) 所需的内部 SRAM;然后在 SRAM 中载入 SPL,并跳转到其入口
  2. 在 SRAM 中的 SPL 可以使用 C 语言实现更复杂的功能了,本阶段的工作主要是初始化所有硬件,其中包括内核所需的外部 DRAM(First stage bootloader)
  3. 最后在 DRAM 中载入内核镜像文件,将启动参数传给内核,跳转到内核函数入口(Second stage bootloader)

以下将详细介绍 U-Boot 在这几个阶段具体完成了哪些工作。

1. U-Boot 启动流程

1.1 Primary Program Loader

Primary Program Loader 的代码位于 arch/mips/cpu/start.S,从 ENTRY(_start) 开始执行,到 END(_start) 结束。目前代码在 ROM / FLASH 中执行,本阶段主要完成了以下工作:[^3]

1.1.1 初始化异常向量(exception vector)

    .macro uhi_mips_exception
    move    k0, t9        # preserve t9 in k0
    move    k1, a0        # preserve a0 in k1
    li      t9, 15        # UHI exception operation
    li      a0, 0         # Use hard register context
    sdbbp   1             # Invoke UHI operation
    .endm
#if defined(CONFIG_ROM_EXCEPTION_VECTORS)
    /*
     * Exception vector entry points. When running from ROM, an exception
     * cannot be handled. Halt execution and transfer control to debugger,
     * if one is attached.
     */
    .org 0x200
    /* TLB refill, 32 bit task */
    uhi_mips_exception

    .org 0x280
    /* XTLB refill, 64 bit task */
    uhi_mips_exception

    .org 0x300
    /* Cache error exception */
    uhi_mips_exception

    .org 0x380
    /* General exception */
    uhi_mips_exception

    .org 0x400
    /* Catch interrupt exceptions */
    uhi_mips_exception

    .org 0x480
    /* EJTAG debug exception */
1:  b       1b
     nop

    .org 0x500
#endif

1.1.2 初始化协处理器 CP0 的寄存器

协处理器 CP0 用于处理异常和中断,这里初始化了以下寄存器:[^4]

  • CP0_COUNT:处理器周期计数器
    mtc0    zero, CP0_COUNT # clear cp0 count for most accurate boot timing
  • CP0_STATUS:处理器状态及控制器
    /* Init CP0 Status */
4:  mfc0    t0, CP0_STATUS
    and     t0, ST0_IMPL
    or      t0, ST0_BEV | ST0_ERL | STATUS_SET
    mtc0    t0, CP0_STATUS
  • CP0_WATCHLO, CP0_WATCHHI:(若 CP0_CONFIG1 未实现则初始化)断点(watchpoint)地址及控制器
    /*
     * Check whether CP0 Config1 is implemented. If not continue
     * with legacy Watch register initialization.
     */
    mfc0    t0, CP0_CONFIG
    bgez    t0, wr_legacy
     nop

wr_legacy:
    MTC0    zero, CP0_WATCHLO
    mtc0    zero, CP0_WATCHHI
  • CP0_CAUSE:上次异常的原因
  • CP0_COMPARE:时钟中断控制器(与 CP0_COUNT 一起使用)
wr_done:
    /* Clear WP, IV and SW interrupts */
    mtc0    zero, CP0_CAUSE

    /* Clear timer interrupt (CP0_COUNT cleared on branch to 'reset') */
    mtc0    zero, CP0_COMPARE

1.1.3 禁用缓存(如果需要)

跳转到 mips_cache_disable 禁用缓存,代码位于 arch/mips/lib/cache_init.S

#ifdef CONFIG_MIPS_CACHE_DISABLE
    /* Disable caches */
    PTR_LA  t9, mips_cache_disable
    jalr    t9
     nop
#endif

1.1.4 初始化硬件

跳转到 lowlevel_init 初始化 SRAM, I-Cache, D-Cache, CPU, DDR 等 SPL 所需的硬件,更新 CP0_CONFIG,代码位置取决于具体开发板,如 arch/mips/mach-mtmips/mt7628/lowlevel_init.S

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    /* Initialize any external memory */
    PTR_LA  t9, lowlevel_init
    jalr    t9
     nop
#endif

1.1.5 初始化缓存

跳转到 mips_cache_reset 初始化缓存,代码位于 arch/mips/lib/cache_init.S

#ifdef CONFIG_MIPS_CACHE_SETUP
    /* Initialize caches... */
    PTR_LA  t9, mips_cache_reset
    jalr    t9
     nop
#endif

1.1.6 初始化栈

执行 setup_stack_gd 宏初始化栈(可以通过 CONFIG_MIPS_INIT_STACK_IN_SRAM 设置在 SRAM 中初始化栈,这里就不展开讲了)。

    .macro setup_stack_gd
    li      t0, -16
    PTR_LI  t1, CONFIG_SYS_INIT_SP_ADDR
    and     sp, t1, t0              # force 16 byte alignment
    PTR_SUBU \
            sp, sp, GD_SIZE         # reserve space for gd
    and     sp, sp, t0              # force 16 byte alignment
    move    k0, sp                  # save gd pointer
#if CONFIG_VAL(SYS_MALLOC_F_LEN) && \
    !CONFIG_IS_ENABLED(INIT_STACK_WITHOUT_MALLOC_F)
    li      t2, CONFIG_VAL(SYS_MALLOC_F_LEN)
    PTR_SUBU \
            sp, sp, t2              # reserve space for early malloc
    and     sp, sp, t0              # force 16 byte alignment
#endif
    move    fp, sp

    /* Clear gd */
    move    t0, k0
1:
    PTR_S   zero, 0(t0)
    PTR_ADDIU t0, PTRSIZE
    blt     t0, t1, 1b
     nop

#if CONFIG_VAL(SYS_MALLOC_F_LEN) && \
    !CONFIG_IS_ENABLED(INIT_STACK_WITHOUT_MALLOC_F)
    PTR_S   sp, GD_MALLOC_BASE(k0)  # gd->malloc_base offset
#endif
    .endm
    /* Set up initial stack and global data */
    setup_stack_gd

1.1.7 调用函数 board_init_f()

调用函数 board_init_f() 进入下一阶段,代码位置取决于具体开发板,如 arch/mips/mach-mtmips/spl.c(在以前的版本中,位于 arch/mips/lib/board.c,此文件于 2014/11/27 被移除)。

    PTR_LA  t9, board_init_f
    jr      t9
    move    ra, zero

1.2 Secondary Program Loader

1.2.1 First stage bootloader

目前代码在 SRAM 中执行,本阶段主要是为 Second stage bootloader 做准备工作:[^2] [^5]

  1. 调用函数 spl_init() 初始化 bootstage(用于记录启动阶段的相关信息)、日志及全部硬件(包括 DRAM),代码位于 common/spl/spl.c
  2. 调用函数 mtmips_spl_serial_init() 初始化串口(serial),函数名及代码位置取决于具体开发板,如 arch/mips/mach-mtmips/mt7628/serial.c
  3. 调用函数 preloader_console_init() 初始化控制台(console),代码位于 common/spl/spl.c
  4. 调用函数 board_init_r() 进入下一阶段,代码位于 common/board_r.c

1.2.2 Second stage bootloader

到了这里,引导加载程序终于有足够的资源来完成所有应当完成的工作了。目前代码在 DRAM 中执行,结合反编译(objdump)最终的 u-boot 二进制文件(MIPS 架构)得到的代码可知,本阶段依次执行了以下这些函数(即 init_sequence_r[]),进行了一系列初始化工作,最后调用 run_main_loop() 进入主循环 main_loop():[^6] [^7]

  1. initr_trace()
  2. initr_reloc()
  3. initr_reloc_global_data()
  4. initr_barrier()
  5. initr_malloc()
  6. log_init()
  7. initr_bootstage()
  8. initr_console_record()
  9. initr_binman()
  10. initr_dm_devices()
  11. stdio_init_tables()
  12. serial_initialize()
  13. initr_announce()
  14. initr_trap()
  15. power_init_board()
  16. initr_flash()
  17. initr_env()
  18. initr_malloc_bootparams()
  19. initr_secondary_cpu()
  20. stdio_add_devices()
  21. initr_jumptable()
  22. console_init_r()
  23. misc_init_r()
  24. interrupt_init()
  25. timer_init()
  26. initr_ethaddr()
  27. initr_net()
  28. initr_ide()
  29. run_main_loop()

主循环 main_loop() 的代码位于 common/main.c,主要完成的工作是设置环境变量,初始化 CLI (Command Line Interface),最后启动操作系统内核,U-Boot 的启动流程到这里就结束了。

void main_loop(void)
{
    const char *s;

    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

    if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
        env_set("ver", version_string);  /* set version variable */

    cli_init();

    if (IS_ENABLED(CONFIG_USE_PREBOOT))
        run_preboot_environment_command();

    if (IS_ENABLED(CONFIG_UPDATE_TFTP))
        update_tftp(0UL, NULL, NULL);

    s = bootdelay_process();
    if (cli_process_fdt(&s))
        cli_secure_boot_cmd(s);

    autoboot_command(s);

    cli_loop();
    panic("No CLI available");
}

2. 研究历程

2.1 获得交叉编译工具链

由于目标架构是 MIPS,而本机则是 x86_64,因此我们需要一个交叉编译工具链(toolchain)。当然我们可以从各种地方直接下载得到,不过为了熟悉整个流程,这里我们还是手动编译了一下。相关代码已经上传到 GitHub,目标架构为 mipsel-unknown-elf

2.2 编译 U-Boot

配置完交叉编译环境后,就可以编译 U-Boot 了。由于手边没有开发板,我们使用 QEMU 来模拟 MIPS 环境。U-Boot 的源码位于 git://www.denx.de/git/u-boot.git

这里主要参考的是清华的教程,在此感谢清华前辈的工作。

2.3 研究 U-Boot 源码

主要就是以看源码为主,遇到理解不了的地方就上 Google 查。本来正常的思路应该是查阅官方文档,但是官方文档是 2008 年的版本,年久失修,其中提到的很多实现细节已经和当前代码有很大出入。此外,英文互联网似乎对 U-Boot 的研究分析并不太多,至少我好像搜不太到。而中文互联网又或者是在研究旧版本(然后互相抄来抄去),或者是在研究 ARM 架构,较新版本 MIPS 架构的相关资料也比较少见。所以整个研究过程还是比较煎熬的,资料匮乏,缺少指导,自己也没什么头绪。大概也只能研究到这种程度了(

研究期间遇到一处有趣的事情,就是 MIPS 架构是默认有一个 delay slot 的,但是我不知道。然后我就很困惑源码里 branch 以后,b 指令下面的那条指令什么时候执行。当然实际上它会在 branch 时就得到执行,虽然我们自己设计 MIPS CPU 时会把它 discard 掉,但按照规范它其实应该这样做。

2.4 心得体会

总体就是这样。其实我对这样的研究过程不太满意,我希望能有一个更完善的引导过程,像北航和清华一样(北航的 manual 也给了我一些启发),不然全程就会处于一种莫名其妙的状态,没有方向。毕竟我们目前还没有系统上过 OS 课程(只是学了个基础),这个期末项目说实在的,有点高估了学生的自学能力。

3. 参考资料

[^1]: Understanding Of U-Boot - So, today I thought...
[^2]: u-boot SPL 启动流程 - wowothink
[^3]: u-boot/u-boot/arch/mips/cpu/start.S - GitHub
[^4]: MIPS32™ Architecture For Programmers Volume III: The MIPS32™ Privileged Resource Architecture
[^5]: u-boot/u-boot/arch/mips/mach-mtmips/spl.c - GitHub
[^6]: u-boot/u-boot/common/board_r.c - GitHub
[^7]: u-boot 启动流程 - wowothink


版权声明:本文为原创文章,版权归 Hakula 所有。

本文链接:https://hakula.xyz/os/u-boot_mips.html

本文采用 CC BY-NC-SA 4.0 许可协议 进行许可。

最后修改:2020-08-10
如果我的文章对你有帮助,欢迎赞赏,谢谢你!111

发表评论

5 条评论

  1. Wu_Keduo   Windows 10 x64 Edition  Microsoft Edge 18.18363
    该评论仅评论双方可见
    1. Hakula   iOS 13.6  Google Chrome for iOS 84.0.4147.122
      @Wu_Keduo
      该评论仅评论双方可见
      1. Wu_Keduo   Windows 10 x64 Edition  Microsoft Edge 18.18363
        @Hakula
        该评论仅评论双方可见
    2. Wu_Keduo   Windows 10 x64 Edition  Microsoft Edge 18.18363
      @Wu_Keduo
      该评论仅评论双方可见
      1. Hakula   Windows 10 x64 Edition  Google Chrome 83.0.4103.116
        @Wu_Keduo
        该评论仅评论双方可见