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
|           |-> 
<think>好的,我现在需要回答用户关于U-Boot中timer_init初始化位置的问题。首先,我需要回忆一下U-Boot的启动流程和相关代码结构。 U-Boot的初始化分为几个阶段,首先是arch级的初始化,然后是板级初始化。timer_init通常属于板级或CPU相关的初始化步骤,可能是在lowlevel_init或者更早的阶段被调用。不过具体位置可能因架构不同而有所变化。 接下来,我应该考虑不同架构的情况,比如ARM、PowerPC、RISC-V等。例如,在ARM架构中,U-Boot的启动流程会执行一系列的init_sequence_f数组中的函数,包括timer_init。但这里可能是指特定板级的timer_init,比如在board_init_f阶段调用的。 另外,我需要确认是否timer_init是在启动阶段的早期,比如在relocate_code之前还是之后。通常,基础外设的初始化会在relocation之前完成,但timer可能因为需要用于延时或看门狗而在较早阶段初始化。 可能还需要查看具体的板级支持包(BSP)代码,比如在arch/arm目录下的相关处理器代码,或者board/目录下的具体板级代码。例如,对于某些SoC,timer的初始化可能在lowlevel_init.S中,或者在spl阶段就已经完成。 另外,用户可能想知道如何找到timer_init的调用位置。可以建议通过代码搜索,在U-Boot源码中查找timer_init函数的调用链。例如,在common/board_f.c或common/board_r.c中的init_sequence数组里可能有相关条目。 还需要注意,某些平台可能将timer_init作为weak函数,允许板级代码覆盖它。因此,具体的实现可能位于arch下的某个目录,或者板级目录下的某个文件中。 总结一下,timer_init的位置取决于具体的架构和板级实现,但通常在板级初始化阶段,可能在init_sequence_f中的某个步骤被调用,或者在lowlevel_init中直接初始化。需要具体分析目标平台的代码结构。</think>在U-Boot中,`timer_init`函数的初始化位置与具体硬件平台和代码实现相关。以下是关键步骤解析: --- ### **1. 初始化流程定位** U-Boot的初始化分为两个主要阶段: - **板级初始化前期** (`board_init_f`) - **板级初始化后期** (`board_init_r`) `timer_init`通常属于**前期初始化**,用于为后续操作(如延时、超时控制)提供时钟基础。 --- ### **2. 代码位置分析** #### **(1) ARM架构常见路径** 对于ARM平台,`timer_init`通常出现在以下位置: - **文件**: `arch/arm/cpu/armv7/start.S` 或板级代码(如`board/厂商名/板名/板名.c`) - **调用链**: ```c start.S → _main → board_init_f → init_sequence_f[] → timer_init() ``` 其中`init_sequence_f`是初始化函数数组,定义在`common/board_f.c`中。 #### **(2) 其他架构** - **PowerPC**: 通常在`cpu/ppc4xx/start.S`或板级代码中调用。 - **RISC-V**: 可能在`arch/riscv/cpu/start.S`或SoC特定目录下。 --- ### **3. 代码示例** 在`common/board_f.c`的初始化序列中,可能包含如下定义: ```c static init_fnc_t init_sequence_f[] = { ... timer_init, /* 初始化定时器 */ ... }; ``` --- ### **4. 如何手动验证** - **步骤1**: 在U-Boot源码中搜索`timer_init`函数 ```bash grep -rnw 'timer_init' arch/ board/ drivers/ ``` - **步骤2**: 检查`init_sequence_f`或`init_sequence_r`数组,确认是否包含`timer_init`。 --- ### **5. 典型场景** - **场景1**: 使用DM(Driver Model)的平台,定时器驱动可能通过`driver/clk`或`driver/timer`实现。 - **场景2**: 旧版代码可能直接在板级文件的`board_init()`中调用`timer_init()`。 --- ### **总结** - **核心位置**: `timer_init`通常位于`board_init_f`阶段的初始化序列中。 - **依赖关系**: 具体实现因芯片架构(ARM/PowerPC/RISC-V)和板级设计而异。 - **调试建议**: 通过代码搜索或跟踪启动日志(`CONFIG_LOG`启用)确认调用路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值