FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

freedom-u-sdkのLinuxを立ち上げながらLinuxのブートプロセスを学ぶ(7. 物理アドレスモードから仮想アドレスモードへの変更)

私の開発した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
...
  1. 全ての割り込みを一時的に停止する。sie(Supervisor Interrupt Enable)を0に設定する。現在はスーパバイザモードに入っているので、mieではなくsieを操作する。
 /* Mask all interrupts */
    csrw sie, zero
  1. グローバルポインタの設定をする。グローバルポインタの位置は以下に設定される。
 /* 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
  1. CSRレジスタを操作してFPUを無効化する。
 /*
    * Disable FPU to detect illegal usage of
    * floating point in kernel space
    */
    li t0, SR_FS
    csrc sstatus, t0
  1. 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
  1. 仮想アドレスの設定を行う。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_offsetpfn_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レジスタの仕様に従っているだけだ。

f:id:msyksphinz:20200424235904p:plain
 /* 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モードに移ると、stvecsecondary_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