RISC-V
Berkeley Boot Loader & Proxy Kernel
のソースコード解析
@Vengineer
2017/1/9
Vengineer DEATH
無限ゲームのなか
@Vengineer に居ます
RISC-Vのブートについて調べてみました。
よろしくお願いします。
自己紹介
RISC-V:https://riscv.org/
The Free and Open RISC ISA
Google / Microsoft /
HPE / IBM / AORACLE / MD / NVIDIA /
Qualcomm / NXP / Rambus / IDT / Micron /
Western Digital / HUAWEI / Mellanox /
Microsemi / LATTICE /
SiFive / Esperanto Technologies / lowRISC /
ultraSoC / SH CONSULTING /
bluespec / Codasip
RISC-V : github.com/riscv
各種ソフトウェア
riscv-gnu-toolchain
riscv-gcc / riscv-glibc / riscv-dejagnu
riscv-binutils-gdb / riscv-newlib
riscv-pk
riscv-linux/riscv-poky
riscv-qemu/riscv-isa-sim/riscv-fesvr
riscv-angle
riscv-llvm/riscv-clang/riscv-go
GNU Compiler Toolchain
https://github.com/riscv/riscv-gnu-toolchain
$ git clone --recursive 
https://github.com/riscv/riscv-gnu-toolchain
$ ./configure --prefix=/opt/riscv
$ make
で、ツールチェインをビルドする
RISC-V Proxy Kernel
https://github.com/riscv/riscv-pk
2つのパッケージ
 RISC−V Proxy Kernel (pk)
  Baremetal Applicationを実行
 Berkeley Boot Loader (bbl)
  Linux Kernelを実行
が実装されている
RISC-Vの権限階層
Supervisor Mode
User Mode
Hypervisor Mode
Machine Mode
mret
sret
hret
リセット解除
参考資料) : https://riscv.org/specifications/privileged-isa
RISC-Vの権限階層
Supervisor Mode
User Mode
Machine Mode
mret
sret
リセット
解除
bblは、ここで
Linuxを実行
pkは、ここで
ユーザアプリを実行
リセット解除後
リセット解除後、
下記のコードをMahine Modeにて実行する
・reset_vector (machine/mentry.S)
・do_reset (machine/mentyr.S)
・init_first_hart (machine/minit.c)
・boot_loader
pk用 (pk/pk.c)
bbl用 (bbl/bbl.c)
Proxy Kernelの場合
boot_loader(pk用)
void boot_loader()
{
extern char trap_entry;
write_csr(stvec, &trap_entry);
write_csr(sscratch, 0);
write_csr(sie, 0);
file_init();
// Supervisor modeで rest_of_boot_loader 関数を実行
enter_supervisor_mode(rest_of_boot_loader, pk_vm_init());
}
enter_supervisor_mode
mchine/minit.c
void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t stack)
{
uintptr_t mstatus = read_csr(mstatus);
mstatus = INSERT_FIELD(mstatus, MSTATUS_MPP, PRV_S);
mstatus = INSERT_FIELD(mstatus, MSTATUS_MPIE, 0);
write_csr(mstatus, mstatus);
write_csr(mscratch, MACHINE_STACK_TOP() - MENTRY_FRAME_SIZE);
write_csr(mepc, fn);
write_csr(sptbr, (uintptr_t)root_page_table >> RISCV_PGSHIFT);
asm volatile ("mv a0, %0; mv sp, %0; mret" : : "r" (stack));
__builtin_unreachable();
}
enter_supervisor_mode
intptr_t mstatus = read_csr(mstatus);
mstatus = INSERT_FIELD(mstatus, MSTATUS_MPP, PRV_S);
mstatus = INSERT_FIELD(mstatus, MSTATUS_MPIE, 0);
write_csr(mstatus, mstatus);
write_csr(mscratch, MACHINE_STACK_TOP() - MENTRY_FRAME_SIZE);
write_csr(mepc, fn);
write_csr(sptbr, (uintptr_t)root_page_table >> RISCV_PGSHIFT);
// mstatus / mscratch / mepc / sptbr を設定し、
asm volatile ("mv a0, %0; mv sp, %0; mret" : : "r" (stack));
// mret で Supervisor mode でプログラム(fn) を実行
参考資料) : http://csg.csail.mit.edu/F6.175/labs/exception.pdf
Supervisor modeでの実行?
void enter_supervisor_mode(void (*fn)(uintptr_t), uintptr_t stack)
enter_supervisor_mode(rest_of_boot_loader, pk_vm_init());
参考) : http://csg.csail.mit.edu/F6.175/labs/exception.pdf
rest_of_boot_loader
static void rest_of_boot_loader(uintptr_t kstack_top)
{
arg_buf args;
size_t argc = parse_args(&args);
if (!argc)
panic("tell me what ELF to load!");
// load program named by argv[0]
long phdrs[128];
current.phdr = (uintptr_t)phdrs;
current.phdr_size = sizeof(phdrs);
// アプリケーション(args.argv[0])をDRAMにロードする
load_elf(args.argv[0], &current);
// ロードしたアプリケーションを実行する
run_loaded_program(argc, args.argv, kstack_top);
}
run_loaded_program
static void run_loaded_program(size_t argc, char** argv, uintptr_t kstack_top)
{
// 途中略
trapframe_t tf;
init_tf(&tf, current.entry, stack_top);
__clear_cache(0, 0);
write_csr(sscratch, kstack_top);
// ここでユーザーアプリを起動
start_user(&tf);
}
start_user
pk/entry.S
.globl start_user
start_user:
LOAD t0, 32*REGBYTES(a0) // a0はスタック
LOAD t1, 33*REGBYTES(a0)
csrw sstatus, t0
csrw sepc, t1 // sscratch <= kstack_top 済み
# restore x registers
LOAD x1,1*REGBYTES(a0)
// 途中略
# restore a0 last
LOAD x10,10*REGBYTES(a0)
# gtfo
sret // Supervisor mode => User modeへ
tf : trapframe_t
pk/pk.h
typedef struct
{
long gpr[32];
long status; // LOAD t0, 32*REGBYTES(a0) : t0 => sstatus
long epc; // LOAD t1, 33*REGBYTES(a0) : t1 => sepc
long badvaddr;
long cause;
long insn;
} trapframe_t;
Berkeley Boot Loader
の場合
boot_loader(bbl用)
void boot_loader()
{
extern char _payload_start, _payload_end;
// ここでカーネルをDRAMにロードする
load_kernel_elf(&_payload_start, &_payload_end - &_payload_start, &info);
supervisor_vm_init();
#ifdef PK_ENABLE_LOGO
print_logo();
#endif
mb();
elf_loaded = 1;
enter_supervisor_mode((void *)info.entry, 0);
}
load_kernel_elf
bbl/kernel_load.c
void load_kernel_elf(void* blob, size_t size, kernel_elf_info* info)
{
Elf_Ehdr* eh = blob;
// 途中略
info->entry = eh->e_entry; // KernelのELF
info->load_offset = bias;
info->first_user_vaddr = min_vaddr;
info->first_vaddr_after_user = ROUNDUP(max_vaddr - bias, RISCV_PGSIZE);
return;
}
_payload_start
bbl/payload.S
.section ".payload","a",@progbits
.align 3
.globl _payload_start, _payload_end
_payload_start:
.incbin BBL_PAYLOAD // --with-payload=で指定したバイナリ
_payload_end:
dummy_payload
dummy_payload/dummy_payload.c
asm (".globl _startn
_start: la sp, stackn
j entryn
.pushsection .rodatan
.align 4n
.skip 4096n
stack:n
.popsection");
dummy_payload
dummy_payload/dummy_payload.c
void entry()
{
const char* message =
"This is bbl's dummy_payload. To boot a real kernel, reconfiguren
bbl with the flag --with-payload=PATH, then rebuild bbl.n";
while (*message)
sbi_console_putchar(*message++);
sbi_shutdown();
}
$ ./configure --with-payload=linux-kernel.elf
Machine-modeで
例外が発生すると、
mtvec(アドレス)に飛ぶ
mtvec
Machine Trap-Vector Base-Address Register (mtvec)
do_reset:
// 途中略
# write mtvec and make sure it sticks
la t0, trap_vector
csrw mtvec, t0
csrr t1, mtvec
1:bne t0, t1, 1b
trap_vector
machine/mentry.S
trap_vector:
csrrw sp, mscratch, sp
beqz sp, .Ltrap_from_machine_mode
STORE a0, 10*REGBYTES(sp)
STORE a1, 11*REGBYTES(sp)
csrr a1, mcause
bgez a1, .Lhandle_trap_in_machine_mode
trap_vector
# This is an interrupt. Discard the mcause MSB and decode the rest.
sll a1, a1, 1
# Is it a machine timer interrupt?
li a0, IRQ_M_TIMER * 2
bne a0, a1, 1f
li a1, TIMER_INTERRUPT_VECTOR
j .Lhandle_trap_in_machine_mode
.Lhandle_trap_in_machine_mode
.Lhandle_trap_in_machine_mode:
// 途中略
1:auipc t0, %pcrel_hi(trap_table) # t0 <- %hi(trap_table)
STORE t1, 6*REGBYTES(sp)
sll t1, a1, 2 # t1 <- mcause << 2
STORE t2, 7*REGBYTES(sp)
add t1, t0, t1 # t1 <- %hi(trap_table)[mcause]
STORE s0, 8*REGBYTES(sp)
LWU t1, %pcrel_lo(1b)(t1) # t1 <- trap_table[mcause]
STORE s1, 9*REGBYTES(sp)
mv a0, sp # a0 <- regs
STORE a2,12*REGBYTES(sp)
csrr a2, mepc # a2 <- mepc
STORE a3,13*REGBYTES(sp)
csrrw t0, mscratch, x0 # t0 <- user sp
.Lhandle_trap_in_machine_mode
STORE a4,14*REGBYTES(sp)
// 途中略
STORE t0, 2*REGBYTES(sp) # sp
#ifndef __riscv_flen
lw tp, (sp) # Move the emulated FCSR from x0's save slot into tp.
#endif
STORE x0, (sp) # Zero x0's save slot.
# Invoke the handler.
jalr t1 // 要因ハンドラ(trap_vector)にジャンプ
#ifndef __riscv_flen
sw tp, (sp) # Move the emulated FCSR from tp into x0's save slot.
#endif
.Lhandle_trap_in_machine_mode
restore_mscratch:
# Restore mscratch, so future traps will know they didn't come from M-mode.
csrw mscratch, sp
restore_regs:
# Restore all of the registers.
LOAD ra, 1*REGBYTES(sp)
// 途中略
LOAD sp, 2*REGBYTES(sp)
mret // ここでMachine-modeから抜ける
.Ltrap_from_machine_mode
.Ltrap_from_machine_mode:
csrr sp, mscratch
addi sp, sp, -INTEGER_CONTEXT_SIZE
STORE a0,10*REGBYTES(sp)
STORE a1,11*REGBYTES(sp)
li a1, TRAP_FROM_MACHINE_MODE_VECTOR
j .Lhandle_trap_in_machine_mode
TRAP_FROM_MACHINE_MODE_VECTOR
#define TRAP_FROM_MACHINE_MODE_VECTOR 14
.word __trap_from_machine_mode
__trap_from_machine_mode:
jal trap_from_machine_mode
j restore_regs // ここでMachie-modeから抜ける
trap_from_machine_mode
machine/mtrap.c
void trap_from_machine_mode(uintptr_t* regs, uintptr_t dummy, uintptr_t mepc)
{
uintptr_t mcause = read_csr(mcause);
switch (mcause)
{
case CAUSE_FAULT_LOAD:
case CAUSE_FAULT_STORE:
return machine_page_fault(regs, mepc);
default:
bad_trap();
}
}
trap_table (その1)
.data
.align 6
trap_table:
.word bad_trap // Instruction address misaligned
.word bad_trap // Instruction access fault
.word illegal_insn_trap // Illegal instruction
.word bad_trap // Breakpoint
.word misaligned_load_trap // Load address misaligned
.word bad_trap // Load access fault
.word misaligned_store_trap// Store/AMO address misaligned
.word bad_trap // Store/AMO access fault
.word bad_trap // Environment call from U-mode
trap_table (その2)
.word mcall_trap // Environment call from S-mode
.word bad_trap // Environment call from H-mode
.word bad_trap // Environment call from M-mode
#define SOFTWARE_INTERRUPT_VECTOR 12
.word software_interrupt
#define TIMER_INTERRUPT_VECTOR 13
.word timer_interrupt
#define TRAP_FROM_MACHINE_MODE_VECTOR 14
.word __trap_from_machine_mode
mcall_trap
machine/mtrap.c
Supervisor modeからのコール( pk用として用意されている)
void mcall_trap(uintptr_t* regs, uintptr_t mcause, uintptr_t mepc)
{
uintptr_t n = regs[17], arg0 = regs[10], arg1 = regs[11], retval;
switch (n)
{
case MCALL_HART_ID:
retval = mcall_hart_id();
break;
// case が続く
おしまい

RISC-V : Berkeley Boot Loader & Proxy Kernelのソースコード解析