- 分析uboot 启动流程
- 硬件:启明智显推出M4核心板 (https://gitee.com/qiming-zhixian/m4-openwrt)
1.U-boot和SPL概述
U-Boot 分为 uboot-spl 和 uboot 两个组成部分。SPL 是 Secondary Program Loader 的简称,第二阶段程序加载器。 对于嵌入式SOC(System On Chip)芯片来说,芯片内本身含有SRAM用于将闪存中的bootloader(uboot)加载到RAM来运行, 但由于片内RAM大小限制,加载不了完整的U-boot程序,所以需要在加载uboot之前加载一个精简版的SPL,SPL可用来初始化DDR等一些必须的外设,然后在把完整的Uboot加载到DDR里面运行。
系统冷启动过程中经历的不同阶段有:
BROM(Boot ROM) -> SPL -> OpenSBI -> U-Boot -> Kernel
因此在 U-Boot SPL 运行之前,BROM 已经对系统进行了基本的初始化。
下载代码:
# 下载主工程代码
git clone https://gitee.com/qiming-zhixian/m4-openwrt.git
# 下载feeds软件包
cd m4-openwrt
git clone https://gitee.com/qiming-zhixian/zx-feeds.git
1.1. 启动参数
http://doc.panel-tag.cn/m4/op/index.html
了解启动参数,首先要了解启动流程。正常启动过程包含几种跳转,每一种跳转都会有相应的参数传递。
1.1.1.正常启动流程
正常启动过程详细介绍:
- BROM 根据启动方式去定位 SPL 位置,初始化对应非易失性存储器,加载 SPL 到 SRAM 上,BROM 运行在 M-Mode;
- SPL 被执行;
- SPL PBP 部分代码初始化 DRAM,之后初始化非易失性存储器;
- SPL 加载u-boot.itb(包含 OpenSBI,U-Boot,DTB)到 DRAM;
- OpenSBI被执行,切换到S-Mode;
- U-Boot 被执行;
- U-Boot加载设备树DTB,环境变量ENV;
- U-Boot加载Kernel,DTB到DRAM;
- Kernel被执行,加载DTB;
- Rootfs被执行,切换到U-Mode,整个系统启动完成;
1.1.2. BROM 传递的参数
BROM 跳转到 SPL 时传递的参数:
OpenSBI 跳转到 U-Boot 时传递的参数:
传递参数结构体定义如下,具体实现可参考:
m4-openwrt/package/boot/uboot-m4/src/arch/riscv/include/asm/arch-zx/m4/boot_param.h:
37 union boot_params {
38 /*
39 * Save registers. Later's code use these register value to:
40 * 1. Get parameters from previous boot stage
41 * 2. Return to Boot ROM in some condition
42 *
43 * For SPL:
44 * a0 is boot param
45 * a1 is private data address
46 * For U-Boot:
47 * a0 is hartid from opensbi
48 * a1 is opensbi param
49 * a2 is opensbi param
50 * a3 is boot param from SPL
51 */
52 unsigned long regs[22];
53 struct {
54 unsigned long a[8]; /* a0 ~ a7 */
55 unsigned long s[12]; /* s0 ~ s11 */
56 unsigned long sp;
57 unsigned long ra;
58 } r;
59 };
1.1.2. BROM 参数获取的过程
无论是 SPL 还是 U-Boot,在执行时的第一步动作就是保存这些启动参数, 具体实现可参考 arch/riscv/cpu/start.S
43 _start:
44 /* Allow the board to save important registers */
45 j save_boot_params
46 save_boot_params_ret:
47 #if CONFIG_IS_ENABLED(RISCV_MMODE)
48 csrr a0, CSR_MHARTID
49 #endif
save_boot_params 的实现在源文件 arch/riscv/mach-zx/lowlevel_init.S
10 #ifdef CONFIG_32BIT
11 #define LREG lw
12 #define SREG sw
13 #define REGBYTES 4
14 #define RELOC_TYPE R_RISCV_32
15 #define SYM_INDEX 0x8
16 #define SYM_SIZE 0x10
17 #else
18 #define LREG ld
19 #define SREG sd
20 #define REGBYTES 8
21 #define RELOC_TYPE R_RISCV_64
22 #define SYM_INDEX 0x20
23 #define SYM_SIZE 0x18
24 #endif
28 ENTRY(save_boot_params)
29 la t0, boot_params_stash
30 SREG a0, REGBYTES * 0(t0)
31 SREG a1, REGBYTES * 1(t0)
32 SREG a2, REGBYTES * 2(t0)
33 SREG a3, REGBYTES * 3(t0)
34 SREG a4, REGBYTES * 4(t0)
35 SREG a5, REGBYTES * 5(t0)
36 SREG a6, REGBYTES * 6(t0)
37 SREG a7, REGBYTES * 7(t0)
38 SREG s0, REGBYTES * 8(t0)
39 SREG s1, REGBYTES * 9(t0)
40 SREG s2, REGBYTES * 10(t0)
41 SREG s3, REGBYTES * 11(t0)
42 SREG s4, REGBYTES * 12(t0)
43 SREG s5, REGBYTES * 13(t0)
44 SREG s6, REGBYTES * 14(t0)
45 SREG s7, REGBYTES * 15(t0)
46 SREG s8, REGBYTES * 16(t0)
47 SREG s9, REGBYTES * 17(t0)
48 SREG s10, REGBYTES * 18(t0)
49 SREG s11, REGBYTES * 19(t0)
50 SREG sp, REGBYTES * 20(t0)
51 SREG ra, REGBYTES * 21(t0)
52 j save_boot_params_ret
53 ENDPROC(save_boot_params)
传递的参数被保存在全局变量 boot_params_stash 中,SPL 或者 U-Boot 可从中获取相关信息。 在保存参数时,不仅将 a0,a1 寄存器保存起来,还将其他重要寄存器一起保存。
arch/riscv/mach-zx/m4/boot_param.c:
13 /*
14 * Save boot parameters and context when save_boot_params is called.
15 */
16 union boot_params boot_params_stash __section(".data");
1.1.3. 参数获取接口
无论是在 SPL 还是在 U-Boot 中,都可以通过下列接口获取上一级引导程序传递的参数。
- enum boot_reason aic_get_boot_reason(void);
- enum boot_device aic_get_boot_device(void);
相关的定义可参考文件:
- arch/riscv/include/asm/arch-zx/m4/boot_param.h
- arch/riscv/mach-zx/m4/boot_param.c
1.2.SPL 阶段
ZX 平台上的 SPL(Secondary Program Loader) 是第一级引导程序(FSBL, First Stage Boot Loader), 同时也是第二级程序加载器。SPL 运行在 SRAM 中,其最重要的任务有两个:
-
完成 DDR,并且使能 Cache
-
加载和验证 U-Boot
SPL RISCV 的启动整体流程
_start // arch/riscv/cpu/start.S
|-> save_boot_params // arch/riscv/mach-zx/lowlevel_init.S
| // BROM 跳转到 SPL 执行的时候,传递了一些参数,这里首先需要将这些参数保存起来
|
|-> csrw MODE_PREFIX(ie), zero // Disable irq
|-> li t1, CONFIG_SPL_STACK // 设置sp寄存器
|-> jal board_init_f_alloc_reserve // common/init/board_init.c
| // 预留初始 HEAP 的空间
| // 预留 GD 全局变量的空间
|
|-> jal board_init_f_init_reserve
| // common/init/board_init.c, init gd area
| // 此时 gd 在 SPL STACK 中。
|
|-> jal icache_enable // arch/riscv/cpu/c906/cache.c 使能指令高速缓存
|-> jal dcache_enable // 使能数据高速缓存
|
|-> jal debug_uart_init // drivers/serial/ns16550.c
| // 初始化调试串口,如果使能
|
|-> board_init_f // arch/riscv/lib/spl.c
| |-> spl_early_init() // common/spl/spl.c
| |-> spl_common_init(setup_malloc = true) // common/spl/spl.c
| |->