/* FW_BASH.S */
_start:
fw_boot_hart:
li a0, -1
/* try_lottery 这一段是判断一个全局变量是不是0,如果是0 就可以去relocate copy,如果不是0 就需要去等待relocate copy 结束 */
/* 这里主要是为了多core 处理所做的,当第一个core 执行这段时会把全局变量设置为1,其他core 执行这里就会去等待第一个core relocate copy结束 */
_try_lottery:
laa6, _relocate_lottery /* 先把全局变量的地址给到a6 */
lia7, 1 /* a7 = 1 */
amoadd.w a6, a7, (a6) /* 将全局变量的值读给a6, 然后将a6 + a7 的值再给到全局变量(如果有多个core,这个全局变量就会被加多次 */
bneza6, _wait_relocate_copy_done /* 判断这个a6的值是不是为0,如果为0 则表明上述代码时第一次执行,这个core需要去完成重定位搬运,其他后来的core需要去等这个过程完成 */
/* 保存下load address */
la t0, _load_start /* t0 = 0x80000500 */
la t1, _start /* t1 = 0x80000000 */
REG_S t1, 0(t0) /* 将t1 写到地址 0x80000500 */
_relocate:
lat0, _link_start
REG_Lt0, 0(t0) /* t0 = link_start */
lat1, _link_end
REG_Lt1, 0(t1) /* t1 = link_end */
lat2, _load_start
REG_Lt2, 0(t2) /* t2 = load_sart */
subt3, t1, t0 /* t3 = link_end - link_satrt = size */
addt3, t3, t2 /* t3 = load_start + size */
beqt0, t2, _relocate_done /* 如果链接地址和加载地址一致,那么就不需要重定位了 */
lat4, _relocate_done /* t4 = _relocate_done */
subt4, t4, t2
addt4, t4, t0 /* 计算出了重定位后的relocate_done 的位置 */
bltt2, t0, _relocate_copy_to_upper /* 比较link_start 和 load_start 的大小,确认下一步relocate 怎么执行 */
/* 当load_start 比 link_satrt 大的的时候,我们需要把代码copy_to_lower */
_relocate_copy_to_lower:
blet1, t2, _relocate_copy_to_lower_loop /* t1(link_end) 小于等于 t2(load_start) 的话就跳到 _relocate_copy_to_lower_loop */
lat3, _relocate_lottery
BRANGEt2, t1, t3, _start_hang
lat3, _boot_status
BRANGEt2, t1, t3, _start_hang
lat3, _relocate
lat5, _relocate_done
BRANGEt2, t1, t3, _start_hang
BRANGEt2, t1, t5, _start_hang
BRANGE t3, t5, t2, _start_hang
_relocate_copy_to_lower_loop:
REG_Lt3, 0(t2)
REG_St3, 0(t0)
addt0, t0, __SIZEOF_POINTER__
addt2, t2, __SIZEOF_POINTER__
bltt0, t1, _relocate_copy_to_lower_loop
jrt4
_relocate_done:
/*
* Mark relocate copy done
* Use _boot_status copy relative to the load address
*/
lat0, _boot_status /* t0 = _boot_status */
lat1, _link_start /* t1 = _link_start */
REG_Lt1, 0(t1) /* t1 = 0x80000000 */
lat2, _load_start /* t2 = _load_start */
REG_Lt2, 0(t2) /* t2 = 0x80000000 */
subt0, t0, t1 /* t0 = t0 - t1 */
addt0, t0, t2 /* t0 = t0 + t2 */
lit1, BOOT_STATUS_RELOCATE_DONE
REG_St1, 0(t0) /* 把这个relocate_done 的标记写到新计算出来的位置 */
fencerw, rw
/* 上述这一段是为了通知其他在等待relocate_done 的core ,现在已经完成了relocate了 */
_reset_regs: /* reset reg 就是把通用寄存器啥的重新设为0 ,除了ra、a0、a1、a2 */
....
....
MOV_5Rs0, a0, s1, a1, s2, a2, s3, a3, s4, a4 /* 将a0\a1\a2\a3\a4分别保存到s0\s1\s2\s3\s4当中 */
call fw_save_info /* 这个调用里,只可以使用a0/a1/a2/a3/a4 , 当opensbi 使用jump时,这里fw_save_info 是直接返回的,啥都没做 */
MOV_5Ra0, s0, a1, s1, a2, s2, a3, s3, a4, s4 /* 恢复fw_save_info 之前的状态 */
laa4, platform /* 将platform保存在a4 当中 , 这个platform就是我们在automan\paltform.c 中定义的platform */
lwus7, SBI_PLATFORM_HART_COUNT_OFFSET(a4) /* 通过platform 地址得到hart_count参数的值,放在s7 */
lwus8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4) /* 同样通过platform地址得到stack_size参数的值,放在s8 */
/* 有了hart 的个数和hart 的stack size ,就可以为这些hart 设置栈空间了 */
latp, _fw_end /* tp = _fw_end */
mula5, s7, s8 /* a5 = s7 * s8 ,这里计算了总共需要的栈空间 */
addtp, tp, a5 /* tp = tp + a5 , 分配栈空间 */
/* Keep a copy of tp */
addt3, tp, zero /* t3 = tp + 0 */
/* Counter */
lit2, 1 /* t2 = 1 */
/* hartid 0 is mandated by ISA */
lit1, 0
_scratch_init:
addtp, t3, zero /* tp = tp */ /* tp = 0x80011000 */
mula5, s8, t1 /* a5 = 0 */
subtp, tp, a5 /* tp = tp - a5 */
lia5, SBI_SCRATCH_SIZE /* a5 = 512 */
subtp, tp, a5 /* tp = tp - 512 */ /* tp = 0x80010e00 */ /* 第一个core的栈顶 */
laa4, _fw_start /* a4 = fw_satrt */
laa5, _fw_end /* a5 = fw_end */
mul t0, s7, s8 /* t0 = s7 * s8 ,又一次计算了总的栈空间 */
adda5, a5, t0 /* a5 = a5 + t0 */
suba5, a5, a4 /* a5 = a5 - a4 */
REG_Sa4, SBI_SCRATCH_FW_START_OFFSET(tp) /* 把a4(fw_start) 放在了栈的开始 */
REG_Sa5, SBI_SCRATCH_FW_SIZE_OFFSET(tp) /* 把a5 (fw_size) 放在了栈的下一个位置 */
/* Store next arg1 in scratch space */
MOV_3Rs0, a0, s1, a1, s2, a2 /* 保存下a0\a1\a2 */
callfw_next_arg1 /* 这里是将FW_JUMP_FDT_ADDR的地址保存起来*/
REG_Sa0, SBI_SCRATCH_NEXT_ARG1_OFFSET(tp)
MOV_3Ra0, s0, a1, s1, a2, s2
/* Store next address in scratch space */
MOV_3Rs0, a0, s1, a1, s2, a2 /* 保存a0,a1,a2 */
callfw_next_addr /* fw_next_addr 就是将_jump_addr 设置到a
0 当中,这个地址定义在config.mk中 */
REG_Sa0, SBI_SCRATCH_NEXT_ADDR_OFFSET(tp) /* 通过fw_next_addr 我们得到了下一阶段(0x81000000)的地址,这里将其保存到栈里 */
MOV_3Ra0, s0, a1, s1, a2, s2 /* 恢复a0,a1,a2 */
/* Store next mode in scratch space */
MOV_3Rs0, a0, s1, a1, s2, a2
callfw_next_mode
REG_Sa0, SBI_SCRATCH_NEXT_MODE_OFFSET(tp) /* 将下一个mode即S mode 也保存到栈中*/
MOV_3Ra0, s0, a1, s1, a2, s2
/* Store warm_boot address in scratch space */
laa4, _start_warm
REG_Sa4, SBI_SCRATCH_WARMBOOT_ADDR_OFFSET(tp) /* 将热启动的地址放入栈中 */
/* Store platform address in scratch space */
laa4, platform
REG_Sa4, SBI_SCRATCH_PLATFORM_ADDR_OFFSET(tp) /* 将platform 地址放入栈中 */
/* Store hartid-to-scratch function address in scratch space */
laa4, _hartid_to_scratch
REG_Sa4, SBI_SCRATCH_HARTID_TO_SCRATCH_OFFSET(tp) /* 将hartid_to_scratch放入栈中 */
/* Clear tmp0 in scratch space */
REG_Szero, SBI_SCRATCH_TMP0_OFFSET(tp) /* 将栈的其余地方清零 */
/* Store firmware options in scratch space */
MOV_3Rs0, a0, s1, a1, s2, a2 /* 将a0、a1、a2 保存起来 */
#ifdef FW_OPTIONS
lia4, FW_OPTIONS
#else
adda4, zero, zero
#endif
callfw_options
ora4, a4, a0
REG_Sa4, SBI_SCRATCH_OPTIONS_OFFSET(tp) /* 将options 保存到栈中 */
MOV_3Ra0, s0, a1, s1, a2, s2
/* Move to next scratch space */
addt1, t1, t2 /* t1 等于0 ,t2等于1 , 每执行一次这个scrtch init t1 会自增1 */
bltt1, s7, _scratch_init /* s7 是core的数量。这里就是个for 循环, 依次为每个core申请栈和scratch 区间 */
/* 在此之后,内存分布如下 */
|****************| hartN scratch | hart N stack | hart N-1 scratch | hart N-1 stack | ---- | hart 0 scratch | hart 0 stack |
fw_start fw_end
/* Zero-out BSS */
laa4, _bss_start
laa5, _bss_end
_bss_zero:
REG_Szero, (a4)
adda4, a4, __SIZEOF_POINTER__
blta4, a5, _bss_zero /* bss 段清0 */
/* 清除完bss 段后,会进行fdt的搬运工作,fdt的地址保存在previous arg1 中(fdt 的初始地址是由进入opensbi时的a1寄存器决定的,fdt 需要搬运到的地址保存在next arg1 中(jump fdt addr 最终是由config.mk 决定的) */
_prev_arg1_override_done:
beqza1, _fdt_reloc_done /* 先判断下a1是不是为0,因为a1里保存的时fdt的地址,如果a1等于0那么就说明没有fdt需要搬运 */
***
_fdt_reloc_done: /* 如果需要搬运就搬一下,然后来到了这里 */
lit0, BOOT_STATUS_BOOT_HART_DONE
lat1, _boot_status
REG_St0, 0(t1)
fencerw, rw
j_start_warm /* 这一步又是把当前的状态更新到一个全局变量中, 通知其他core */
_start_warm:
/* Reset all registers for non-boot HARTs */
lira, 0
call_reset_regs
/* Disable and clear all interrupts */
csrwCSR_MIE, zero
csrwCSR_MIP, zero
laa4, platform
lwus7, SBI_PLATFORM_HART_COUNT_OFFSET(a4) /* 通过platform 拿到当前平台下的hart count */
lwus8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4) /* 通过platform 拿到当前平台下的stack size */
/* HART ID should be within expected limit */
csrrs6, CSR_MHARTID /* mhartid = 0 */
bges6, s7, _start_hang /* 如果s6 >= s7 那么就跳转到start_hang */
latp, _fw_end /* tp = fw_end */
mula5, s7, s8 /* a5 = s7 * s8 = 栈区大小 */
addtp, tp, a5 /* tp = tp + a5 ==> tp 移动到栈尾 */
mula5, s8, s6 /* 计算出当前hatr 的栈区 */
subtp, tp, a5 /* tp 移动到当前的hart 的栈顶 */
lia5, SBI_SCRATCH_SIZE
subtp, tp, a5 /* tp 移动到当前hart 的scratch 区域 */
csrwCSR_MSCRATCH, tp /* 将tp的值写入mscratch中, 这是一个备份寄存器 */
addsp, tp, zero /* 设置栈指针 */
laa4, _trap_handler
csrwCSR_MTVEC, a4 /* 设置异常入口 */
1:csrra5, CSR_MTVEC
bnea4, a5, 1b /* 这两步是一个小的死循环,为的是确保异常入口被正确的设置了 */
csrra0, CSR_MSCRATCH /* 将刚保存的tp写到a0 */
call sbi_init /* 准备好了栈区,准备好了中断异常入口,此时可以去为这个core调用c代码初始化了 */
/* SBI_INIT.c */
void __noreturn sbi_init(struct sbi_scratch *scratch)
u32 hartid = sbi_current_hartid(); /* 通过读取MHARTID 寄存器获取hartid */
if (atomic_add_return(&coldboot_lottery, 1) == 1) /* 通过原子指令去对一个全局变量进行加1操作,以确认谁是需要冷启动的cpu core */
init_coldboot(scratch, hartid); /* 在我们的automan中,只有一个core,冷启动 */
init_count_offset = sbi_scratch_alloc_offset(__SIZEOF_POINTER__,"INIT_COUNT"); /* 为这个变量分配一个内存,通过加锁的方式实现 */
rc = sbi_system_early_init(scratch, TRUE); /* 调用了platform的early_init */
rc = sbi_hart_init(scratch, hartid, TRUE);
mstatus_init(scratch, hartid); /* 通过misa判断当前core所支持的扩展,然后初始化了MSTATUS、关闭了中断(MIE)等 */
rc = fp_init(hartid); /* 不影响 */
rc = delegate_traps(scratch, hartid); /* 设置MIDELEG\MEDELEG两个寄存器将M 态下的中断委托到S态 */
return pmp_init(scratch, hartid); /* 设置pmp */
rc = sbi_console_init(scratch); /* 调用platform 的console init */
rc = sbi_platform_irqchip_init(plat, TRUE); /* 调用platform 的irqchip_init */
rc = sbi_ipi_init(scratch, TRUE); /* 核间通信使用软中断的形式实现,最后开启了软件中断 */
rc = sbi_tlb_init(scratch, TRUE); /* 为之后的用户态触发中断到opensbi 的处理做准备 */
rc = sbi_timer_init(scratch, TRUE); /* 调用了platform的timer_init */
rc = sbi_ecall_init(); /* opensbi 提供给用户态的接口,用户态调用ecall后,会进入trap_handler中,然后根据不同的id查找这里初始化好的链表去处理不同的事物 */
ret = sbi_ecall_register_extension(&ecall_time); /* 将ecall_time 加入ecall_exts_list 当中 */
ret = sbi_ecall_register_extension(&ecall_rfence);
ret = sbi_ecall_register_extension(&ecall_ipi);
ret = sbi_ecall_register_extension(&ecall_base);
ret = sbi_ecall_register_extension(&ecall_legacy);
ret = sbi_ecall_register_extension(&ecall_vendor);
rc = sbi_system_final_init(scratch, TRUE); /* 调用platform中的final_init */
sbi_boot_prints(scratch, hartid); /* 打印opensbi的log */
sbi_hart_wake_coldboot_harts(scratch, hartid); /* 唤醒其他的等待的core, 我们这里不需要做什么处理 */
sbi_hart_mark_available(hartid); /* 标记当前core为available */
sbi_hart_switch_mode(hartid, scratch->next_arg1, scratch->next_addr,scratch->next_mode, FALSE); /* 跳转进入uboot 等,切换到S Mode */
总结: opensbi 在一开始的汇编阶段做了这些事: 首先主core 会去做重定位,此时其他core会陷入循环等待主core 重定位结束,主core 重定位结束之后会通过写一个全局变量通知其他core此时已经完成了重定位了。完成重定位后主core 会去为每个core分配一段栈以及scratch空间,并将一些scratch结构体参数放入scratch空间(包括: 下一阶段的跳转地址、下一阶段的Mode等)。主core在完成栈空间分配后会清除bss 段。然后进行fdt的重定向,fdt的源地址保存在a1寄存器中(这个a1寄存器的值从进入opensbi至今 都还保持着原先得值)fdt的目的地地址时通过宏定义决定。在搬运fdt的过程中,首先会判断a1寄存器的值是否符号要求(是否存在fdt需要搬运)如果a1 == 0 直接跳过这一部分,搬运完fdt后,主core又会写一个全局变量通知其他core该做的初始化已经完成,接下来准备启动c调用了。其他core接收到这个通知后会跳出一个循环开始下一阶段。opensbi 汇编的最后就是每个core 去找到自己的栈空间,然后把栈顶设置到sp寄存器中,再设置好异常入口地址,紧接着就是跳转如c 代码执行sbi_init.