Reading OpenSBI part6

Thursday, March 18, 2021

前回 。 今回は、_start_warmを読んでいく。

_start_warm

firmware/fw_base.S

...
_start_warm:
	/* Reset all registers for non-boot HARTs */
	li	ra, 0
	call	_reset_regs

	/* Disable and clear all interrupts */
	csrw	CSR_MIE, zero
	csrw	CSR_MIP, zero

	/* Find HART count and HART stack size */
	la	a4, platform
#if __riscv_xlen == 64
	lwu	s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4)
	lwu	s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4)
#else
	lw	s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4)
	lw	s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4)
#endif
	REG_L	s9, SBI_PLATFORM_HART_INDEX2ID_OFFSET(a4)

	/* Find HART id */
	csrr	s6, CSR_MHARTID

	/* Find HART index */
	beqz	s9, 3f
	li	a4, 0
1:
#if __riscv_xlen == 64
	lwu	a5, (s9)
#else
	lw	a5, (s9)
#endif
	beq	a5, s6, 2f
	add	s9, s9, 4
	add	a4, a4, 1
	blt	a4, s7, 1b
	li	a4, -1
2:	add	s6, a4, zero
3:	bge	s6, s7, _start_hang
...

_reset_regsでは、fence.iをしてから、汎用レジスタおよび、mscratchレジスタを0で初期化する。 その後、mipmieをクリアする。 struct sbi_platformへのポインタをa4レジスタへ格納する。 a4レジスタを用いてsbi_platform構造体のフィールドから情報を取得する。 s7にhart count、s8にスタックサイズ、s9hart_index2idテーブルのポインタをそれぞれ格納する。 s6レジスタにmhartidCSRの値を取り出す。

hart_index2idテーブルを索引することで、indexに対応するhartidを取り出す。 使われていないインデックスには-1を返す。使われているインデックスにはhartidを返す。 なお、hart_index2id == NULLの場合はidentity mappingであり、index == hartidとなる。 hart idおよびhart idについての制限は以下のとおりである。

  • hart index < sbi_platform hart_count
  • hart id < SBI_HARTMASK_MAX_BITS

まずは、s9がNULLか調べ、NULLの場合はラベル3へ分岐する。 NULL出ない場合は、a4レジスタ(index)を0で初期化する。 ラベル1にてa5レジスタにhart_index2idの索引結果を入れる。 a5 == s6(mhartid)の場合はラベル2へ分岐する。 add s9, s9, 4がわからん。(s9hart_index2id(配列)の先頭アドレスを持っているはず。) a4(index)を+1する。 a4(index) < s7(hart count)の場合は、ラベル1へ戻る。 それ以外はa4レジスタを-1にする。(使われていないindex) ラベル2において、s6レジスタ(mhartid)にa4レジスタの値を入れる。 ラベル3では、s6(hartid) <= s7(hart count)の場合は、_start_hangへ分岐し、ハングする。

hartのscratch spaceの準備

...
	/* Find the scratch space based on HART index */
	la	tp, _fw_end
	mul	a5, s7, s8		/* hart count * stack size */
	add	tp, tp, a5		/* last address of scratch area of all harts */
	mul	a5, s8, s6		/* stack size * hartid */
	sub	tp, tp, a5		/* scratch space of hartid */
	li	a5, SBI_SCRATCH_SIZE
	sub	tp, tp, a5		/* first address of scratch space of hartid */

	/* update the mscratch */
	csrw	CSR_MSCRATCH, tp

	/* Setup stack */
	add	sp, tp, zero
...

次に、scratch spaceを見つける。 コード片にコメントを挿入した。_fw_endの後にn-hartid-1のscratch spaceがあり、_fw_end + stack size * hart countにhart0のscratch spaceがある。 tpはhartのscratch spaceの先頭アドレスを示す。 hartのscratch spaceの先頭アドレスをmscratchCSRに格納し、spをscratch spaceにする。

トラップハンドラーの設定

...
	/* Setup trap handler */
	la	a4, _trap_handler
#if __riscv_xlen == 32
	csrr	a5, CSR_MISA
	srli	a5, a5, ('H' - 'A')
	andi	a5, a5, 0x1
	beq	a5, zero, _skip_trap_handler_rv32_hyp
	la	a4, _trap_handler_rv32_hyp
_skip_trap_handler_rv32_hyp:
#endif
	csrw	CSR_MTVEC, a4

#if __riscv_xlen == 32
	/* Override trap exit for H-extension */
	csrr	a5, CSR_MISA
	srli	a5, a5, ('H' - 'A')
	andi	a5, a5, 0x1
	beq	a5, zero, _skip_trap_exit_rv32_hyp
	la	a4, _trap_exit_rv32_hyp
	csrr	a5, CSR_MSCRATCH
	REG_S	a4, SBI_SCRATCH_TRAP_EXIT_OFFSET(a5)
_skip_trap_exit_rv32_hyp:
#endif
...

RV32の場合、misaレジスタを確認し、H-extensionが有効であれば、H-mode用のトラップハンドラー(trap_handler_rv32_hyp)を設定する。 それ以外は、mtvec_trap_handlerを設定する。 なお、トラップハンドラーは8-byte境界配置されており、下位3ビットは常に000である。よってDirect modeによるトラップが行われる。 H-extensionが有効の場合は、struct sbi_scratchtrap_exittrap_exit_rv32_hypで上書きする。

...	
.section .entry, "ax", %progbits
	.align 3
	.globl _trap_handler
_trap_handler:
	TRAP_SAVE_AND_SETUP_SP_T0
	TRAP_SAVE_MEPC_MSTATUS 0
	TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0
	TRAP_CALL_C_ROUTINE
	TRAP_RESTORE_GENERAL_REGS_EXCEPT_SP_T0
	TRAP_RESTORE_MEPC_MSTATUS 0
	TRAP_RESTORE_SP_T0
	mret
...
#if __riscv_xlen == 32
	.section .entry, "ax", %progbits
	.align 3
	.globl _trap_handler_rv32_hyp
_trap_handler_rv32_hyp:
	TRAP_SAVE_AND_SETUP_SP_T0
	TRAP_SAVE_MEPC_MSTATUS 1
	TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0
	TRAP_CALL_C_ROUTINE
	TRAP_RESTORE_GENERAL_REGS_EXCEPT_SP_T0
	TRAP_RESTORE_MEPC_MSTATUS 1
	TRAP_RESTORE_SP_T0
	mret
	.section .entry, "ax", %progbits
	.align 3
	.globl _trap_exit_rv32_hyp
_trap_exit_rv32_hyp:
	add	sp, a0, zero
	TRAP_RESTORE_GENERAL_REGS_EXCEPT_SP_T0
	TRAP_RESTORE_MEPC_MSTATUS 1
	TRAP_RESTORE_SP_T0
	mret
#endif
...

_trap_handler

TRAP_SAVE_AND_SETUP_SP_T0

TRAP_SAVE_AND_SETUP_SP_T0について調べていく。 mscratchレジスタにstruct sbi_scratchのポインタが入っている。 tpmscratchをスワップする。 tmp0フィールドはscratch spaceとして扱える。そこにt0レジスタを格納する。 t0レジスタにスタックポインタを作成する。 ((mstatus.mpp < PRIV_M) ? 1 : 0) - 1により、M-modeからのトラップであれば、-1、それ以外は0とする。 tp ^ (((mstatus.mpp < PRIV_M) ? 1 : 0) - 1) & (SP ^ TP))にて、M-modeであれば、SP、それ以外はTPをスタックポインタとしてt0へ格納する。 t0からSBI_TRAP_REGS_SIZE引き、struct sbi_trap_regsをスタックに確保し、spstruct sbi_trap_regsspフィールドに格納する。 spt0 - SBI_TRAP_REGS_SIZEをスタックポインタとして格納する。 t0レジスタをstruct sbi_scratchtmp0フィールドから取り出し(tpをスタックポインタにしていることに注意)、struct sbi_trap_regst0フィールドに格納する。 tpmscratchをスワップする。 これ以降、t0レジスタは自由に使え、spを通して、例外スタックにアクセスできる。

...
.macro	TRAP_SAVE_AND_SETUP_SP_T0
	/* Swap TP and MSCRATCH */
	csrrw	tp, CSR_MSCRATCH, tp

	/* Save T0 in scratch space */
	REG_S	t0, SBI_SCRATCH_TMP0_OFFSET(tp)

	/*
	 * Set T0 to appropriate exception stack
	 *
	 * Came_From_M_Mode = ((MSTATUS.MPP < PRV_M) ? 1 : 0) - 1;
	 * Exception_Stack = TP ^ (Came_From_M_Mode & (SP ^ TP))
	 *
	 * Came_From_M_Mode = 0    ==>    Exception_Stack = TP
	 * Came_From_M_Mode = -1   ==>    Exception_Stack = SP
	 */
	csrr	t0, CSR_MSTATUS
	srl	t0, t0, MSTATUS_MPP_SHIFT
	and	t0, t0, PRV_M
	slti	t0, t0, PRV_M
	add	t0, t0, -1
	xor	sp, sp, tp
	and	t0, t0, sp
	xor	sp, sp, tp
	xor	t0, tp, t0

	/* Save original SP on exception stack */
	REG_S	sp, (SBI_TRAP_REGS_OFFSET(sp) - SBI_TRAP_REGS_SIZE)(t0)

	/* Set SP to exception stack and make room for trap registers */
	add	sp, t0, -(SBI_TRAP_REGS_SIZE)

	/* Restore T0 from scratch space */
	REG_L	t0, SBI_SCRATCH_TMP0_OFFSET(tp)

	/* Save T0 on stack */
	REG_S	t0, SBI_TRAP_REGS_OFFSET(t0)(sp)

	/* Swap TP and MSCRATCH */
	csrrw	tp, CSR_MSCRATCH, tp
.endm
...

TRAP_SAVE_MEPC_MSTATUS

次に、TRAP_SAVE_MEPC_MSTATUS have_mstatushを調べていく。 spは例外スタックを指しており、struct sbi_trap_regsにレジスタを保存していく。 t0レジスタはすでに保存済みであり、自由に使える。 mepcmstatusレジスタをstruct sbi_trap_regsmepcmstatusフィールドにそれぞれ保存する。 have_mstatush1の場合は、mstatushmstatushフィールドに保存する。 それ以外は、0mstatushフィールドに保存する。

...
.macro	TRAP_SAVE_MEPC_MSTATUS have_mstatush
	/* Save MEPC and MSTATUS CSRs */
	csrr	t0, CSR_MEPC
	REG_S	t0, SBI_TRAP_REGS_OFFSET(mepc)(sp)
	csrr	t0, CSR_MSTATUS
	REG_S	t0, SBI_TRAP_REGS_OFFSET(mstatus)(sp)
	.if \have_mstatush
	csrr	t0, CSR_MSTATUSH
	REG_S	t0, SBI_TRAP_REGS_OFFSET(mstatusH)(sp)
	.else
	REG_S	zero, SBI_TRAP_REGS_OFFSET(mstatusH)(sp)
	.endif
.endm
...

TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0

次に、TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0を見ていく。

...
.macro	TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0
	/* Save all general regisers except SP and T0 */
	REG_S	zero, SBI_TRAP_REGS_OFFSET(zero)(sp)
	REG_S	ra, SBI_TRAP_REGS_OFFSET(ra)(sp)
	REG_S	gp, SBI_TRAP_REGS_OFFSET(gp)(sp)
	REG_S	tp, SBI_TRAP_REGS_OFFSET(tp)(sp)
	REG_S	t1, SBI_TRAP_REGS_OFFSET(t1)(sp)
	REG_S	t2, SBI_TRAP_REGS_OFFSET(t2)(sp)
	REG_S	s0, SBI_TRAP_REGS_OFFSET(s0)(sp)
	REG_S	s1, SBI_TRAP_REGS_OFFSET(s1)(sp)
	REG_S	a0, SBI_TRAP_REGS_OFFSET(a0)(sp)
	REG_S	a1, SBI_TRAP_REGS_OFFSET(a1)(sp)
	REG_S	a2, SBI_TRAP_REGS_OFFSET(a2)(sp)
	REG_S	a3, SBI_TRAP_REGS_OFFSET(a3)(sp)
	REG_S	a4, SBI_TRAP_REGS_OFFSET(a4)(sp)
	REG_S	a5, SBI_TRAP_REGS_OFFSET(a5)(sp)
	REG_S	a6, SBI_TRAP_REGS_OFFSET(a6)(sp)
	REG_S	a7, SBI_TRAP_REGS_OFFSET(a7)(sp)
	REG_S	s2, SBI_TRAP_REGS_OFFSET(s2)(sp)
	REG_S	s3, SBI_TRAP_REGS_OFFSET(s3)(sp)
	REG_S	s4, SBI_TRAP_REGS_OFFSET(s4)(sp)
	REG_S	s5, SBI_TRAP_REGS_OFFSET(s5)(sp)
	REG_S	s6, SBI_TRAP_REGS_OFFSET(s6)(sp)
	REG_S	s7, SBI_TRAP_REGS_OFFSET(s7)(sp)
	REG_S	s8, SBI_TRAP_REGS_OFFSET(s8)(sp)
	REG_S	s9, SBI_TRAP_REGS_OFFSET(s9)(sp)
	REG_S	s10, SBI_TRAP_REGS_OFFSET(s10)(sp)
	REG_S	s11, SBI_TRAP_REGS_OFFSET(s11)(sp)
	REG_S	t3, SBI_TRAP_REGS_OFFSET(t3)(sp)
	REG_S	t4, SBI_TRAP_REGS_OFFSET(t4)(sp)
	REG_S	t5, SBI_TRAP_REGS_OFFSET(t5)(sp)
	REG_S	t6, SBI_TRAP_REGS_OFFSET(t6)(sp)
.endm
...

単に、spt0レジスタ以外の汎用レジスタをstruct sbi_trap_regsの対応するフィールドに保存していく。

TRAP_CALL_C_ROUTINE

次は、TRAP_CALL_C_ROUTINEを見ていく。

...
.macro	TRAP_CALL_C_ROUTINE
	/* Call C routine */
	add	a0, sp, zero
	call	sbi_trap_handler
.endm
...

spを第一引数(a0に格納)してsbi_trap_handler(C関数(lib/sbi/sbi_trap.c))を呼び出す。 ここから、C言語で処理を行う。

復帰処理

TRAP_RESTORE_GENERAL_REGS_EXCEPT_SP_T0TRAP_RESTORE_MEPC_MSTATUS 0TRAP_RESTORE_SP_T0mretの復帰のシーケンスは _trap_exitと同じである。

_trap_handler_rv32_hyp

とりあえず、スキップ

_trap_exit_rv32_hyp

とりあえず、スキップ

sbi_initの実行(C関数)

...
	/* Initialize SBI runtime */
	csrr	a0, CSR_MSCRATCH
	call	sbi_init
	/* We don't expect to reach here hence just hang */
	j	_start_hang
...

sbi_initsbi_trap_handlerなどを呼び出す前に、mscratchにはstruct sbi_scratchのアドレスが格納されている必要がある。 また、spはhartのかぶっていないスタックを指す。

C言語で書かれたsbi_initを呼び出す。 なお、sbi_initは復帰しない。

次はsbi_trap_handlersbi_initからPAYLOADへ制御の引き渡しを見ていく。

OpenSBIOpenSBILinuxRISC-V

Reading OpenSBI part7

Reading OpenSBI part5