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 传递的参数;
  • 设置异常入口函数、关中断;
.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/902518.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

重塑在线软件开发新纪元:集成高效安全特性,深度解析与评估会员与促销管理系统的系统架构设计

案例 阅读以下关于软件架构设计与评估的叙述&#xff0c;回答问题1和问题2。 【题目】 某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。在项目立项之初&#xff0c;公司领导层一致认为本次升级的主要目标是提升会员管…

简单的udp程序

文章目录 1. 预备知识1.1 源IP地址和目的IP地址1.2 端口号1.3 套接字初识1.4 tcp协议和udp协议简单认识1.5 网络字节序 2. udp程序2.1 创建套接字&#xff08;socket&#xff09;的系统调用2.2 bind()2.2.1 初始化一个sockaddr_in结构体2.2.2 inet_addr函数2.2.3 0.0.0.02.2.4 …

深入解析东芝TB62261FTG,步进电机驱动方案

TB62261FTG是一款由东芝推出的两相双极步进电机驱动器&#xff0c;采用了BiCD工艺&#xff0c;能够提供高效的电机控制。这款芯片具有多种优秀的功能&#xff0c;包括PWM斩波、内置电流调节、低导通电阻的MOSFET以及多种步进操作模式&#xff0c;使其非常适合用于需要精确运动控…

一步一步从微信小程序获取asp.net Core API的数据

前面我们说过&#xff0c;如何使用微信小程序获取asp.net的数据&#xff0c;这里我们继续介绍如何获取asp.net core api的数据。两者之间还是有一些差别的。本篇博文旨在详细介绍如何一步一步从微信小程序获取asp.net Core API的数据。 文章目录 一、建立并了解asp.net core we…

RabbitMQ集群搭建及使用

1. 概述 前提条件&#xff1a;linux服务器下已经安装好了docker服务。 本文档将搭建一个三台RabbitMQ的集群&#xff0c;包括三个RabbitMQ容器安装在同一服务器和三台不同的服务器。 2. 集群搭建 在一台服务器上创建三个RabbitMQ容器。 2.1.1. 创建容器 执行以下命令创建三…

合理利用IPIDEA代理IP,优化数据采集效率!

一、前言 在全球化与信息化交织的当代社会&#xff0c;数据已成为驱动商业智慧与技术革新的核心引擎。网络&#xff0c;作为信息汇聚与交流的枢纽&#xff0c;不仅是人们获取知识的窗口&#xff0c;更是商业活动与技术创新的广阔舞台。在这个信息繁荣的时代&#xff0c;Python…

Docker 实践与应用举例教程:从入门到精通

Docker 实践与应用举例教程&#xff1a;从入门到精通 引言 在现代软件开发中&#xff0c;Docker 已成为一种不可或缺的工具。它通过容器化技术简化了应用的部署、管理和扩展&#xff0c;极大地提高了开发和运维的效率。本文将详细介绍 Docker 的基本概念、安装步骤、常用命令…

开放式耳机哪个品牌音质好?音质最好的开放式耳机推荐!

如今&#xff0c;开放式耳机市场日益繁荣&#xff0c;成为了众多音乐爱好者和追求舒适佩戴体验者的新宠。然而&#xff0c;面对琳琅满目的品牌和产品&#xff0c;消费者往往陷入选择的困境。音质&#xff0c;作为衡量一款耳机优劣的关键因素&#xff0c;更是备受关注。究竟哪个…

反编译华为-研究功耗联网监控日志

摘要 待机功耗中联网目前已知的盲点&#xff1a;App自己都不知道的push类型的被动联网、app下载场景所需时长、组播联网、路由器打醒AP。 竞品 策略 华为 灭屏使用handler定时检测&#xff08;若灭屏30分钟内则周期1分钟&#xff0c;否则为2分钟&#xff09;&#xff0c;检…

【Unity踩坑】UWP应用未通过Windows应用认证:API不支持

在将Unity项目导出为XAML类型的UWP项目后&#xff0c;通过Visual Studio打包成功&#xff0c;但在进行Windows应用认证时结果是Failed。 其中的错误是某些dll里用到了Windows SDK不支持的API。 本次问题中涉及到的具体dll有两个&#xff1a;gilzoide-sqlite-net.dll和D3D12Cor…

【Linux网络】传输层协议UDP与TCP

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; 目录 传输层 再谈端口号 ​编辑 端口号范围划分 认识知名端口号(Well-Know Port Number) netstat pidof UDP协议 UDP协议端格式 UDP的特点 面向数据报 UDP的缓冲区 UDP使用注意事项 基于UDP的应用层协议…

开源实时数仓的构建

设计计思路 基本思路 开源数据平台的设计思路是通过 Flink SQL Batch、StartRocks SQL 、StartRocks物化视图 的能力实现一个离线任务的开发&#xff1b;使用 DolphinScheduler 进行离线工作流编排和调度&#xff1b;通过 Flink CDC 和 Flink SQL 实现流处理能力&#xff0c;进…

Redis+Lua限流的四种算法

1. 固定窗口&#xff08;Fixed Window&#xff09; 原理&#xff1a; 固定窗口算法将时间划分为固定的时间段&#xff08;窗口&#xff09;&#xff0c;比如 1 秒、1 分钟等。在每个时间段内&#xff0c;允许最多一定数量的请求。如果请求超出配额&#xff0c;则拒绝。 优点…

软工毕设开题建议

文章目录 &#x1f6a9; 1 前言1.1 选题注意事项1.1.1 难度怎么把控&#xff1f;1.1.2 题目名称怎么取&#xff1f; 1.2 开题选题推荐1.2.1 起因1.2.2 核心- 如何避坑(重中之重)1.2.3 怎么办呢&#xff1f; &#x1f6a9;2 选题概览&#x1f6a9; 3 项目概览题目1 : 深度学习社…

文档解析与向量化技术加速 RAG 应用落地

在不久前举办的 AICon 全球人工智能开发与应用大会上&#xff0c;合合信息智能创新事业部研发总监&#xff0c;复旦博士常扬从 RAG 应用落地时常见问题与需求&#xff08;文档解析、检索精度&#xff09;出发&#xff0c;分享了针对性的高精度、高泛化性、多版面多元素识别支持…

Linux系统下串口AT指令控制EC20连接华为云物联网平台

一、前言 在当今万物互联的时代背景下&#xff0c;物联网技术的快速发展极大地推动了智能化社会的构建。作为其中的关键一环&#xff0c;设备与云端平台之间的通信变得尤为重要。本文介绍如何在Linux操作系统环境下&#xff0c;利用串口通信来实现EC20模块与华为云物联网平台的…

Word中Normal.dotm样式模板文件

Normal.dotm文档 首先将自己电脑中C:\Users\自己电脑用户名\AppData\Roaming\Microsoft\Templates路径下的Normal.dotm文件做备份&#xff0c;在下载本文中的Normal.dotm文件&#xff0c;进行替换&#xff0c;重新打开word即可使用。 字体样式如下&#xff08;可自行修改&#…

基于opencv答题卡识别判卷

项目源码获取方式见文章末尾&#xff01; 回复暗号&#xff1a;13&#xff0c;免费获取600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 **《------往期经典推荐------》**项目名称 1.【基于DDPG算法的股票量化交易】 2.【卫星图像道路检测DeepLabV3Plus模型】 3…

蓝桥杯 单片机 DS1302和DS18B20

DS1302 时钟 时钟试题 常作为实验室考核内容 控制三个引脚 P17 时钟 P23输入 P13复位 其他已经配置好 寄存器原理 定位地址 0x80地址 固定格式 0x57 5*107*1 57 小时写入格式 不同 首位区分 A上午 P下午 0为24小时制 1为12小时制 写入8小时 0x87 //1000 7 十二小时制 7…

H5的Canvas绘图——使用fabricjs绘制一个可多选的随机9宫格

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;最近一直被测试…