下面根据具体的代码分析start.S
start.S位于arch/riscv/cpu下面,每一种框架都有自己的目录,本文用risc-v作为参考。
根据链接脚本发现start.S里面的_start将是uboot的执行入口,我们从这里开始分析。
1. 在代码的最开始,根据cpu的bit定义了一套后续使用的宏定义。
#ifdef CONFIG_32BIT
#define LREG lw
#define SREG sw
#define REGBYTES 4
#define RELOC_TYPE R_RISCV_32
#define SYM_INDEX 0x8
#define SYM_SIZE 0x10
#else
#define LREG ld
#define SREG sd
#define REGBYTES 8
#define RELOC_TYPE R_RISCV_64
#define SYM_INDEX 0x20
#define SYM_SIZE 0x18
#endif
2. 对_start进行修饰
.section .text
.globl _start
_start: /* 链接脚本里面指定了这里是uboot的入口 */
#if CONFIG_IS_ENABLED(RISCV_MMODE)
csrr a0, CSR_MHARTID
#endif
保存上一级boot传下来的参数
/*
* Save hart id and dtb pointer. The thread pointer register is not
* modified by C code. It is used by secondary_hart_loop.
*/
mv tp, a0 /* 上一级boot传下来的 hart id */
mv s1, a1 /* 上一级boot传下来的 uboot的fdt地址,如果有,没有就是0 */
/*
* Set the global data pointer to a known value in case we get a very
* early trap. The global data pointer will be set its actual value only
* after it has been initialized.
*/
mv gp, zero
设置异常入口函数
/*
* Set the trap handler. This must happen after initializing gp because
* the handler may use it.
*/
la t0, trap_entry
csrw MODE_PREFIX(tvec), t0
##### 关中断
/*
* Mask all interrupts. Interrupts are disabled globally (in m/sstatus)
* for U-Boot, but we will need to read m/sip to determine if we get an
* IPI
*/
csrw MODE_PREFIX(ie), zero /* sie寄存器清零,关中断 */
#if CONFIG_IS_ENABLED(SMP)
/* check if hart is within range */
/* tp: hart id */
li t0, CONFIG_NR_CPUS
bge tp, t0, hart_out_of_bounds_loop
/* set xSIE bit to receive IPIs */
#if CONFIG_IS_ENABLED(RISCV_MMODE)
li t0, MIE_MSIE
#else
li t0, SIE_SSIE
#endif
/* sie寄存器中,SSIE bit置为1 ,SSIE:interrupt-enable bits for supervisor level software interrupts.*/
csrs MODE_PREFIX(ie), t0
#endif
设置堆栈,地址对齐,每个core都会分配自己的对站地址,挑选一个主core进行初始化,其他的core wait在wait_for_gd_init
/*
* Set stackpointer in internal/ex RAM to call board_init_f
*/
call_board_init_f:
li t0, -16 /* -16 的16进制就是 0xffff fff0 ,用来对齐使用的 */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) /* 这里是给SPL使用的 */
li t1, CONFIG_SPL_STACK
#else
li t1, CONFIG_SYS_INIT_SP_ADDR
#endif
and sp, t1, t0 /* 设置堆栈指针,并16byte对齐,force 16 byte alignment */
call_board_init_f_0:
mv a0, sp
jal board_init_f_alloc_reserve /* 从sp高地址开始预留一段内存给global_data使用,返回的是减去预留后的地址,也就是gd的首地址*/
/*
* Save global data pointer for later. We don't set it here because it
* is not initialized yet.
*/
mv s0, a0
/* setup stack ,设置新的堆栈,根据core的数量进行划分 */
#if CONFIG_IS_ENABLED(SMP)
/* tp: hart id */
slli t0, tp, CONFIG_STACK_SIZE_SHIFT /* tp保存的是当前core id,根据core id 进行sp的划分,每一个core分一块内存用作sp */
sub sp, a0, t0
#else
mv sp, a0
#endif
#ifndef CONFIG_XIP
/*
* Pick hart to initialize global data and run U-Boot. The other harts
* wait for initialization to complete.
* 挑选一个core用来初始化uboot,其他的core等待gd初始化完成,opensbi/kernel都有类似的动作
*/
la t0, hart_lottery
li t1, 1
amoswap.w s2, t1, 0(t0)
bnez s2, wait_for_gd_init
#else
/*
* FIXME: gp is set before it is initialized. If an XIP U-Boot ever
* encounters a pending IPI on boot it is liable to jump to whatever
* memory happens to be in ipi_data.addr on boot. It may also run into
* problems if it encounters an exception too early (because printf/puts
* accesses gd).
*/
mv gp, s0
bnez tp, secondary_hart_loop
#endif
#ifdef CONFIG_OF_PRIOR_STAGE
la t0, prior_stage_fdt_address
SREG s1, 0(t0) /* 把prior_stage_fdt_address 地址放在s1里面,如果不走这里,s1保存的是上级boot传下来的fdt地址,在下面会保存在gp里面*/
#endif
jal board_init_f_init_reserve /* 参数a0 还是前面的gd的首地址,初始化global_data指针 */
SREG s1, GD_FIRMWARE_FDT_ADDR(gp) /* 保存上面的prior_stage_fdt_address或者fdt的地址 -------www */
/* save the boot hart id to global_data */
SREG tp, GD_BOOT_HART(gp) /* 保存core id */
#ifndef CONFIG_XIP
la t0, available_harts_lock
/* t0地址里面的值1和数值0进行交换,原子操作,此时available_harts_lock就等于0了, rl: 被置位的原子指令保证其它线程在此之前看到顺序的原子操作 */
amoswap.w.rl zero, zero, 0(t0)
wait_for_gd_init:
la t0, available_harts_lock /* 上面设置完后,available_harts_lock就是0了,available_harts_lock是一个全局的变量 */
li t1, 1
1: amoswap.w.aq t1, t1, 0(t0) /* t0的0赋值为t1, t1的1赋值为t0 */
bnez t1, 1b /* 如果 t1 != 0, 循环,从这几行代码看,一次只有一个core能接着往下走,其他的core只能在这里循环???? */
/*
* Set the global data pointer only when gd_t has been initialized.
* This was already set by arch_setup_gd on the boot hart, but all other
* harts' global data pointers gets set here.
*/
mv gp, s0 /* s0里面就是global_data地址 */
/* register available harts in the available_harts mask */
li t1, 1
sll t1, t1, tp
LREG t2, GD_AVAILABLE_HARTS(gp) /* 先读取保存的 hart id */
or t2, t2, t1 /* 或上当前的hart id */
SREG t2, GD_AVAILABLE_HARTS(gp) /* 新的值回写到gp里面 */
amoswap.w.rl zero, zero, 0(t0) /* available_harts_lock设置为0,上面死循环的core可以接着往下走了,释放其他的core吗? */
/*
* Continue on hart lottery winner, others branch to
* secondary_hart_loop.
*/
/*
* s2里面存的是hart_lottery的值,第一个core获取到的hart_lottery是0,其他的core获取都是1
* 其他的core进入secondary_hart_loop->wfi,第一个进来的core继续初始化。
*/
bnez s2, secondary_hart_loop
#endif
设置icache和dcache
/* Enable cache, 打开icache和dcache */
jal icache_enable
jal dcache_enable
#ifdef CONFIG_DEBUG_UART
jal debug_uart_init
#endif
设置参数调用board_init_f 进行环境的初始化,后面会详细讲board_init_f
mv a0, zero /* a0 <-- boot_flags = 0 */
/*
* 如果是uboot,那么会调用common下面的board_f.c里面的函数,spl可能不是这个,要看编译配置
* 这个函数会顺序执行init_sequence_f数组里面的函数
*/
la t5, board_init_f
jalr t5 /* jump to board_init_f(),执行完后,跳到本文件里面的relocate_code继续往下走 */
下面是SPL专用的代码,主要用于清bss,设置堆栈,最后跳到board_init_r
#ifdef CONFIG_SPL_BUILD
spl_clear_bss:
la t0, __bss_start
la t1, __bss_end
beq t0, t1, spl_stack_gd_setup
spl_clear_bss_loop:
SREG zero, 0(t0)
addi t0, t0, REGBYTES
blt t0, t1, spl_clear_bss_loop
spl_stack_gd_setup:
jal spl_relocate_stack_gd
/* skip setup if we did not relocate */
beqz a0, spl_call_board_init_r
mv s0, a0
/* setup stack on main hart */
#if CONFIG_IS_ENABLED(SMP)
/* tp: hart id */
slli t0, tp, CONFIG_STACK_SIZE_SHIFT
sub sp, s0, t0
#else
mv sp, s0
#endif
#if CONFIG_IS_ENABLED(SMP)
/* set new stack and global data pointer on secondary harts */
spl_secondary_hart_stack_gd_setup:
la a0, secondary_hart_relocate
mv a1, s0
mv a2, s0
mv a3, zero
jal smp_call_function
/* hang if relocation of secondary harts has failed */
beqz a0, 1f
mv a1, a0
la a0, secondary_harts_relocation_error
jal printf
jal hang
#endif
/* set new global data pointer on main hart */
1: mv gp, s0
spl_call_board_init_r:
mv a0, zero
mv a1, zero
jal board_init_r
#endif /* end --- CONFIG_SPL_BUILD */
下面是代码的重定位汇编程序,uboot会在board_init_f 的最后跳到这里执行,不再返回
/*
* void relocate_code(addr_sp, gd, addr_moni)
*
* This "function" does not return, instead it continues in RAM
* after relocating the monitor code.
*
*/
.globl relocate_code
relocate_code: /* 注意函数参数:relocate_code(gd->start_addr_sp, gd->new_gd, gd->relocaddr); */
mv s2, a0 /* save addr_sp */
mv s3, a1 /* save addr of gd */
mv s4, a2 /* save addr of destination */
/*
*Set up the stack
*/
stack_setup:
#if CONFIG_IS_ENABLED(SMP)
/* tp: hart id,这里使用的应该是重定向后的sp地址 */
slli t0, tp, CONFIG_STACK_SIZE_SHIFT
sub sp, s2, t0
#else
mv sp, s2
#endif
la t0, _start
sub t6, s4, t0 /* t6 <- relocation offset,t6里面存的是现在的地址和新地址之间的offset,s4里面存放的是重定向的目的地址 */
beq t0, s4, clear_bss /* skip relocation,如果现在的start地址和新的目的地址一样,就不用重定位了 */
mv t1, s4 /* t1 <- scratch for copy_loop */
la t3, __bss_start
sub t3, t3, t0 /* t3 <- __bss_start_ofs */
add t2, t0, t3 /* t2 <- source end address */
//下面是代码的copy重定向,太长了,懒得看了
copy_loop:
LREG t5, 0(t0)
addi t0, t0, REGBYTES
SREG t5, 0(t1)
addi t1, t1, REGBYTES
blt t0, t2, copy_loop
/*
* Update dynamic relocations after board_init_f
*/
fix_rela_dyn:
la t1, __rel_dyn_start
la t2, __rel_dyn_end
beq t1, t2, clear_bss
add t1, t1, t6 /* t1 <- rela_dyn_start in RAM */
add t2, t2, t6 /* t2 <- rela_dyn_end in RAM */
/*
* skip first reserved entry: address, type, addend
*/
j 10f
6:
LREG t5, -(REGBYTES*2)(t1) /* t5 <-- relocation info:type */
li t3, R_RISCV_RELATIVE /* reloc type R_RISCV_RELATIVE */
bne t5, t3, 8f /* skip non-RISCV_RELOC entries */
LREG t3, -(REGBYTES*3)(t1)
LREG t5, -(REGBYTES)(t1) /* t5 <-- addend */
add t5, t5, t6 /* t5 <-- location to fix up in RAM */
add t3, t3, t6 /* t3 <-- location to fix up in RAM */
SREG t5, 0(t3)
j 10f
8:
la t4, __dyn_sym_start
add t4, t4, t6
9:
LREG t5, -(REGBYTES*2)(t1) /* t5 <-- relocation info:type */
srli t0, t5, SYM_INDEX /* t0 <--- sym table index */
andi t5, t5, 0xFF /* t5 <--- relocation type */
li t3, RELOC_TYPE
bne t5, t3, 10f /* skip non-addned entries */
LREG t3, -(REGBYTES*3)(t1)
li t5, SYM_SIZE
mul t0, t0, t5
add s5, t4, t0
LREG t0, -(REGBYTES)(t1) /* t0 <-- addend */
LREG t5, REGBYTES(s5)
add t5, t5, t0
add t5, t5, t6 /* t5 <-- location to fix up in RAM */
add t3, t3, t6 /* t3 <-- location to fix up in RAM */
SREG t5, 0(t3)
10:
addi t1, t1, (REGBYTES*3)
ble t1, t2, 6b
设置新的重定向后的异常入口地址
/*
* trap update, 代码已经重定位完成,设置tvec为重定位后的地址
*/
la t0, trap_entry
add t0, t0, t6
csrw MODE_PREFIX(tvec), t0
清bss
clear_bss: /* clear 重定位后的bss段内存 */
la t0, __bss_start /* t0 <- rel __bss_start in FLASH */
add t0, t0, t6 /* t0 <- rel __bss_start in RAM */
la t1, __bss_end /* t1 <- rel __bss_end in FLASH */
add t1, t1, t6 /* t1 <- rel __bss_end in RAM */
beq t0, t1, relocate_secondary_harts
clbss_l:
SREG zero, 0(t0) /* clear loop... */
addi t0, t0, REGBYTES
blt t0, t1, clbss_l
relocate_secondary_harts:
#if CONFIG_IS_ENABLED(SMP)
/* send relocation IPI */
la t0, secondary_hart_relocate
add a0, t0, t6
/* store relocation offset */
mv s5, t6
mv a1, s2
mv a2, s3
mv a3, zero
jal smp_call_function
/* hang if relocation of secondary harts has failed */
beqz a0, 1f
mv a1, a0
la a0, secondary_harts_relocation_error
jal printf
jal hang
/* restore relocation offset */
1: mv t6, s5
#endif
调用board_init_r,进行第二阶段的初始化
/*
* We are done. Do not return, instead branch to second part of board
* initialization, now running from RAM.
* 开始第二阶段的初始化,跳到board_init_r新的地址开始执行,不再返回了.......
*/
call_board_init_r:
jal invalidate_icache_all //使icache无效
jal flush_dcache_all //flush dcache
la t0, board_init_r /* offset of board_init_r(),uboot使用的是common下面的board_r.c,spl要自己实现 */
add t4, t0, t6 /* real address of board_init_r() ,计算出新的重定位后的board_init_r的地址*/
/*
* setup parameters for board_init_r
*/
mv a0, s3 /* gd_t */
mv a1, s4 /* dest_addr */
/*
* jump to it ...
*/
jr t4 /* jump to board_init_r() */
#if CONFIG_IS_ENABLED(SMP)
hart_out_of_bounds_loop:
/* Harts in this loop are out of bounds, increase CONFIG_NR_CPUS. */
wfi
j hart_out_of_bounds_loop
/* SMP relocation entry */
secondary_hart_relocate:
/* a1: new sp */
/* a2: new gd */
/* tp: hart id */
/* setup stack */
slli t0, tp, CONFIG_STACK_SIZE_SHIFT
sub sp, a1, t0
/* update global data pointer */
mv gp, a2
#endif
/*
* Interrupts are disabled globally, but they can still be read from m/sip. The
* wfi function will wake us up if we get an IPI, even if we do not trap.
*/
secondary_hart_loop:
wfi
#if CONFIG_IS_ENABLED(SMP)
csrr t0, MODE_PREFIX(ip)
#if CONFIG_IS_ENABLED(RISCV_MMODE)
andi t0, t0, MIE_MSIE
#else
andi t0, t0, SIE_SSIE
#endif
beqz t0, secondary_hart_loop
mv a0, tp
jal handle_ipi
#endif
j secondary_hart_loop