rt-threadのラズパイ2MMU設定


cortex-AのMMU勉強中ということで[rt-thread](https://github.com/RT-Thread/rt-thread)のMMU設定を見てみる。

# `rt_hw_mmu_init()`

rt-threadにおけるMMU設定の全体。

```c:bsp/raspi2/cpu/mmu.c
void rt_hw_mmu_init(void)
{
    rt_hw_cpu_dcache_disable();
    rt_hw_cpu_icache_disable();
    rt_cpu_mmu_disable();

    /* set page table */
    /* 4G 1:1 memory */
    rt_hw_mmu_setmtt(0, 0xffffffff-1, 0, RW_CB);
    /* IO memory region */
    rt_hw_mmu_setmtt(0x44000000, 0x80000000-1, 0x44000000, RW_NCNBXN);

    /*rt_hw_cpu_dump_page_table(MMUTable);*/
    rt_hw_set_domain_register(0x55555555);

    rt_cpu_tlb_set(MMUTable);

    rt_cpu_mmu_enable();

    rt_hw_cpu_icache_enable();
    rt_hw_cpu_dcache_enable();
}
```

以降で各関数について説明していく。

# `rt_hw_mmu_setmtt()`

```c:bsp/raspi2/cpu/mmu.c
/* level1 page table, each entry for 1MB memory. */
volatile static unsigned long MMUTable[4*1024] __attribute__((aligned(16*1024)));
void rt_hw_mmu_setmtt(rt_uint32_t vaddrStart,
                      rt_uint32_t vaddrEnd,
                      rt_uint32_t paddrStart,
                      rt_uint32_t attr)
{
    volatile rt_uint32_t *pTT;
    volatile int i, nSec;
    pTT  = (rt_uint32_t *)MMUTable + (vaddrStart >> 20);
    nSec = (vaddrEnd >> 20) - (vaddrStart >> 20);
    for(i = 0; i <= nSec; i++)
    {
        *pTT = attr | (((paddrStart >> 20) + i) << 20);
        pTT++;
    }
}
```

メモリ変換テーブルの設定を行っている。

short-descriptionでの１段めのテーブルは立てたビットにより名称と使用方法が変わるが、rt-threadではSectionと呼ばれる1段目テーブルのみを使用する1MB単位でのメモリ変換を定義している。

![1st-table-section.png](https://qiita-image-store.s3.amazonaws.com/0/39647/ee3fe853-6e49-1ea2-30d5-31f85fc764d5.png)

なお１段めテーブルのBit[1]が1かつBit[18]が0でSectionとなる。

このコードでは仮想アドレス`vaddrStart`から`vaddrEnd`までのアドレスを物理アドレス`paddrStart`とリニアに対応付けている。

## 属性について

引数に指定している属性は以下の通り。

```c:bsp/raspi2/cpu.mmu.c
#define DESC_SEC       (0x2)
#define CB             (3<<2)  //cache_on, write_back
#define CNB            (2<<2)  //cache_on, write_through
#define NCB            (1<<2)  //cache_off,WR_BUF on
#define NCNB           (0<<2)  //cache_off,WR_BUF off
#define AP_RW          (3<<10) //supervisor=RW, user=RW
#define AP_RO          (2<<10) //supervisor=RW, user=RO
#define XN             (1<<4)  // eXecute Never

#define DOMAIN0        (0x0<<5)
#define DOMAIN1        (0x1<<5)

/* Read/Write, cache, write back */
#define RW_CB          (AP_RW|DOMAIN0|CB|DESC_SEC)
/* Read/Write, cache, write through */
#define RW_CNB         (AP_RW|DOMAIN0|CNB|DESC_SEC)
/* Read/Write without cache and write buffer */
#define RW_NCNB        (AP_RW|DOMAIN0|NCNB|DESC_SEC)
/* Read/Write without cache and write buffer, no execute */
#define RW_NCNBXN      (AP_RW|DOMAIN0|NCNB|DESC_SEC|XN)
/* Read/Write without cache and write buffer */
#define RW_FAULT       (AP_RW|DOMAIN1|NCNB|DESC_SEC)
```

rt-threadではIOメモリである0x4400_0000から0x8000_0000-1までを`RW_NCNBXN`つまり、「実行不可、RW可、ドメイン0，キャッシュ/バッファOFF」とし（`AP_RO`じゃなくて良いのか？とも思うがrt-threadってそもそもユーザーモード使ってないし…）、それ以外を`RW_CB`つまり「実行可、RW可、ドメイン0、キャッシュON」としている。

## ドメインについて

B3.7.3参照

short-descriptionでは16種類のドメインが設定でき、それぞれについて「No access」、「Clients」、「Managers」の設定が可能。
rt-threadでは全てのドメインが「Clients」に設定されている。
「Clients」ではメモリアクセスの権限がチェックされ、権限がない場合はパーミッションフォルトが発生する。

# `rt_cpu_tlb_set()`

```:bsp/raspi2/cpu/cp15_gcc.S
.globl rt_cpu_tlb_set
rt_cpu_tlb_set:
    mcr     p15, #0, r0, c2, c0, #0
    dmb
    bx      lr
```

TTBR0に対してメモリ変換テーブルの先頭アドレスの設定。

TTBR0のフォーマットは以下の通り

![32-bit-TTBR0.png](https://qiita-image-store.s3.amazonaws.com/0/39647/afcf8a98-fccf-b303-17bc-22b603fca595.png)


xは14-TTBCR.N(後述)。ここではTTBCR.N=0なのでx=14。
[31:x]はメモリ変換テーブルのアドレスをxビットシフトしたものになっている。
その他のビットは主にメモリ共有関連の設定になっているがあまり良くわかってないので要追記。

x=14なのでメモリ変換テーブルは1MB境界になっている必要がある。`rt_cpu_tlb_set(MMUTable)`という形で呼び出されるが,`MMUTable`は`aligned(16*1024 /*0x4000*/)`なので1MB境界となっている。

# TTBCRについて

rt-threadではTTBCRの設定は行っていないため、デフォルト値の0になっていると思われる。

最上位ビットEAEが0の場合はTTBCRは以下のshort-descriptor形式をとる。

![short-descriptor_TTBCR.png](https://qiita-image-store.s3.amazonaws.com/0/39647/91106ce5-a39b-00d8-85ee-cc52dc827dc7.png)


Nが0にになっているので、rt-threadではTTBR0しか使わないことになる。

# `rt_hw_set_domain_regiter()`

```c:bsp/raspi2/cpu/mmu.c
unsigned long rt_hw_set_domain_register(unsigned long domain_val)
{
    unsigned long old_domain;

    asm volatile ("mrc p15, 0, %0, c3, c0\n" : "=r" (old_domain));
    asm volatile ("mcr p15, 0, %0, c3, c0\n" : :"r" (domain_val) : "memory");

    return old_domain;
}
```

rt-threadでは16全てのドメインにClients(=0x01)を設定するため、
0x5555_5555を設定している(0x5=0b0101)

# `rt_cpu_mmu_enable()`

```:bsp/raspi2/cpu/cp15_gcc.S
.globl rt_cpu_mmu_enable
rt_cpu_mmu_enable:
    mrc     p15, #0, r0, c1, c0, #0
    orr     r0, r0, #0x001
    mcr     p15, #0, r0, c1, c0, #0    @ set mmu enable bit
    dsb
    bx      lr
```

SCTLR.M[0]を有効にするだけ。

# 参考・引用URL

* [ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition](https://static.docs.arm.com/ddi0406/c/DDI0406C_C_arm_architecture_reference_manual.pdf)