Rust の準備

Rustのコードを実行できるようになる前にいくつかの初期化が必要です。

.section .init.entry, "ax"
.global entry
entry:
    /*
     * Load and apply the memory management configuration, ready to
     * enable MMU and caches.
     */
    adrp x30, idmap
    msr ttbr0_el1, x30

    mov_i x30, .Lmairval
    msr mair_el1, x30

    mov_i x30, .Ltcrval
    /* Copy the supported PA range into TCR_EL1.IPS. */
    mrs x29, id_aa64mmfr0_el1
    bfi x30, x29, #32, #4

    msr tcr_el1, x30

    mov_i x30, .Lsctlrval

    /*
     * Ensure everything before this point has completed, then
     * invalidate any potentially stale local TLB entries before they
     * start being used.
     */
    isb
    tlbi vmalle1
    ic iallu
    dsb nsh
    isb

    /*
     * Configure sctlr_el1 to enable MMU and cache and don't proceed
     * until this has completed.
     */
    msr sctlr_el1, x30
    isb

    /* Disable trapping floating point access in EL1. */
    mrs x30, cpacr_el1
    orr x30, x30, #(0x3 << 20)
    msr cpacr_el1, x30
    isb

    /* Zero out the bss section. */
    adr_l x29, bss_begin
    adr_l x30, bss_end
0:  cmp x29, x30
    b.hs 1f
    stp xzr, xzr, [x29], #16
    b 0b

1:  /* Prepare the stack. */
    adr_l x30, boot_stack_end
    mov sp, x30

    /* Set up exception vector. */
    adr x30, vector_table_el1
    msr vbar_el1, x30

    /* Call into Rust code. */
    bl main

    /* Loop forever waiting for interrupts. */
2:  wfi
    b 2b
  • この初期化内容はCの場合と同じになります。プロセッサ状態を初期化して、BSSをゼロ埋めして、スタックポインタを設定します。
    • BSS(歴史的な理由によりblock starting symbolと呼ばれているもの)はオブジェクトファイルにおいてゼロ初期化される静的な変数を含む部分です。この部分はゼロによる領域の浪費を避けるためにイメージからは除外されています。コンパイラはローダがこの領域をゼロ初期化することを想定しているのです。
  • メモリの初期化方法やイメージのロード方法によってはBSSはすでにゼロ埋めされていることがありますが、ここでは念の為にゼロ埋めしています。
  • いかなるメモリのreadやwriteよりも前にMMUとキャッシュを有効化する必要があります。それをしないと:
    • アラインされていないアクセスがフォールトになります。我々はコンパイラがアラインされていないアクセスを生成しないように+strict-alignオプション を設定するaarch64-unknown-none ターゲット向けにRustコードをビルドします。そのためここでは問題にはなりませんが、一般的にはそうとは言えません。
    • もしVM上で実行していたとすると、キャッシュコヒーレンシーの問題を起こすことがあります。問題なのはVMがキャッシュを無効化したまま直接メモリにアクセスしているのに対し、ホストは同じメモリに対してキャッシュ可能なエイリアスを持ってしまうということです。ホストが仮に明示的にメモリにアクセスしないとしても、投機的なアクセスによりキャッシュフィルが起きることがあります。そうなると、ホストがキャッシュをフラッシュするかVMがキャッシュを有効化したときに、VMかホストのどちらかによる変更が失われてしまいます。(キャッシュは仮想アドレスやIPAではなく物理アドレスをキーとしてアクセスされます)
  • 単純化のために、ハードコードしたページテーブル(idmap.S参照)を利用します。このページテーブルは最初の1GiBをデバイス用に、次の1GiBをDRAM用に、次の1GiBをさらなるデバイス用に透過的にマップします。これはQEMUのメモリレイアウトに合致します。
  • 例外ベクタ(vbar_el1)も設定します。これに関しては後ほど詳しく見ます。
  • 今日の午後に扱うすべての例は例外レベル1(EL1)で実行されることを想定しています。もし、別の例外レベルで実行する必要がある場合には、entry.Sをそれに合わせて変更する必要があります。