riscv uboot 启动流程分析 - SPL启动流程

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 已经对系统进行了基本的初始化。

  下载代码:

c 复制代码
# 下载主工程代码
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 时传递的参数:

传递参数结构体定义如下,具体实现可参考:

c 复制代码
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

c 复制代码
 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

c 复制代码
 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 寄存器保存起来,还将其他重要寄存器一起保存。

c 复制代码
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 的启动整体流程

c 复制代码
_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的入口是由链接脚本决定:

c 复制代码
arch/riscv/Kconfig:
251 config SPL_LDSCRIPT
252   default "arch/riscv/cpu/u-boot-spl.lds"

  arch/riscv/cpu/u-boot-spl.lds:

c 复制代码
  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 传递的参数;
  • 设置异常入口函数、关中断;
c 复制代码
.section .text
.globl _start
_start:     /* 链接脚本里面指定了这里是uboot的入口 */
#if CONFIG_IS_ENABLED(RISCV_MMODE)
	csrr	a0, CSR_MHARTID
#endif
  • RISCV_MMODE 就是 machine mode
c 复制代码
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 传递的参数:

c 复制代码
 64   /* save hart id and dtb pointer */
 65   mv  tp, a0
 66   mv  s1, a1

设置异常入口函数、关中断:

c 复制代码
 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.

c 复制代码
 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:

c 复制代码
186   /* Enable cache */
187   jal icache_enable
188   jal dcache_enable

设置参数调用board_init_f 进行环境的初始化,后面会详细讲board_init_f

c 复制代码
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 :

c 复制代码
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.

c 复制代码
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:

c 复制代码
|-> 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 读取的存储介质类型,然后依次检查是否存在对应的程序加载器。

c 复制代码
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
c 复制代码
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

相关推荐
u0103055276 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u0103055276 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝8 天前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u0103055278 天前
Java图像处理实战指南
人工智能·1024程序员节
u0103055278 天前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u01030552711 天前
Java实用类应用精讲
人工智能·1024程序员节
u01030552711 天前
Java I/O核心操作实战
人工智能·1024程序员节
u01030552712 天前
ArrayList操作详解与实战应用
人工智能·1024程序员节
u01030552713 天前
Java模块化编程实战指南
人工智能·笔记·1024程序员节
u01030552714 天前
使用BufferedReader读取控制台输入
人工智能·1024程序员节