Welcome to my blog|

Satellite98

园龄:3年3个月粉丝:1关注:1

2024-07-18 09:07阅读: 182评论: 0推荐: 0

从头理清uboot(2)-启动流程分析

从头理清uboot(2)-启动流程分析

1.总体流程图

上电
entry: start
设置中断向量表,跳转到reset
设置处理器工作模式
设置CP的值/初始化中断向量表
关闭mmu,初始化堆栈值
初始化gd结构体:完成内存布局初步分配
利用函数指针初始化函数
代码重定位
dyn段的table值重定位+向量表重定位
再次执行函数指针数组
进入main_loop函数

2. 逐步分析

2.1 上电后执行的第一条指令

由于在编译的时候有下面的链接命令:

arm-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic -Ttext 0x87800000 -o u-boot -T u-boot.lds arch/arm/cpu/armv7/start.o --start-group arch/arm/cpu/built-in.o ...... test/dm/built-in.o --end-group arch/arm/lib/eabi_compat.o -L /usr/lib/gcc-cross/arm-linux-gnueabihf/9 -lgcc -Map u-boot.map

所以其实是利用u-boot.lds来链接整个bin 文件的。那么就可以在lds 中看到uboot 的程序入口:

ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
arch/arm/cpu/armv7/start.o (.text*)
*(.text*)
}

arch/arm/lib/vector.s中能够找到_start的具体实现:

_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq

​ 由此我们找到了程序上电执行的第一条指令。

2.2 初始化中断向量表

​ 由上可知,_start的汇编代码的作用便是初始化了中断向量表,并且跳转像了reset函数。至于为什么这里实现了跳转了中断向量表,可先参考这个blog 学习:

https://www.cnblogs.com/liangliangge/p/12549087.html

TODO:补充armv7 中断处理功能

2.3 初始化工作模式 & 初始化中断向量表。

​ 在_start函数中,会跳到reset函数(在arch/arm/cpu/armv7/start.s)中,其函数如下:

.globl reset
.globl save_boot_params_ret
reset:
......//中间代码边分析边给出
bl _main

​ 进入reset 之后,首先执行的就是b save_boot_params但是又会跳回save_boot_params_ret:,在reset 中再次执行,由于定义是week 的,最后实际实现还可以再覆盖。见下方代码:

ENTRY(save_boot_params)
b save_boot_params_ret
ENDPROC(save_boot_params)
.weak save_boot_params

​ 之后便会初始化处理器的工作模式及关闭中断,见下方代码,注释已经写入其中。

https://blog.csdn.net/zhoutaopower/article/details/113746587

mrs r0, cpsr //读cpsr 的值到r0
and r1, r0, #0x1f //取 低五位到r1
teq r1, #0x1a //判断是,即判断是不是HYP模式
bicne r0, r0, #0x1f //不是,就清除低五位
orrne r0, r0, #0x13 //不是,设置为SVC 模式
orr r0, r0, #0xc0 //关闭FIQ 和IRQ
msr cpsr,r0 //把新的r0 值写入CPSR

​ 其次就会设置SCTLR 的值,这里会涉及到armv7 的CP 寄存器,可以见这篇文章:协处理器CP15介绍—MCR/MRC指令(6) - 诺谦 - 博客园 (cnblogs.com),uboot 汇编相关见下方注释:

#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 //把cp15寄存器中c1控制位 读到r0中
bic r0, #CR_V //清除r0 中的 CR_V 控制位,表示向量表地址为0x00000000,且可以重定位向量表。
mcr p15, 0, r0, c1, c0, 0 //把r0写回 cp15-c1
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 //把_start设置为向量表地址,写入 cp15-c12
#endif

2.4 cpu_init_cp15 & cpu_init_crit

​ 在设置好中断向量表之后,在reset 中会做两个CPU 核相关的init动作,之后便会跳转到_main执行,见下方代码:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
bl _main

我们依次分析这两个函数都做了什么事情:

2.4.1 cpu_init_cp15

​ 通过下方代码,可以看出,功能为:关闭指令cache、清空写和预取缓存区,关闭mmu、使能地址跳转预测、获取CPU信息并且根据CPU info 信息的不同来做处理。下方直接将注释写入下方代码段中:

/*************************************************************************
*
* cpu_init_cp15
*
* Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
* CONFIG_SYS_ICACHE_OFF is defined.
*
*************************************************************************/
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ 无效整个数据和指令TLB
mcr p15, 0, r0, c7, c5, 0 @无效整个指令cache
mcr p15, 0, r0, c7, c5, 6 @ 无效整个跳转目标cache
mcr p15, 0, r0, c7, c10, 4 @ 清空写缓存区
mcr p15, 0, r0, c7, c5, 4 @ 清空预取缓存区
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0 @把cp15-c1 的值读到r0
bic r0, r0, #0x00002000 @ 设置低端一场中断向量表,且在可重定位状态
bic r0, r0, #0x00000007 @ 关闭mmu、地址对齐、禁止cache
orr r0, r0, #0x00000002 @ 使能地址对齐检查
orr r0, r0, #0x00000800 @ 使能跳转预测
#ifdef CONFIG_SYS_ICACHE_OFF
bic r0, r0, #0x00001000 @ 关闭 I-cahe
#else
orr r0, r0, #0x00001000 @ 使能 I-cahe
#endif
mcr p15, 0, r0, c1, c0, 0
#ifdef CONFIG_ARM_ERRATA_716044 --使能跳转预测
mrc p15, 0, r0, c1, c0, 0 @ read system control register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c1, c0, 0 @ write system control register
#endif
--C15 寄存器会随着设计的不同而不同。
#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 4 @ set bit #4
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
.
. @中间为相似判断,省略
.
.
.
.
mov r5, lr @ Store my Caller 保存下lr指针,保存函数返回地址
mrc p15, 0, r1, c0, c0, 0 @ 把cp15-c0-主标识符 读到r1
mov r3, r1, lsr #20 @ 将r1 右移20bit ,存储到r3
and r3, r3, #0xf @获取r1[23:20]
and r4, r1, #0xf @ 获取r1[4:0]
mov r2, r3, lsl #4 @ shift variant field for combined value
orr r2, r4, r2 @ r2= r1[23:20] + r1[4:0] = c0[23:20] + c0[4:0] CPU variant + revision
.
. @中间为相似判断,省略
.
.
.
.
#ifdef CONFIG_ARM_ERRATA_621766
cmp r2, #0x21 @ Only on < r2p1
bge skip_errata_621766
mrc p15, 0, r0, c1, c0, 1 @ Read ACR
orr r0, r0, #(0x1 << 5) @ Set L1NEON bit
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through
skip_errata_621766:
#endif
mov pc, r5 @ 退出函数执行,跳回。
ENDPROC(cpu_init_cp15)

2.4.1 cpu_init_crit

​ 跳入 lowlevel_init函数,在arch/arm/cpu/armv7/lowlevel_init.s中有定义,可见初步作用是初始化栈,调用s_init函数,定义在arch/arm/cpu/armv7/mx6/soc.c,但是对于imax6ull而言就是个空函数,故整体而言,这里只是做了一个获取CONFIG_SYS_INIT_SP_ADDR的值,做了下sp 的初始化,给GD_SIZE留出空间。

ENTRY(lowlevel_init)
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /*8byte对齐*/
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE @减去 GD_SIZE 的值。
bic sp, sp, #7 /*8byte对齐*/
mov r9, sp
#endif
#endif
push {ip, lr} @将IP 和 lr 都压入栈中
bl s_init
pop {ip, pc} @将IP 和 lr 都出栈
ENDPROC(lowlevel_init)

​ 对于CONFIG_SYS_INIT_SP_ADDR和``在include/configs/mx6sllevk.h中有定义:

#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
#define CONFIG_SYS_INIT_SP_OFFSET \
(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \
(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

​ 其中IRAM_BASE_ADDRIRAM_SIZE在``arch/arm/include/asm/arch-mx6/imx-regs.h`中有定义,可以看出,这应该是CPU内部的ram

#define IRAM_BASE_ADDR 0x00900000
#if !(defined(CONFIG_MX6SX) || defined(CONFIG_MX6UL) || \
defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
#define IRAM_SIZE 0x00040000
#else
#define IRAM_SIZE 0x00020000
#endif

​ 我们还需要知道GENERATED_GBL_DATA_SIZEGD_SIZE的值,都定义在include/generated/generic-asm-offsets.h中,有以下定义:

#define GENERATED_GBL_DATA_SIZE 256 /* (sizeof(struct global_data) + 15) & ~15 @ */
#define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */
#define GD_SIZE 248 /* sizeof(struct global_data) @ */
#define GD_BD 0 /* offsetof(struct global_data, bd) @ */
#define GD_MALLOC_BASE 188 /* offsetof(struct global_data, malloc_base) @ */
#define GD_RELOCADDR 44 /* offsetof(struct global_data, relocaddr) @ */
#define GD_RELOC_OFF 64 /* offsetof(struct global_data, reloc_off) @ */
#define GD_START_ADDR_SP 60 /* offsetof(struct global_data, start_addr_sp) @ */

​ 由此我们可以得到当前的栈指针的值:

* sp = CONFIG_SYS_INIT_SP_ADDR = (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE) = 0x00900000 + 0x00020000 -256 = 0x0091FF00
* sp = sp - GD_SIZE = 0x0091FF00 - 248 = 0x0091FE08
* bic sp, sp, #7 --> sp = 0x0091FE00

2.5 进入_main 函数

补充重点:建议看crt0.S 中关于_main 函数的作用,里面解释了为什么会有这些流程

​ 在上面步骤,应该是初步完成了CPU的一些配置,解下来会进入_main函数,进一步完成系统级的初始化工作。 __main函数定义在 arch/arm/lib/crt0.S中。【crt0.S 作用是 准备计入C语言需要的环境。】

​ 首先判断是否定义了SPL build 相关,初始化栈sp 指针。然后会分别调用board_init_f_alloc_reserveboard_init_f_init_reserveboard_init_f(r0 ==0),可见下方代码:

ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve
mov r0, #0
bl board_init_f
#if ! defined(CONFIG_SPL_BUILD)
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
.......

2.5.1 board_init_f_alloc_reserve(sp)

​ 其中调用的board_init_f_alloc_reserve(sp)common/init/board_init.c中,这个函数有一个传参,实际为sp 的值,可见这个函数的作用为在栈中留出CONFIG_SYS_MALLOC_F_LEN = 0x400大小和global_data结构体的大小(GD_SIZE)空间区域后,进行一个16byte 的对齐操作。

ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
}

​ 所以当前的sp 为:0x0091FF00 - 0x400 - 248 = 0x0091Fa00 。出栈后会把算好的r0 在赋值给sp,同时赋值g给r9。

2.5.2 board_init_f_init_reserve(sp)

​ 之后会调用board_init_f_init_reserve,对于imax6ull 条件编译之后见下方,可见主要作用为:

​ 将当前sp 设置为global_data的基地址、global_data区域清0、然后把gd->malloc_base设置为base 加上struct global_data并且16字节取整,之后base 加上CONFIG_SYS_MALLOC_F_LEN长度。那么这个函数就是把global_data初始化了,把malloc地址指向了 0x0091FF00 - 0x400,然后把base设置为了0x0091FF00 再赋值给r0了。

​ 作用:这里留出一块内存用于保存global_data和 执行后面的函数的内存空间

void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;
/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/
gd_ptr = (struct global_data *)base;
/* zero the area */
#ifdef _USE_MEMCPY
memset(gd_ptr, '\0', sizeof(*gd));
/* next alloc will be higher by one GD plus 16-byte alignment */
base += roundup(sizeof(struct global_data), 16);
/*
* record early malloc arena start.
* Use gd as it is now properly set for all architectures.
*/
#if defined(CONFIG_SYS_MALLOC_F)
/* go down one 'early malloc arena' */
gd->malloc_base = base;
/* next alloc will be higher by one 'early malloc arena' size */
base += CONFIG_SYS_MALLOC_F_LEN;
#endif
}

2.5.3 board_init_f(0)

board_init_f函数定义在 common/board_f.c中,经过条件编译之后的结果为下图所示:

void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
/* Light up LED1 */
imx6_light_up_led1();
}

gd->have_console = 0;表明现在还没有窗口。

​ 其中boot_flags == 0作为输入传入,后续主要是执行initcall_run_list(init_sequence_f)这个函数,我们需要先看函数输入init_sequence_f的定义(已经经过条件编译,见下方)。

​ 其中init_fnc_t的定义为typedef int (*init_fnc_t)(void);,由此这是一个函数指针结构体,而在board_init_f中调用的initcall_run_list函数就是会遍历执行下方的init_sequence_f函数结构体,来执行板级的初始化。对于被遍历的函数,大致功能见注释。

static init_fnc_t init_sequence_f[] = {
setup_mon_len, //gd->mon_len = (ulong)&__bss_end - (ulong)_start; 设置mon_len 为代码长度
initf_malloc, //gd->malloc_limit = 0x40; 当前内存池大小。
initf_console_record, //return 0;
arch_cpu_init, /* basic arch cpu dependent setup */
initf_dm, //初始化驱动模型相关
arch_cpu_init_dm,
mark_bootstage, /* need timer, go after init dm */
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f, //,I.MX6ULL 用来初始化串口的 IO 配置
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \
defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \
defined(CONFIG_SPARC)
timer_init, //初始化内部定时器
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
board_postclk_init, //设置 VDDSOC 电压
#endif
#if defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
get_clocks, //可以配置获取多种时钟值,I.MX6ULL 获取的是 sdhc_clk 时钟
#endif
env_init, //gd->env_addr = (ulong)&default_environment[0]; 设置gd ENV 地址
init_baud_rate, /* 根据 “baudrate” 环境变量配置波特率 */
serial_init, /* 串口初始化 会调用函数指针 */
console_init_f, /* gd->have_console = 1; 配置输出窗口*/
display_options, /* 在窗口上显示输出 */
display_text_info, /* 在窗口上打印文本输出*/
print_cpuinfo, /* 打印CPU 信息 */
#if defined(CONFIG_DISPLAY_BOARDINFO)
show_board_info, /* 打印板子 信息 */
#endif
INIT_FUNC_WATCHDOG_INIT /*初始化看门狗, I.MX6ULL 来说是空函数 */
INIT_FUNC_WATCHDOG_RESET /*喂狗, I.MX6ULL 来说是空函数 */
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
init_func_i2c, /* 初始化IIC */
#endif
announce_dram_init,
/* TODO: unify all these dram functions? */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)
dram_init, /*gd->ram_size = imx_ddr_size();把 外部能用的DDR大小付给gd->ram_size */
#endif
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
setup_dest_addr, /* 这个函数下方详细介绍 */
reserve_round_4k, /* gd->relocaddr &= ~(4096 - 1);重定位地址4k 对齐 */
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
defined(CONFIG_ARM)
reserve_mmu, /* 这个函数下方详细介绍 */
#endif
reserve_trace, /*留出debug 空间,I.MX6ULL 来说是空函数*/
#if !defined(CONFIG_BLACKFIN)
reserve_uboot, /*留出 uboot code空间 */
#endif
#ifndef CONFIG_SPL_BUILD
reserve_malloc, /*留出malloc 空间 TOTAL_MALLOC_LEN*/
reserve_board, /* 留出 sizeof(bd_t) 的空间*/
#endif
setup_machine, /* 设置机器id 等设置,I.MX6ULL 来说是空函数 */
reserve_global_data, /*留出sizeof(gd_t)的空间 */
reserve_fdt, /* 留出 fdt_size 的空间,I.MX6ULL 无效 */
reserve_arch, /* I.MX6ULL 来说是空函数 */
reserve_stacks, /*gd->start_addr_sp 栈顶做16字节对齐 */
setup_dram_config, /* 设置Dram的地址和大小,后续告诉linux 内核 */
show_dram_config, /* 打印上述信息 */
display_new_sp, /* 打印sp-栈顶 */
INIT_FUNC_WATCHDOG_RESET
reloc_fdt, /* I.MX6ULL 没有用到 */
重要:setup_reloc, /*设置一些重定位要用的参数,并且将gd 从ram中拷贝到DDR中* /
NULL,
};

可以看出,board_init_f(0)总体上是围绕global_data展开的,关于global_data可以参考这篇文档:https://blog.csdn.net/wenjin359/article/details/82849492,

  • setup_dest_addr 函数

    • 作用:初始化gd->ram_sizgd->ram_topgd->relocaddr
    static int setup_dest_addr(void)
    {
    gd->ram_size = board_reserve_ram_top(gd->ram_size);// ram_szie 前面已经init过了,是imx_ddr_size()
    #ifdef CONFIG_SYS_SDRAM_BASE
    gd->ram_top = CONFIG_SYS_SDRAM_BASE; //设置为DDR base 地址CONFIG_SYS_SDRAM_BASE
    #endif
    gd->ram_top += get_effective_memsize();// base + gd->ram_size
    gd->ram_top = board_get_usable_ram_top(gd->mon_len);
    gd->relocaddr = gd->ram_top; //重定位地址 = gd->ram_top
    debug("Ram top: %08lX\n", (ulong)gd->ram_top);
    return 0;
    }
  • reserve_round_4k 函数,把刚刚初始化的gd->relocaddr地址4K对齐。gd->relocaddr &= ~(4096 - 1);

  • reserve_mmu:

    • 作用:在DDR中留出PGTABLE_SIZE大小的空间用于做tlb。
    static int reserve_mmu(void)
    {
    /* reserve TLB table */
    gd->arch.tlb_size = PGTABLE_SIZE;
    gd->relocaddr -= gd->arch.tlb_size;
    /* round down to next 64 kB limit */
    gd->relocaddr &= ~(0x10000 - 1);
    gd->arch.tlb_addr = gd->relocaddr;
    debug("TLB table from %08lx to %08lx\n", gd->arch.tlb_addr,
    gd->arch.tlb_addr + gd->arch.tlb_size);
    return 0;
    }
  • reserve_uboot:

    • 作用:留出code_len 的空间,并且把此时relocaddr地址付给start_addr_sp注意,这里开始gd->relocaddr不再继续减小了,因为已经到了代码段都拷完的地方了,重定位的地址已经确定好了!!!

      static int reserve_uboot(void)
      {
      gd->relocaddr -= gd->mon_len;
      gd->relocaddr &= ~(4096 - 1);
      debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10,
      gd->relocaddr);
      gd->start_addr_sp = gd->relocaddr;
      return 0;
      }
  • 其余剩余的reserve_xxx 函数都是在DDR 内存中,划分出对应区域给对应功能实用。有以下几个参数需要注意一下

    • 重定位offset 偏移:gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;

    • 会有一次gd 的搬运:,其中gd 的宏定义展开见下方,由于之前将将cpu ram 的gd 指针存储在R9中,所以芯片内部的ram 中有一份global_data结构体。新的gd 会放在全局变量后面:参考reserve_global_data函数

      //搬运:
      `memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));`
      //获取老gd 地址:
      __asm__ volatile("mov %0, r9\n" : "=r" (gd_ptr));
      //新GD 地址
      gd->new_gd = (gd_t *)map_sysmem(gd->start_addr_sp, sizeof(gd_t));
  • 总结:由此 board_info 就执行完了,主要是初始化了DDR的内存布局,并且对golobal data 进行初始化,同时拷贝进了DDR中。

2.5.4 再次回到 _main 函数

执行完DDR区域分配及配置好gd 之后,会执行下面的操作:

  • 把 sp设置到DDR上:ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */

  • 初始化栈,之后会执行代码段的重定位,需要注意lr的值,再次返回时就已经到DDR中的重定位地址了。

    • 问题:当前代码在什么地址?R:根据obj dump 来看,在87802800 <_main>:DDR 中。
    • 追问:那为什么需要再搬运一次地址?新搬运的地址在哪里?R:搬运的地址是board_init_f()中设置的地址,提高了灵活性?
    #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
    mov r3, sp
    bic r3, r3, #7
    mov sp, r3
    #else
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
    #endif
    ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
    sub r9, r9, #GD_SIZE /* new GD is below bd */ 获取新gd的位置?
    adr lr, here
    ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
    add lr, lr, r0 lr = here 地址 + gd->reloc_off
    #if defined(CONFIG_CPU_V7M)
    orr lr, #1 /* As required by Thumb-only */
    #endif
    ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
    b relocate_code /*代码从定位*/ 所以执行完这个,跳回的时候就已经在DDR中了?
    here:
    /*
    * now relocate vectors
    */
    bl relocate_vectors
    /* Set up final (full) environment */
    bl c_runtime_cpu_setup /* we still call old routine here */
    #endif
  • 执行relocate_code函数

    ​ 见下方代码,r1是代码段起始地址,r0是传入的gd->relocaddr重定位地址,整体作用是判断起始地址和relocaddr是否相等,不相等的话就会进行代码段的搬运(__image_copy_start ~ image_copy_end)。然后进行dyn段,即动态代码段的符号表地址的更改。【此处涉及到elf文件的表示,有兴趣的可以查阅elf 文件格式,了解动态代码是如何执行的。】

    注意:这里跳转使用的是b而不是bl就是不需要更改LR的值,因为LR 已经被重定位了。

    ENTRY(relocate_code)
    ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
    subs r4, r0, r1 /* r4 <- relocation offset */
    beq relocate_done /* skip relocation */
    ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
    copy_loop:
    ldmia r1!, {r10-r11} /* copy from source address [r1] */
    stmia r0!, {r10-r11} /* copy to target address [r0] */
    cmp r1, r2 /* until source end address [r2] */
    blo copy_loop
    /*
    * fix .rel.dyn relocations
    */
    ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
    ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
    fixloop:
    ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
    and r1, r1, #0xff
    cmp r1, #23 /* relative fixup? */
    bne fixnext
    /* relative fix: increase location by offset */
    add r0, r0, r4
    ldr r1, [r0]
    add r1, r1, r4
    str r1, [r0]
    fixnext:
    cmp r2, r3
    blo fixloop
    relocate_done:
    bx lr
    ENDPROC(relocate_code)
  • 向量表的重定位:

    由于arm处理器将向量表放在代码头部,所以这里中断向量表的重定位也是设置 cp15-c12 的值,将其设置为gd->relocaddr,即重定位代码段的首地址。编译后的程序如下所示:

    ENTRY(relocate_vectors)
    ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
    mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
    bx lr
    ENDPROC(relocate_vectors)
  • 执行c_runtime_cpu_setup 函数,此代码在arch/arm/cpu/armv7/start.S 中,作用是关闭指令cache

  • 执行清除BSS段的汇编代码

  • 跳转执行到board_init_r(),至此—main函数运行结束。

2.6 执行board_init_r函数,开始最后的初始化

board_init_r函数如下,如同board_init_f 函数,主要是遍历了init_sequence_r这样一个函数指针结构提。

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
if (initcall_run_list(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}

同样,对于init_sequence_r这样一个函数指针数据,我们分析其条件条件编译之后的参数项

init_fnc_t init_sequence_r[] = {
initr_trace, /* 初始化追踪调试内容 */
initr_reloc, /* gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT; 表示重定位完成 */
/* TODO: could x86/PPC have this also perhaps? */
#ifdef CONFIG_ARM
initr_caches,/* 使能cache */
#endif
initr_reloc_global_data, /* 修改重定位之后的 gd 参数 */
initr_barrier, /* I.MX6ULL 来说是空函数 */
initr_malloc, /* 初始化 malloc 的内存空间,一些全局变量参数 */
initr_console_record, /* I.MX6ULL 来说是空函数 */
bootstage_relocate, /* bootstage重定位-但实际未看到*/
initr_bootstage, /* 初始化 bootstage*/
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
board_init, /* Setup chipselects - 片上外设初始化在这里 USB IIC FEC QSPI 等等*/
#endif
stdio_init_tables, /* 初始化 STDIO */
initr_serial, /* 串行设备初始化 */
initr_announce,
INIT_FUNC_WATCHDOG_RESET
power_init_board, /* 电源初始化- imx6ull 来说是空函数*/
#ifdef CONFIG_GENERIC_MMC
initr_mmc, /* 初始化mmc */
#endif
initr_env, /* 环境变量初始化 */
INIT_FUNC_WATCHDOG_RESET
initr_secondary_cpu, /* 多核初始化,I.MX6ULL 来说是空函数 */
stdio_add_devices, /* 各种输入输出外设初始化 */
initr_jumptable, /* 初始化跳转表 */
console_init_r, /* 人机交互控制台初始化 */
interrupt_init, /* 中断堆栈初始化 */
#if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
initr_enable_interrupts, /* 中断使能 */
#endif
#ifdef CONFIG_CMD_NET
initr_ethaddr, /* 获取环境变量“ethaddr” 来初始化MAC的值 */
#endif
#ifdef CONFIG_BOARD_LATE_INIT
board_late_init, /* 后续初始化,如果环境变量需要存储在EMMC/SD 中的话, 会初始化EMMC,并且调用了 cmd 接口*/
#endif
#ifdef CONFIG_CMD_NET
INIT_FUNC_WATCHDOG_RESET
initr_net, /* 网口初始化 */
#endif
imx6_light_up_led2,
run_main_loop, /* uboot 的主命令循环 */
};

由上注释可见,调用board_init_r函数更多的是板级初始化的设置,让整个SOC板子和外设能够正常的运行起来,进入到main_loop 的函数。

3. 整体函数调用框架

set scv mode
bss-init
reset
cpu_init_cp15
cpu_init_crit
lowlevel_init
s_init
_main
board_init_f_alloc_reserve
board_init_f_init_reserve
board_init_f
initcall_run_list-init_sequence_f
relocate_code
relocate_vectors
c_runtime_cpu_setup
board_init_r

有了整体框架之后我们依次简短的介绍下各个函数的作用

  • cpu_init_cp15:在进入SVC 模式之后,关闭各种Cache、MMU,读取CPU variant + revisionCPU 信息。
  • cpu_init_crit:第一次初始化 SP 在CPU内部的SRAM中。并且给结构体数组gd_date留出空间,把gd_data的指针赋值给r9,后面可以直接通过r9寄存器调用gd。(sram内部)然后调用s_init了。
  • board_init_f_alloc_reserve:在_main 函数中会重新设定sp的值为CONFIG_SYS_INIT_SP_ADDR,在此函数中会在sram中留出malloc 和 global_data的空间
  • board_init_f_init_reserve:把global_data 的空间清0,并且设定malloc 的空间起始地址。为执行C语言的环境做准备。
  • board_init_f:调用initcall_run_list(init_sequence_f)函数来初步板级初始化(CPU特性、时钟、环境变量、串行显示等)和对内存的划分(boot 使用的内存空间、global_date、relocate、mmu_TLB、ENV)信息的存储。
  • relocate_code & relocate_vectors:代码和中断向量的重定位
  • board_init_r:调用initcall_run_list(init_sequence_r)函数来进一步初始化板级外设、设置和开启中断,最后进入run_main_loop函数等待执行命令。

本文作者:Satellite98

本文链接:https://www.cnblogs.com/satellite98/p/18308705

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Satellite98  阅读(182)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起