AArch64のMMUを使ってみました。
MMUを使ってみたといっても、論理アドレスと物理アドレスを1対1対応させただけです。
動作確認はQEMU ver 2.12 で行いました。
MMUの機能
AArch64のMMUは二つの機能があります。
- 論理アドレスから物理アドレスに変換
- メモリ属性の設定
アドレス変換
ARMのドキュメントにある、アドレス変換のイメージイラストです。

あとで説明を書く。
キャッシュポリシーとメモリ属性
今回使うキャッシュポリシーは3種類です。
- デバイスメモリ、キャッシュしない
- ノーマルメモリ、キャッシュしない
- ノーマルメモリ、リードキャッシュ有効、ライトキャッシュ有効、ライトバックキャッシュ有効
今回明示的に設定するメモリ属性は3つです。
- 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ビットです。
エントリのフォーマットは3種類あります。

今回は、1段目はTable descriptorを使い、2段目は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 : 未使用
2段目のページテーブル
使うのは513エントリです。
1エントリ当たり2MBの範囲を変換する。
- 0-440 : RAM
- 441-502 : GPU用RAM
- 503-511 : ペリフェラル
- 512 : Mailbox
- 513-1023 : 未使用
MMU設定手順
- MAIR_EL1 にキャッシュポリシーを3つ設定
- 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