riscv uboot 启动流程分析 - SPL启动流程
- 分析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
| |-> fdtdec_setup(); // lib/fdtdec.c 获取dtb的地址,并验证合法性
| | // 只对带有“u-boot,dm-pre-reloc”属性节点进行解析,初始化驱动模型的根节点,扫描设备树创建udevice,uclass
| |-> dm_init_and_scan(!CONFIG_IS_ENABLED(OF_PLATDATA)); // drivers/core/root.c
| |-> dm_init(); // driver model, initiate virtual root driver
| | |-> INIT_LIST_HEAD(DM_UCLASS_ROOT_NON_CONST); // 初始化uclass链表
| | |-> device_bind_by_name()
| | | | // drivers/core/device.c
| | | | // 加载"root_driver"name, gd->dm_root
| | | |-> lists_driver_lookup_name()
| | | | |-> ll_entry_start(struct driver, driver); // 获取driver table起始位置
| | | | |-> ll_entry_count(struct driver, driver); // 获取driver table长度
| | | | // drivers/core/lists.c
| | | | // 采用 U_BOOT_DRIVER(name) 声明的 driver,从driver table中获取struct driver数据
| | | |
| | | | // 初始化udevice 与对应的uclass,driver绑定
| | | |-> device_bind_common(); // drivers/core/device.c
| | | |-> uclass_get(&uc)
| | | | |-> uclass_find(id); // 判断对应的uclass是否存在
| | | | |-> uclass_add(id, ucp); // 如果不存在就创建
| | | | |-> lists_uclass_lookup(id); // 获取uclass_driver结构体数据
| | | |-> uclass_bind_device(dev) // uclass绑定udevice drivers/core/uclass.c
| | | |-> drv->bind(dev) // driver绑定udevice
| | | |-> parent->driver->child_post_bind(dev)
| | | |-> uc->uc_drv->post_bind(dev)
| | |
| | |-> device_probe(gd->dm_root) // drivers/core/device.c
| | |-> uclass_resolve_seq(dev) // 通过dtb解析获得设备差异数据
| | |-> uclass_pre_probe_device(dev); // probe前操作
| | |-> drv->probe(dev); // 执行driver的probe操作
| | |-> uclass_post_probe_device(dev); // probe后操作
| |
| |-> dm_scan(pre_reloc_only);
| | // 扫描和绑定由 U_BOOT_DEVICE 声明的驱动。
| | // 一般用在 SPL OF_PLATDATA 的情况
| |-> dm_scan_plat(pre_reloc_only);
| | |-> lists_bind_drivers(DM_ROOT_NON_CONST, pre_reloc_only);
| | |-> bind_drivers_pass(parent, pre_reloc_only);
| | |-> device_bind_by_name();
| |
| |-> dm_extended_scan(pre_reloc_only);
| | |-> dm_scan_fdt(pre_reloc_only); // 扫描设备树并与设备驱动建立联系
| | | |-> dm_scan_fdt_node(gd->dm_root, ofnode_root(), pre_reloc_only); //扫描设备树并绑定root节点下的设备
| | | |-> ofnode_first_subnode(parent_node) // 获取设备树的第一个子节点
| | | |-> ofnode_next_subnode(node) // 遍历所有的子节点
| | | |-> ofnode_is_enabled(node) // 判断设备树的子节点是否使能
| | | |-> lists_bind_fdt(parent, node, NULL, pre_reloc_only); // 绑定设备树节点,创建新的udevicd drivers/core/lists.c
| | | |-> ofnode_get_property(node, "compatible", &compat_length); // 获取compatible
| | | |-> driver_check_compatible() // 和driver比较compatible值
| | | |-> device_bind_with_driver_data() // 创建一个设备并绑定到driver drivers/core/device.c
| | | |-> device_bind_common() // 创建初始化udevice 与对应的uclass,driver绑定
| | |
| | | // /chosen /clocks /firmware 一些节点本身不是设备,但包含一些设备,遍历其包含的设备
| | |-> dm_scan_fdt_ofnode_path(nodes[i], pre_reloc_only);
| | |-> ofnode_path(path); // 找到节点下包含的设备
| | |-> dm_scan_fdt_node(gd->dm_root, node, pre_reloc_only);
| |
| |-> dm_scan_other(pre_reloc_only);
| | // 扫描使用者自定义的节点 nothing
|
|-> spl_clear_bss // arch/riscv/cpu/start.S
|-> spl_relocate_stack_gd // 切换stack 和 gd 到dram空间
|-> board_init_r() // common/spl/spl.c
|-> spl_set_bd() // board data info
| // 设置完 bd 之后,才能 enable d-cache
|-> mem_malloc_init()
| // init heap
| // - CONFIG_SYS_SPL_MALLOC_START
| // - CONFIG_SYS_SPL_MALLOC_SIZE>
|
|-> spl_init
| |-> spl_common_init
| // 由于前面已经调用了 spl_early_init,
| // 这里不再调用 spl_common_init
|
|-> timer_init(); // lib/time.c nothing
|-> spl_board_init(); // arch/riscv/mach-zx/spl.c nothing
|
|-> initr_watchdog // enable watchdog,如果使能
|-> dram_init_banksize(); // 如果使能
|-> board_boot_order() // common/spl/spl.c
| |-> spl_boot_device(); // arch/riscv/mach-zx/spl.c
| |-> aic_get_boot_device(); // arch/riscv/mach-zx/boot_param.c
| // 从 boot param 中获取启动介质信息
|
|-> boot_from_devices(spl_boot_list)
| |-> spl_ll_find_loader() // 根据boot device找到spl_load_image指针
| | // 这里可能是各种介质的 load image 函数
| | // SPL_LOAD_IMAGE_METHOD() 定义的 Loader
| | // 可能是 MMC/SPI/BROM/...
| |
| |-> spl_load_image // 以emmc启动为例
| |-> spl_mmc_load_image // common/spl/spl_mmc.c
| |-> spl_mmc_load // 具体可看后面的流程
|
|-> spl_perform_fixups // vendor hook,用于修改device-tree传递参数
|-> spl_board_prepare_for_boot // vendor hook, 可不实现
|-> jump_to_image_no_args // 跳转到u-boot执行
1.2.1.SPL 启动具体分析
SPL 入口函数:
Because SPL images normally have a different text base, one has to be configured by defining CONFIG_SPL_TEXT_BASE. The linker script has to be defined with CONFIG_SPL_LDSCRIPT.
SPL的入口是由链接脚本决定:
arch/riscv/Kconfig:
251 config SPL_LDSCRIPT
252 default "arch/riscv/cpu/u-boot-spl.lds"
arch/riscv/cpu/u-boot-spl.lds:
9 MEMORY { .spl_mem : ORIGIN = IMAGE_TEXT_BASE, LENGTH = IMAGE_MAX_SIZE }
10 MEMORY { .bss_mem : ORIGIN = CONFIG_SPL_BSS_START_ADDR, \
11 LENGTH = CONFIG_SPL_BSS_MAX_SIZE }
12
13 OUTPUT_ARCH("riscv")
14 ENTRY(_start)
15
16 SECTIONS
17 {
18 . = ALIGN(4);
19 .text : {
20 arch/riscv/cpu/start.o (.text)
21 *(.text*)
22 } > .spl_mem
23
24 . = ALIGN(4);
25 .rodata : {
26 *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
27 } > .spl_mem
28
29 . = ALIGN(4);
30 .data : {
31 *(.data*)
32 } > .spl_mem
33 . = ALIGN(4);
启动流程:
-
第一阶段 arch/riscv/cpu/start.S 文件。
-
第二阶段启动阶段中, 会重点涉及到 位于 common/board_r.c 的 board_init_f 函数和位于 common/board_r.c 文件的的 board_init_r 函数。board_init_f 会初始化必要的板卡和 global_data 结构体,然后调用 board_init_r 进行下一阶段的板卡初始化工作。board_init_r 运行结束之后, 会调用位于 common/main.c 的 main_loop() 函数进行。
1.2.1.1.第一阶段 start.S
1).代码最开始,根据cpu的bit定义了一套后续使用的宏定义。
19 #ifdef CONFIG_32BIT
20 #define LREG lw
21 #define SREG sw
22 #define REGBYTES 4
23 #define RELOC_TYPE R_RISCV_32
24 #define SYM_INDEX 0x8
25 #define SYM_SIZE 0x10
26 #else
27 #define LREG ld
28 #define SREG sd
29 #define REGBYTES 8
30 #define RELOC_TYPE R_RISCV_64
31 #define SYM_INDEX 0x20
32 #define SYM_SIZE 0x18
33 #endif
2).入口_start
主要做的事情:
- 读取控制状态寄存器(CSR) mhartid 的值到a0寄存器;
- 保存上一级boot 传递的参数;
- 设置异常入口函数、关中断;
- 设置堆栈;
- 设置icache和dcache;
- 调用board_init_f;
- board_init_r.
.section .text
.globl _start
_start: /* 链接脚本里面指定了这里是uboot的入口 */
#if CONFIG_IS_ENABLED(RISCV_MMODE)
csrr a0, CSR_MHARTID
#endif
- RISCV_MMODE 就是 machine mode
arch/riscv/Kconfig:
choice
prompt "Run Mode"
default RISCV_MMODE
config RISCV_MMODE
bool "Machine"
help
Choose this option to build U-Boot for RISC-V M-Mode.
config RISCV_SMODE
bool "Supervisor"
help
Choose this option to build U-Boot for RISC-V S-Mode.
- csrr a0, CSR_MHARTID 读取控制状态寄存器(CSR) mhartid 的值到a0寄存器。CSR_MHARTID 存储了当前硬件线程(hart)的ID。
保存上一级boot 传递的参数:
64 /* save hart id and dtb pointer */
65 mv tp, a0
66 mv s1, a1
设置异常入口函数、关中断:
68 la t0, trap_entry
69 csrw MODE_PREFIX(tvec), t0
70
71 /* mask all interrupts */
72 csrw MODE_PREFIX(ie), zero
设置堆栈,地址对齐,每个core都会分配自己的堆栈地址,挑选一个主core进行初始化,其他的core wait在wait_for_gd_init.
91 /*
92 * Set stackpointer in internal/ex RAM to call board_init_f
93 */
94 call_board_init_f:
95 li t0, -16 /* -16 的16进制就是 0xffff fff0 ,用来对齐使用的 */
96 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) /* 这里是给SPL使用的 */
97 li t1, CONFIG_SPL_STACK
98 #else
99 li t1, CONFIG_SYS_INIT_SP_ADDR
100 #endif
101 and sp, t1, t0 /* 设置堆栈指针,并16byte对齐,force 16 byte alignment */
102
103 call_board_init_f_0:
104 mv a0, sp
105 jal board_init_f_alloc_reserve /* 从sp高地址开始预留一段内存给global_data使用,返回的是减去预留后的地址,也就是gd的首地址*/
106
107 /* setup stack ,设置新的堆栈,根据core的数量进行划分 */
108 #ifdef CONFIG_SMP
109 /* tp: hart id */
110 slli t0, tp, CONFIG_STACK_SIZE_SHIFT /* tp保存的是当前core id,根据core id 进行sp的划分,每一个core分一块内存用作sp */
111 sub sp, a0, t0
112 #else
113 mv sp, a0
114 #endif
115 ...
设置icache和dcache:
186 /* Enable cache */
187 jal icache_enable
188 jal dcache_enable
设置参数调用board_init_f 进行环境的初始化,后面会详细讲board_init_f
194 mv a0, zero /* a0 <-- boot_flags = 0 */
/*
* 如果是uboot,那么会调用common下面的board_f.c里面的函数,spl可能不是这个,要看编译配置
* 这个函数会顺序执行init_sequence_f数组里面的函数
*/
195 la t5, board_init_f
196 jalr t5 /* jump to board_init_f(),执行完后,跳到本文件里面的relocate_code继续往下走 */
board_init_f :
arch/riscv/lib/spl.c:
22 __weak void board_init_f(ulong dummy)
23 {
24 int ret;
25
26 ret = spl_early_init();
27 if (ret)
28 panic("spl_early_init() failed: %d\n", ret);
29
30 arch_cpu_init_dm();
31
32 preloader_console_init();
33
34 ret = spl_board_init_f();
35 if (ret)
36 panic("spl_board_init_f() failed: %d\n", ret);
37 }
下面是SPL专用的代码,主要用于清bss,设置堆栈,最后跳到board_init_r.
198 #ifdef CONFIG_SPL_BUILD
199 spl_clear_bss:
200 la t0, __bss_start
201 la t1, __bss_end
202 beq t0, t1, spl_stack_gd_setup
203
204 spl_clear_bss_loop:
205 SREG zero, 0(t0)
206 addi t0, t0, REGBYTES
207 blt t0, t1, spl_clear_bss_loop
208
209 spl_stack_gd_setup:
210 jal spl_relocate_stack_gd
211
212 /* skip setup if we did not relocate */
213 beqz a0, spl_call_board_init_r
214 mv s0, a0
215
216 /* setup stack on main hart */
217 #ifdef CONFIG_SMP
218 /* tp: hart id */
219 slli t0, tp, CONFIG_STACK_SIZE_SHIFT
220 sub sp, s0, t0
221 #else
222 mv sp, s0
223 #endif
224
225 /* set new stack and global data pointer on secondary harts */
226 spl_secondary_hart_stack_gd_setup:
227 la a0, secondary_hart_relocate
228 mv a1, s0
229 mv a2, s0
230 mv a3, zero
231 jal smp_call_function
232
233 /* hang if relocation of secondary harts has failed */
234 beqz a0, 1f
235 mv a1, a0
236 la a0, secondary_harts_relocation_error
237 jal printf
238 jal hang
239
240 /* set new global data pointer on main hart */
241 1: mv gp, s0
242
243 spl_call_board_init_r:
244 mv a0, zero
245 mv a1, zero
246 jal board_init_r
247 #endif
board_init_r:
|-> board_init_r() // common/spl/spl.c
|-> spl_set_bd() // board data info
| // 设置完 bd 之后,才能 enable d-cache
|-> mem_malloc_init()
| // init heap
| // - CONFIG_SYS_SPL_MALLOC_START
| // - CONFIG_SYS_SPL_MALLOC_SIZE>
|
|-> spl_init
| |-> spl_common_init
| // 由于前面已经调用了 spl_early_init,
| // 这里不再调用 spl_common_init
|
|-> timer_init(); // lib/time.c nothing
|-> spl_board_init(); // arch/riscv/mach-zx/spl.c nothing
|
|-> initr_watchdog // enable watchdog,如果使能
|-> dram_init_banksize(); // 如果使能
|-> board_boot_order() // common/spl/spl.c
| |-> spl_boot_device(); // arch/riscv/mach-zx/spl.c
| |-> aic_get_boot_device(); // arch/riscv/mach-zx/boot_param.c
| // 从 boot param 中获取启动介质信息
|
|-> boot_from_devices(spl_boot_list)
| |-> spl_ll_find_loader() // 根据boot device找到spl_load_image指针
| | // 这里可能是各种介质的 load image 函数
| | // SPL_LOAD_IMAGE_METHOD() 定义的 Loader
| | // 可能是 MMC/SPI/BROM/...
| |
| |-> spl_load_image // 以emmc启动为例
| |-> spl_mmc_load_image // common/spl/spl_mmc.c
| |-> spl_mmc_load // 具体可看后面的流程
|
|-> spl_perform_fixups // vendor hook,用于修改device-tree传递参数
|-> spl_board_prepare_for_boot // vendor hook, 可不实现
|-> jump_to_image_no_args // 跳转到u-boot执行
-
MMC 加载
SPL 从 MMC 加载 U-Boot 的处理过程。程序编码的时候,针对 MMC 设备添加了对应的加载程序支持,如 spl_mmc.c 中,通过使用宏: SPL_LOAD_IMAGE_METHOD(“MMC1”, 0, BOOT_DEVICE_MMC1, spl_mmc_load_image);
将 spl_mmc_load_image 函数添加到 .u_boot_list_2_spl_image_loader_* 段。在 SPL 初始化过程中,通过 boot_from_devices(spl_boot_list) 函数调用,检查当前项目所支持的 SPL 读取的存储介质类型,然后依次检查是否存在对应的程序加载器。
board_init_r() // common/spl/spl.c
|-> boot_from_devices(spl_boot_list)
|-> spl_ll_find_loader() // 根据boot device找到spl_load_image指针
// 这里可能是各种介质的 load image 函数
// SPL_LOAD_IMAGE_METHOD() 定义的 Loader
// 可能是 MMC/SPI/BROM/...
找到 SPL MMC Loader 之后,从项目配置的指定 Sector 读取数据。
- CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR
boot_from_devices(spl_boot_list); // common/spl/spl.c
|-> spl_ll_find_loader() // 根据boot device找到spl_load_image指针
| // 此处通过遍历固件的 .u_boot_list_2_spl_image_loader_* 段
| // 找到当前支持的存储介质,然后逐个尝试
|
|-> spl_load_image(loader);
|-> loader->load_image(spl_image, &bootdev);
spl_mmc_load_image(); // common/spl/spl_mmc.c
|-> spl_mmc_load();
|
+-------------+
|
spl_mmc_load();
|-> spl_mmc_find_device(&mmc, bootdev->boot_device);
| |-> mmc_initialize
| |-> mmc_probe
| |-> uclass_get(UCLASS_MMC, &uc)
| |-> device_probe(dev)
| |-> uclass_resolve_seq(dev)
| |-> pinctrl_select_state(dev, "default")
| | |-> pinctrl_select_state_full(dev, "default")
| | | |-> state = dev_read_stringlist_search(dev,
| | | | "pinctrl-names", "default");
| | | |-> dev_read_prop(dev, propname, &size)
| | | | // snprintf(propname, sizeof(propname),
| | | | // "pinctrl-%d", state)
| | | |
| | | |-> pinctrl_config_one(config)
| | | |-> ops = pinctrl_get_ops(pctldev)
| | | |-> ops->set_state(pctldev, config)
| | |
| | |-> pinctrl_select_state_simple(dev)
| | |-> uclass_get_device_by_seq(UCLASS_PINCTRL, 0, &pctldev)
| | |-> ops=pinctrl_get_ops(pctldev)
| | | // #define pinctrl_get_ops(dev)
| | | // ((struct pinctrl_ops *)(dev)->driver->ops)
| | |
| | |-> ops->set_state_simple(pctldev, dev)
| |
| |-> power_domain_on(&powerdomain)
| |-> uclass_pre_probe_device(dev)
| |-> clk_set_defaults(dev)
| | |-> clk_set_default_parents(dev)
| | |-> clk_set_default_rates(dev)
| |
| |-> drv->probe(dev)
| |-> uclass_post_probe_device(dev)
|
|-> mmc_init
|-> boot_mode=spl_boot_mode(bootdev->boot_device)
|-> mmc_load_image_raw_sector
|-> header=spl_get_load_buffer(-sizeof(*header), bd->blksz)
| // header位于load_addr偏移-head_size处
|
|-> blk_dread(bd, sector, 1, header)
| // 读取一个sector的u-boot image header
|
|-> mmc_load_legacy(spl_image, mmc, sector, header)
| |-> spl_parse_image_header(spl_image, header)
| // 解析u-boot image header信息,得到u-boot的addr和size信息
|
|-> blk_dread(bd, sector, cnt, load_addr)
// 读取完整的u-boot image,包括header,注意load_addr是向前偏移过的地址
refer to
- https://gitee.com/qiming-zhixian/m4-openwrt
- http://doc.panel-tag.cn/m4/op/boot/spl/boot_flow.html
- https://blog.csdn.net/Runner_Linux/article/details/129552707
- http://doc.panel-tag.cn/m4/op/boot/boot_param_1602/index.html