私の開発したRISC-VシミュレータはLinuxを立ち上げることができる。シミュレータのデバッグ時には相当中身を読み込んだのだが、きちんと文章化していない挙句、大昔のプロジェクトなのでもう忘れかけている。
Linuxのブートの方法から各種プロセスの取り扱いまで、思い出しながらRISC-Vシミュレータを動かしていき、ちゃんと文章化しておきたいと思った。
物理アドレスモードから仮想アドレスモードへの変更
さて、mret
によってbbl_payload
にジャンプする。bbl_payload
はいったい何をしているかを見ていかなければならない。まずはbbl_payload
のダンプを見てみるのが良いだろう。bbl_payload
はバイナリファイルなのだが、ベースとなっているのはfreedom-u-sdk/work/linux/vmlinux-stripped
なので、元になっているvmlinux
をobjdumpでダンプしてみる。
riscv64-unknown-elf-objdump -D freedom-u-sdk/work/linux/vmlinux > work/riscv-pk/vmlinux.dmp
vmlinux.dmp
Disassembly of section .init.text: ffffffe000000000 <_start>: ffffffe000000000: 10401073 csrw sie,zero ffffffe000000004: 00d33197 auipc gp,0xd33 ffffffe000000008: 8dc18193 addi gp,gp,-1828 # ffffffe000d328e0 <__global_pointer$> ffffffe00000000c: 6299 lui t0,0x6 ffffffe00000000e: 1002b073 csrc sstatus,t0 ...
_start
から始まっているようだ。どうにかしてアセンブリファイルを探せないだろうか。
ここにあった。
freedom-u-sdk/linux/arch/riscv/kernel/head.S
__INIT ENTRY(_start) /* Mask all interrupts */ csrw sie, zero /* Load the global pointer */ .option push .option norelax la gp, __global_pointer$ .option pop ...
- 全ての割り込みを一時的に停止する。
sie
(Supervisor Interrupt Enable)を0に設定する。現在はスーパバイザモードに入っているので、mie
ではなくsie
を操作する。
/* Mask all interrupts */ csrw sie, zero
- グローバルポインタの設定をする。グローバルポインタの位置は以下に設定される。
/* Load the global pointer */ .option push .option norelax la gp, __global_pointer$ .option pop
ffffffe000d328e0 <__global_pointer$>: ffffffe000d328e0: ffe1 bnez a5,ffffffe000d328b8 <sysctl_icmp_msgs_per_sec> ffffffe000d328e2: ffff 0xffff ffffffe000d328e4: 0000 unimp
/* * Disable FPU to detect illegal usage of * floating point in kernel space */ li t0, SR_FS csrc sstatus, t0
- CPUのうちの一つがブートシーケンスを実行する。それ以外のCPUは
.Lsecondary_start
にジャンプする。
/* Pick one hart to run the main boot sequence */ la a3, hart_lottery li a2, 1 amoadd.w a3, a2, (a3) bnez a3, .Lsecondary_start
- 仮想アドレスの設定を行う。
setup_vm
を呼び出す。
la sp, init_thread_union + THREAD_SIZE call setup_vm
freedom-u-sdk/linux/arch/riscv/kernel/setup.c
asmlinkage void __init setup_vm(void) { extern char _start; uintptr_t i; uintptr_t pa = (uintptr_t) &_start; pgprot_t prot = __pgprot(pgprot_val(PAGE_KERNEL) | _PAGE_EXEC); va_pa_offset = PAGE_OFFSET - pa; pfn_base = PFN_DOWN(pa); ...
va_pa_offset
とpfn_base
はグローバルに定義されているシンボルである。
pfn
とは、Page Frame Numberの意味で、ページに対して付加されるインデックスである。
va_pa_offset
はその名の通り仮想アドレスのベースと物理アドレスのベースの差分である。現在のモードはスーパバイザモードではあるが、RISC-VのsatpのVMModeはMBarのために未だに物理アドレスモードで動作している。したがって、変数pa
は物理アドレスでのstart
の場所を示しており、PAGE_OFFSET
は仮想アドレスのベースとなるアドレスが格納されている。
freedom-u-sdk/linux/arch/riscv/include/asm/page.h
/* * PAGE_OFFSET -- the first address of the first page of memory. * When not using MMU this corresponds to the first free page in * physical memory (aligned on a page boundary). */ #define PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET, UL)
freedom-u-sdk/work/linux/include/generated/autoconf.h
#define CONFIG_PAGE_OFFSET 0xffffffe000000000
freedom-u-sdk/linux/arch/riscv/kernel/setup.c
unsigned long va_pa_offset; EXPORT_SYMBOL(va_pa_offset); unsigned long pfn_base; EXPORT_SYMBOL(pfn_base);
freedom-u-sdk/linux/include/linux/pfn.h
#define PFN_ALIGN(x) (((unsigned long)(x) + (PAGE_SIZE - 1)) & PAGE_MASK) #define PFN_UP(x) (((x) + PAGE_SIZE-1) >> PAGE_SHIFT) #define PFN_DOWN(x) ((x) >> PAGE_SHIFT) #define PFN_PHYS(x) ((phys_addr_t)(x) << PAGE_SHIFT) #define PHYS_PFN(x) ((unsigned long)((x) >> PAGE_SHIFT))
次にrelocate
にジャンプする。relocate
はいよいよ物理アドレスモードから仮想アドレスへのモードチェンジを行う。
relocate: /* Relocate return address */ li a1, PAGE_OFFSET la a0, _start sub a1, a1, a0 add ra, ra, a1
仮想アドレスのベースとなるアドレスから、_start
と物理アドレスとの差分を計算し、これを現在のリターンアドレス(つまりcall relocate
の次のアドレス)に対してこの差分を加算する。
次はstvec
の設定。スーパバイザモードでのトラップベクタアドレスの設定を行う。ここではrelocate
中にトラップが発生したときに備えてrelocate
内部のラベルにトラップアドレスを設定しておく。
/* Point stvec to virtual address of intruction after satp write */ la a0, 1f add a0, a0, a1 csrw stvec, a0
次にsatp
に設定すべき仮想アドレスのベースアドレスを計算する。satp
は仮想アドレスから物理アドレスを計算するためのベースとなるアドレスで、swapper_pg_dir
配列の先頭に相当し、これに対してページアドレス分右側にシフトしておく。これはSATPレジスタの仕様に従っているだけだ。
/* Compute satp for kernel page tables, but don't load it yet */ la a2, swapper_pg_dir srl a2, a2, PAGE_SHIFT li a1, SATP_MODE or a2, a2, a1
そしてSATPのモード設定を行っている。MODEレジスタフィールドに対する設定は0x8000000000000000
で、Sv39モードへの変更を意味する。
まずは、一段目のページテーブル変更を行う。trampoline_pg_dir
をSATPレジスタに設定する。さらにSv39モードに設定する。
/* * Load trampoline page directory, which will cause us to trap to * stvec if VA != PA, or simply fall through if VA == PA */ la a0, trampoline_pg_dir srl a0, a0, PAGE_SHIFT or a0, a0, a1 sfence.vma csrw sptbr, a0
これにより、ここから先はSv39モードで動作するようになる。Swimmer-RISCVで詳細な変換動作を見てみよう。
4961004:S:MBar:0000000080200094:P000080200094: 8131:c.srli x10,0x0c :r10=>000000008021a000 r10<=000000000008021a 4961005:S:MBar:0000000080200096:P000080200096: 8d4d:c.or x10,x11 :r10=>000000000008021a r11=>8000000000000000 r10<=800000000008021a 4961006:S:MBar:0000000080200098:P000080200098:12000073:sfence.vma x00,x00 : 4961007:S:MBar:000000008020009c:P00008020009c:18051073:csrrw x00,0x180,x10 :satp=>0000000000000000 r10=>800000000008021a satp<=800000000008021a <Info: VAddr = 0x00000000802000a0 PTEAddr = 0x000000008021a010 : PPTE = 0x0000000000000000> <Page Table Error : 000000008021a010 = 0000000000000000 is not valid Page Table. Generate Exception> <Info: VAddr = 0x00000000802000a0 PTEAddr = 0x000000008021a010 : PPTE = 0x0000000000000000> <Page Table Error : 000000008021a010 = 0000000000000000 is not valid Page Table. Generate Exception> <Info: GenerateException Code=12, TVAL=00000000802000a0 PC=00000000802000a0,0000000000000010> <Info: Exception. ChangeMode from SuprevisorMode to SuprevisorMode> <Info: Set Program Counter = 0xffffffe0000000a0> <Info: MemResult::MemTlbError occured.> <Info: VAddr = 0xffffffe0000000a0 PTEAddr = 0x000000008021ac00 : PPTE = 0x0000000020086401> <Info: VAddr = 0xffffffe0000000a0 PTEAddr = 0x0000000080219000 : PPTE = 0x00000000200800cf> <Info: TLB[0] <= 0x000ffffffe000000(0x0000000080200000)> <Info: Converted Virtual Address is = 0x00000000802000a0> 4961008:S:Sv39:ffffffe0000000a0:P0000802000a0:00000517:auipc x10,0x00000 :r10<=ffffffe0000000a0 4961009:S:Sv39:ffffffe0000000a4:P0000802000a4:06050513:addi x10,x10,0x060 :r10=>ffffffe0000000a0 r10<=ffffffe000000100
Sv39モードに移ると、stvec
をsecondary_park
に設定して、スーパバイザモードのトラップベクタを設定する。
/* Set trap vector to spin forever to help debug */ la a0, .Lsecondary_park csrw stvec, a0
最後に、SATPをswapper_pg_dir
に設定して元に戻る。
/* Switch to kernel page tables */ csrw sptbr, a2 ret
4961008:S:Sv39:ffffffe0000000a0:P0000802000a0:00000517:auipc x10,0x00000 :r10<=ffffffe0000000a0 4961009:S:Sv39:ffffffe0000000a4:P0000802000a4:06050513:addi x10,x10,0x060 :r10=>ffffffe0000000a0 r10<=ffffffe000000100 4961010:S:Sv39:ffffffe0000000a8:P0000802000a8:10551073:csrrw x00,0x105,x10 :stvec=>ffffffe0000000a0 r10=>ffffffe000000100 stvec<=ffffffe000000100 4961011:S:Sv39:ffffffe0000000ac:P0000802000ac:00d33197:auipc x03,0x00d33 :r03<=ffffffe000d330ac 4961012:S:Sv39:ffffffe0000000b0:P0000802000b0:83418193:addi x03,x03,0x834 :r03=>ffffffe000d330ac r03<=ffffffe000d328e0 4961013:S:Sv39:ffffffe0000000b4:P0000802000b4:18061073:csrrw x00,0x180,x12 :satp=>800000000008021a r12=>8000000000080fb5 satp<=8000000000080fb5 <Info: VAddr = 0xffffffe0000000b8 PTEAddr = 0x0000000080fb5c00 : PPTE = 0x00000000203cd401> <Info: VAddr = 0xffffffe0000000b8 PTEAddr = 0x0000000080f35000 : PPTE = 0x00000000200800cf> <Info: TLB[0] <= 0x000ffffffe000000(0x0000000080200000)> <Info: Converted Virtual Address is = 0x00000000802000b8> 4961014:S:Sv39:ffffffe0000000b8:P0000802000b8: 8082:c.jr x01 :r01=>ffffffe000000032 pc<=ffffffe000000032