ARM AArch64のMMU

AArch64のMMUを使ってみました。

MMUを使ってみたといっても、論理アドレスと物理アドレスを１対１対応させただけです。

動作確認はQEMU ver 2.12 で行いました。

# MMUの機能
AArch64のMMUは二つの機能があります。

 * 論理アドレスから物理アドレスに変換
 * メモリ属性の設定

# アドレス変換

ARMのドキュメントにある、アドレス変換のイメージイラストです。

<img width="595" alt="スクリーンショット 2018-06-26 22.01.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/42120/0989dfc2-76f6-a254-2355-2c9cbf305d30.png">


あとで説明を書く。

# キャッシュポリシーとメモリ属性

今回使うキャッシュポリシーは３種類です。

* デバイスメモリ、キャッシュしない
* ノーマルメモリ、キャッシュしない
* ノーマルメモリ、リードキャッシュ有効、ライトキャッシュ有効、ライトバックキャッシュ有効

今回明示的に設定するメモリ属性は３つです。

* AF : Accesss Flag : アクセス可能なら1
* NS : Security bit: Non Secureなら1
* SH : Shareable attribute : よくわかっていないデータキャッシュの共有についてのなにか。

# MMU関連レジスタ

MMUの設定に関連するレジスタの概要です。とりあえずEL1用のものだけです。

 * MAIR_EL1 : Memory Attribute Indirection Register (EL1) キャッシュポリシーの設定。ここで設定した値のインデックスをページテーブルエントリで指定する。
 * TCR_EL1 : Translation Control Register (EL1) MMUの設定
 * TTBR0_EL1　 : Translation Table Base Register 0 (EL1) 変換テーブルアドレス
 * TTBR1_EL1 : Translation Table Base Register 1 (EL1) 変換テーブルアドレス 使わない
 * SCTLR_EL1 : System Control Register (EL1) システムの設定、MMUの有効化はこのレジスタ

詳細は Architecture Reference Manual を見てください。

# ページテーブルエントリ

ページテーブルのエントリーの長さは64ビットです。

エントリのフォーマットは３種類あります。

<img width="622" alt="スクリーンショット 2018-06-26 22.01.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/42120/1f1f35d5-8907-d4a2-6ef0-25f034d6659c.png">

今回は、1段目はTable descriptorを使い、２段目はTable entryを使います。


# ページテーブル設定例
Raspberry Pi 3のMMUを、論理アドレスと仮想アドレスを1対1に設定してみました。

## Raspberry Pi 3のメモリマップ
RAMは1024MB搭載されており、そのうち128MBをGPU(VideoCore4)に割り当て、残りを880MBをARMに割り当てる設定です。

| 物理アドレス | 用途 | 属性 |
|:-:|:-:|:-:|
|0000_0000 - 36FF_FFFF | RAM  | ノーマルメモリ、リードキャッシュ有効、ライトキャッシュ有効、ライトバックキャッシュ有効|
|3700_0000 - 3EFF_FFFF | GPU用RAM | ノーマルメモリ、キャッシュしない   |
|3F00_0000 - 3FFF_FFFF | Peripheral  | デバイスメモリ、キャッシュしない  |
|4000_0000 - 41FF_FFFF | Mailbox |デバイスメモリ、キャッシュしない |
|4200_0000 - FFFF_FFFF | 使わない | 未使用 |

## ページテーブルの配置
ページテーブルは4KBアラインで配置する必要があるみたいです。

## 1段目のページテーブル
使うのは2エントリです。
1エントリ当たり1024GBの範囲を変換する。

 * 0 : 0-1GBの範囲だったら、0-511のL2 Page Tableを適用
 * 1 : 1-2GBの範囲だったら、512-1023のL2 Page Tableを適用
 * 2-511 : 未使用

## ２段目のページテーブル
使うのは513エントリです。
１エントリ当たり2MBの範囲を変換する。

 * 0-440 : RAM
 * 441-502 : GPU用RAM
 * 503-511 : ペリフェラル
 * 512 : Mailbox
 * 513-1023 : 未使用

# MMU設定手順
* MAIR_EL1 にキャッシュポリシーを３つ設定
* L2 Page Table に値を設定
* L1 Page Table に値を設定
* TTBR0_EL1 に L1 Page Tableのアドレスを設定
* TCR_EL1 を設定
* SCTLR_EL1 を設定しMMUとキャッシュを有効

# コード
実際のコードです。

```
static __attribute__((aligned(4096))) uint64_t l1_page_table[512];
static __attribute__((aligned(4096))) uint64_t l2_page_table[1024];

void mmu_init(void)
{
    uint32_t i;
    uint64_t r;

    /* clear pipeline */
    asm volatile("dsb sy");

    /* cache policy */
    r = ((0x00ul << (2 * 8)) |   /* device memory */
         (0x44ul << (1 * 8)) |   /* normal memory and no data cache */
         (0xfful << (0 * 8)));   /* normal memory and data cache */
    asm volatile ("msr mair_el1, %0" : : "r" (r));

    /* l2 page table */
    /* RAM        : AF=1, SH=3, Indx=0, EntryType=1 */
    for (i = 0; i < 441; i++) {
        l2_page_table[i] = (uintptr_t) i << (21 - 12 + 12) | 1 << 10 | 3 << 9 | 0 << 2 | 1;
    }
    /* GPU RAM    : AF=1, Indx=1, EntryType=1 */
    for (i = 441; i < 503; i++) {
        l2_page_table[i] = (uintptr_t) i << (21 - 12 + 12) | 1 << 10          | 1 << 2 | 1;
    }
    /* peripheral : AF=1, Indx=2, EntryType=1 */
    for (i = 503; i < 512; i++) {
        l2_page_table[i] = (uintptr_t) i << (21 - 12 + 12) | 1 << 10          | 2 << 2 | 1;
    }
    /* mailbox    : AF=1, Indx=2, EntryType=1 */
    l2_page_table[512] = (uintptr_t) 512 << (21 - 12 + 12) | 1 << 10          | 2 << 2 | 1;
    for (i = 513; i < 1024; i++) {
        l2_page_table[i] = 0;
    }

    /* l1 page table */
    l1_page_table[0] = ((uintptr_t) &l2_page_table[0] >> 12 ) << 12 | 1<<5 | 3;
    l1_page_table[1] = ((uintptr_t) &l2_page_table[512] >> 12 ) << 12 | 1<<5 | 3;
    for (i = 2; i < 512; i++) {
        l1_page_table[i] = 0;
    }

    /* ttbr */
    asm volatile ("msr ttbr0_el1, %0" : : "r" ((uintptr_t)&l1_page_table[0]));
    asm volatile ("isb");

    /* tcr */
    r = (0b00LL << 37) | // TBI=0, no tagging
        (0b000LL << 32)| // IPS= 32 bit ... 000 = 32bit, 001 = 36bit, 010 = 40bit
        (0b10LL << 30) | // TG1=4k ... options are 10=4KB, 01=16KB, 11=64KB ... take care differs from TG0
        (0b11LL << 28) | // SH1=3 inner ... options 00 = Non-shareable, 01 = INVALID, 10 = Outer Shareable, 11 = Inner Shareable
        (0b01LL << 26) | // ORGN1=1 write back .. options 00 = Non-cacheable, 01 = Write back cacheable, 10 = Write thru cacheable, 11 = Write Back Non-cacheable
        (0b01LL << 24) | // IRGN1=1 write back .. options 00 = Non-cacheable, 01 = Write back cacheable, 10 = Write thru cacheable, 11 = Write Back Non-cacheable
        (0b0LL  << 23) | // EPD1 ... Translation table walk disable for translations using TTBR1_EL1  0 = walk, 1 = generate fault
        (25LL   << 16) | // T1SZ=25 (512G) ... The region size is 2 POWER (64-T1SZ) bytes
        (0b00LL << 14) | // TG0=4k  ... options are 00=4KB, 01=64KB, 10=16KB,  ... take care differs from TG1
        (0b11LL << 12) | // SH0=3 inner ... .. options 00 = Non-shareable, 01 = INVALID, 10 = Outer Shareable, 11 = Inner Shareable
        (0b01LL << 10) | // ORGN0=1 write back .. options 00 = Non-cacheable, 01 = Write back cacheable, 10 = Write thru cacheable, 11 = Write Back Non-cacheable
        (0b01LL << 8)  | // IRGN0=1 write back .. options 00 = Non-cacheable, 01 = Write back cacheable, 10 = Write thru cacheable, 11 = Write Back Non-cacheable
        (0b0LL  << 7)  | // EPD0  ... Translation table walk disable for translations using TTBR0_EL1  0 = walk, 1 = generate fault
        (25LL   << 0);   // T0SZ=25 (512G)  ... The region size is 2 POWER (64-T0SZ) bytes
    asm volatile ("msr tcr_el1, %0" : : "r" (r));

    /* sctlr */
    asm volatile ("isb");
    asm volatile ("mrs %0, sctlr_el1" : "=r" (r));
    r |= 0xC00800;   // set mandatory reserved bits
    r |= (1<<12) |   // I, Instruction cache enable. 
         (1<<4)  |   // SA0, Stack Alignment Check Enable for EL0
         (1<<3)  |   // SA, Stack Alignment Check Enable
         (1<<2)  |   // C, Data cache enable.
         (1<<1)  |   // A, Alignment check enable bit
         (1<<0);     // set M, enable MMU
    asm volatile ("msr sctlr_el1, %0" : : "r" (r));
    asm volatile("isb");

```

# Todo
* QEMUでMMUが有効になっているか確認する。(MMUのイベントをトレースすれば良いのか？)
* QEMUでキャッシュの有無で実行速度が変わるのか


# 参考にした情報
* https://github.com/LdB-ECM/Raspberry-Pi/tree/master/10_virtualmemory
* ARMv8-A Address Translation
* Bare-metal Boot Code for ARMv8-A Processors