Skip to main content

atmos/kernel/
mmu.rs

1// mmu.rs - MMU Configuration for AArch64 with 4KB Pages (Identity Mapping)
2#![allow(dead_code)]
3extern crate alloc;
4use alloc::boxed::Box;
5
6// ページテーブル用メモリの定義 (4KBアラインメント必須)
7#[repr(C, align(4096))]
8struct PageTables {
9    l1_table: [u64; 512],   // L1テーブル (1エントリ1GBをカバー。512エントリで512GB)
10    l2_table_0: [u64; 512], // L2テーブル 0
11    l2_table_1: [u64; 512], // L2テーブル 1
12}
13
14static mut TABLES: PageTables = PageTables {
15    l1_table: [0; 512],
16    l2_table_0: [0; 512],
17    l2_table_1: [0; 512],
18};
19
20/// GPU / フレームバッファ / MMIO が始まる物理アドレス。
21///
22/// ここ以上は Device(uncached) としてマップされる。
23/// 通常メモリ(キャッシュ有効)として使ってよいのは**この値未満**だけ。
24///
25/// 【2026-08-25】この境界は 3 箇所に直書きされていた
26/// (`mmu.rs` のマップ判定、`fs/mod.rs` の RAMDisk 上限、
27///   `draw.rs` の領域表示)。RAMDisk がヒープ外の物理 RAM を
28/// 直接使うようになり、**この値を間違えるとフレームバッファを壊す**。
29/// 権威はマップを決めている此処に置き、他はここを参照する。
30pub const DEVICE_REGION_START: usize = 0x3800_0000;
31
32/// 恒等マップとMMU保護を有効化します
33pub fn init() {
34    crate::info!("[MMU] Initializing MMU (4KB Page Size, Identity Mapping)...");
35
36    unsafe {
37        // L1, L2 テーブルのクリア
38        for i in 0..512 {
39            TABLES.l1_table[i] = 0;
40            TABLES.l2_table_0[i] = 0;
41            TABLES.l2_table_1[i] = 0;
42        }
43
44        // 1. L1テーブルにL2テーブルのアドレスを登録
45        let l2_phys_addr_0 = &TABLES.l2_table_0 as *const u64 as u64;
46        let l2_phys_addr_1 = &TABLES.l2_table_1 as *const u64 as u64;
47        TABLES.l1_table[0] = (l2_phys_addr_0 & 0x0000_FFFF_FFFF_F000) | 0b11;
48        TABLES.l1_table[1] = (l2_phys_addr_1 & 0x0000_FFFF_FFFF_F000) | 0b11;
49
50        // 2. L2テーブルの構築 (2MBブロックマッピングによる 2GB 恒等マップ)
51        for i in 0..1024 {
52            let phys_block_addr = (i as u64) * 0x0020_0000;
53
54            let mut desc = phys_block_addr & 0x0000_FFFF_FFFF_F000;
55            desc |= 1 << 10; // Access Flag (AF = 1)
56            desc |= 3 << 8; // Shareability = Inner Shareable (SH = 0b11)
57            desc |= 0 << 6; // AP = Read/Write (AP[2:1] = 0b00) - EL1からはRW可能、EL0は設定次第
58
59            // gpu_mem=128 の RPi3 では GPU/フレームバッファ領域は 0x3800_0000〜0x3FFF_FFFF。
60            // フレームバッファがこの範囲に確保され得るため、0x3800_0000 以上を Device(uncached)
61            // にしないと CPU のフレームバッファ書き込みが GPU と非コヒーレントになり横線ノイズが出る。
62            // (ARM RAM は 0x0000_0000〜0x37FF_FFFF。カーネルヒープもこの範囲内のため安全)
63            if phys_block_addr >= DEVICE_REGION_START as u64 {
64                // MMIO/VRAM/ローカル割り込み領域
65                desc |= 1 << 2; // AttrIndex = 1
66                desc |= 1 << 54; // Execute-Never (UXN = 1)
67                desc |= 1 << 53; // Privileged Execute-Never (PXN = 1)
68                desc |= 0b01; // Type = Block Entry (0b01)
69            } else {
70                // 通常のRAM領域
71                desc |= 0 << 2; // AttrIndex = 0
72                desc |= 0b01; // Type = Block Entry (0b01)
73            }
74
75            if i < 512 {
76                TABLES.l2_table_0[i] = desc;
77            } else {
78                TABLES.l2_table_1[i - 512] = desc;
79            }
80        }
81
82        // 3. MAIR_EL1 (Memory Attribute Indirection Register) の設定
83        // Attr 0: 0xFF (Normal Memory: Write-Back, Non-transient, Read/Write allocate)
84        // Attr 1: 0x04 (Device nGnRE)
85        let mair: u64 = 0xFF | (0x04 << 8);
86        core::arch::asm!("msr mair_el1, {}", in(reg) mair);
87
88        // 4. TCR_EL1 (Translation Control Register) の設定
89        // TG0 = 0b00 (4KB Page Size)
90        // ORGN0 = 0b01, IRGN0 = 0b01 (Inner/Outer Write-Back Cacheable)
91        // SH0 = 0b11 (Inner Shareable)
92        // T0SZ = 25 (仮想アドレス空間を39ビット幅に制限: 64 - 39 = 25. 恒等マップの1GBはL1開始にすることで正しく処理されます)
93        // IPS = 0b001 (物理アドレス幅 36ビット)
94        let tcr: u64 = (3 << 12)  | // SH0 = Inner Shareable (0b11)
95                       (1 << 10)  | // ORGN0 = Outer WB WA
96                       (1 << 8)   | // IRGN0 = Inner WB WA
97                       25  | // T0SZ = 25
98                       (1 << 32); // IPS = 36-bit PA
99        core::arch::asm!("msr tcr_el1, {}", in(reg) tcr);
100
101        // 5. TTBR0_EL1 (Translation Table Base Register 0) の設定
102        let l1_phys_addr = &TABLES.l1_table as *const u64 as u64;
103        core::arch::asm!("msr ttbr0_el1, {}", in(reg) l1_phys_addr);
104
105        // TLB 全体を無効化 (Invalidate TLB)
106        core::arch::asm!("tlbi vmalle1is", "dsb sy", "isb", options(nostack));
107
108        // 6. MMU とキャッシュの有効化 (SCTLR_EL1)
109        // 最初は M = 1 (MMU Enable) と I = 1 (Instruction Cache Enable) のみ有効化、
110        // C = 0 (Data/Unified Cache Disable) は無効にしておくことで、ページテーブル読み込みでのキャッシュ不整合を防ぐ。
111        let mut sctlr: u64;
112        core::arch::asm!("mrs {}, sctlr_el1", out(reg) sctlr);
113        sctlr |= 1 << 0; // M (MMU)
114        sctlr &= !(1 << 2); // C (Cache)
115        sctlr |= 1 << 12; // I (Instruction Cache)
116        core::arch::asm!("msr sctlr_el1, {}", in(reg) sctlr);
117
118        // 命令同期バリアとデータバリアで設定をフラッシュ
119        core::arch::asm!("isb", "dsb sy", options(nostack));
120
121        // MMU有効化(かつデータキャッシュ無効)の状態で、ページテーブル全体のキャッシュフラッシュと無効化を確実に実行する
122        // (MMU有効時なので、仮想アドレス指定の dc civac が正しく機能する)
123        clean_dcache_range(
124            &TABLES as *const PageTables as usize,
125            core::mem::size_of::<PageTables>(),
126        );
127
128        // データキャッシュを有効化
129        core::arch::asm!("mrs {}, sctlr_el1", out(reg) sctlr);
130        sctlr |= 1 << 2; // C (Cache)
131        core::arch::asm!("msr sctlr_el1, {}", in(reg) sctlr);
132
133        // 再度フラッシュ
134        core::arch::asm!("isb", "dsb sy", options(nostack));
135    }
136
137    crate::info!("[MMU] MMU and Caching successfully enabled!");
138}
139
140/// セカンダリコア向けのMMU有効化
141pub fn init_secondary() {
142    unsafe {
143        let mair: u64 = 0xFF | (0x04 << 8);
144        core::arch::asm!("msr mair_el1, {}", in(reg) mair);
145
146        // TG0 = 4KB, T0SZ = 25
147        let tcr: u64 = (3 << 12) | (1 << 10) | (1 << 8) | 25 | (1 << 32);
148        core::arch::asm!("msr tcr_el1, {}", in(reg) tcr);
149
150        let l1_phys_addr = &TABLES.l1_table as *const u64 as u64;
151        core::arch::asm!("msr ttbr0_el1, {}", in(reg) l1_phys_addr);
152
153        let mut sctlr: u64;
154        core::arch::asm!("mrs {}, sctlr_el1", out(reg) sctlr);
155        sctlr |= 1 << 0;
156        sctlr &= !(1 << 2); // C (Cache)
157        sctlr |= 1 << 12;
158        core::arch::asm!("msr sctlr_el1, {}", in(reg) sctlr);
159
160        core::arch::asm!("isb", "dsb sy", options(nostack));
161
162        // データキャッシュを有効化
163        core::arch::asm!("mrs {}, sctlr_el1", out(reg) sctlr);
164        sctlr |= 1 << 2;
165        core::arch::asm!("msr sctlr_el1, {}", in(reg) sctlr);
166
167        core::arch::asm!("isb", "dsb sy", options(nostack));
168    }
169}
170
171pub fn get_kernel_page_table() -> u64 {
172    unsafe { core::ptr::addr_of!(TABLES.l1_table) as *const u64 as u64 }
173}
174
175pub fn create_process_page_table() -> u64 {
176    get_kernel_page_table()
177}
178
179pub fn create_user_page_table() -> u64 {
180    create_page_table_internal(false)
181}
182
183fn create_page_table_internal(is_kernel: bool) -> u64 {
184    unsafe {
185        let mut pt = Box::new(PageTables {
186            l1_table: [0; 512],
187            l2_table_0: [0; 512],
188            l2_table_1: [0; 512],
189        });
190
191        // 既存のカーネルマップ (TABLES) をコピー
192        for i in 0..512 {
193            pt.l1_table[i] = TABLES.l1_table[i];
194            pt.l2_table_0[i] = TABLES.l2_table_0[i];
195            pt.l2_table_1[i] = TABLES.l2_table_1[i];
196        }
197
198        // ユーザープロセス用: RAM領域はEL0アクセス許可、MMIO領域はEL0ブロック
199        if !is_kernel {
200            for i in 0..1024 {
201                let phys_block_addr = (i as u64) * 0x0020_0000;
202                let table = if i < 512 {
203                    &mut pt.l2_table_0
204                } else {
205                    &mut pt.l2_table_1
206                };
207                let idx = if i < 512 { i } else { i - 512 };
208
209                if phys_block_addr >= DEVICE_REGION_START as u64 {
210                    // MMIO/VRAM/ローカル割り込み領域: EL0からのアクセスを完全にブロック
211                    // gpu_mem=128 の GPU/フレームバッファ領域(0x3800_0000〜)を Device(uncached) にし、
212                    // CPU 書き込みと GPU スキャンアウトのコヒーレンシを保つ(横線ノイズ対策)。
213                    // AP[2:1] = 0b00 (EL1のみ RW, EL0はアクセス不可 → Data Abort 発生)
214                    let mut desc = phys_block_addr & 0x0000_FFFF_FFFF_F000;
215                    desc |= 1 << 10; // AF
216                    desc |= 3 << 8; // SH = Inner Shareable
217                    desc |= 0 << 6; // AP[2:1] = 0b00 (EL1 only)
218                    desc |= 1 << 2; // AttrIndex = 1 (Device memory)
219                    desc |= 1 << 54; // UXN
220                    desc |= 1 << 53; // PXN
221                    desc |= 0b01; // Block Entry
222                    table[idx] = desc;
223                } else if phys_block_addr < 0x0060_0000 {
224                    // カーネルコード領域(block 0,1 = 0..4MB、.user_text(0x400000)より下):
225                    // カーネルコード・カーネルスタック・例外ベクタテーブル。
226                    // 特権(EL1)のみアクセス可能にし、PANやPXNによる例外(ダブルフォルト)を回避。
227                    // ※ kernel .text が 2MB を越えて block 1 に伸びたため、block 0 のみでは
228                    //   block 1 のカーネルコードが EL0 アクセス可ページのままとなり EL1 実行で
229                    //   命令アボートを起こしていた(JSエンジン追加でバイナリ肥大時に顕在化)。
230                    let mut desc = phys_block_addr & 0x0000_FFFF_FFFF_F000;
231                    desc |= 1 << 10; // AF
232                    desc |= 3 << 8; // SH = Inner Shareable
233                    desc |= 0 << 6; // AP[2:1] = 0b00 (EL1 only)
234                    desc |= 0 << 2; // AttrIndex = 0 (Normal Memory)
235                    desc |= 0b01; // Block Entry
236                    table[idx] = desc;
237                } else {
238                    // RAM領域(インデックス 1 以降): EL0/EL1 ともに RW 可能 (AP[2:1] = 0b01)
239                    // ユーザーのコード、スタック、ヒープがRAM上にあるため必須
240                    let mut desc = phys_block_addr & 0x0000_FFFF_FFFF_F000;
241                    desc |= 1 << 10; // AF
242                    desc |= 3 << 8; // SH = Inner Shareable
243                    desc |= 1 << 6; // AP[2:1] = 0b01 (EL0/EL1 RW)
244                    desc |= 0 << 2; // AttrIndex = 0 (Normal Memory)
245                    desc |= 0b01; // Block Entry
246                    table[idx] = desc;
247                }
248            }
249        }
250
251        let l2_phys_addr_0 = pt.l2_table_0.as_ptr() as u64;
252        let l2_phys_addr_1 = pt.l2_table_1.as_ptr() as u64;
253        pt.l1_table[0] = (l2_phys_addr_0 & 0x0000_FFFF_FFFF_F000) | 0b11;
254        pt.l1_table[1] = (l2_phys_addr_1 & 0x0000_FFFF_FFFF_F000) | 0b11;
255
256        let raw_ptr = Box::into_raw(pt);
257
258        // ページテーブル全体をフラッシュ
259        clean_dcache_range(raw_ptr as usize, core::mem::size_of::<PageTables>());
260
261        raw_ptr as u64
262    }
263}
264
265pub fn map_page(pt_addr: u64, virt_addr: u64, phys_addr: u64) {
266    unsafe {
267        let pt = &mut *(pt_addr as *mut PageTables);
268        let l2_index_global = (virt_addr / 0x0020_0000) as usize; // 2MBブロック
269
270        let mut desc = phys_addr & 0x0000_FFFF_FFFF_F000;
271        desc |= 1 << 10; // Access Flag (AF = 1)
272        desc |= 3 << 8; // Shareability = Inner Shareable (SH = 0b11)
273        desc |= 0 << 6; // AP = Read/Write (AP[2:1] = 0b00) - EL1
274        desc |= 0 << 2; // AttrIndex = 0 (Normal Memory)
275        desc |= 0b01; // Type = Block Entry (0b01)
276
277        let entry_addr;
278        if l2_index_global < 512 {
279            pt.l2_table_0[l2_index_global] = desc;
280            entry_addr = &pt.l2_table_0[l2_index_global] as *const u64 as usize;
281        } else {
282            pt.l2_table_1[l2_index_global - 512] = desc;
283            entry_addr = &pt.l2_table_1[l2_index_global - 512] as *const u64 as usize;
284        }
285
286        // 更新したL2テーブルエントリのキャッシュラインをDキャッシュから物理メモリへ確実にフラッシュする
287        clean_dcache_range(entry_addr, 8);
288    }
289}
290
291/// データキャッシュの指定された範囲を Point of Coherency (DDR 物理メモリ) まで確実にクリーン(フラッシュ)します
292pub fn clean_dcache_range(addr: usize, size: usize) {
293    let cache_line_size = 64; // Cortex-A53 の標準キャッシュラインサイズ (64バイト)
294    let start = addr & !(cache_line_size - 1);
295    let end = addr + size;
296    let mut current = start;
297
298    while current < end {
299        unsafe {
300            core::arch::asm!(
301                "dc civac, {}",
302                in(reg) current,
303                options(nostack)
304            );
305        }
306        current += cache_line_size;
307    }
308
309    unsafe {
310        core::arch::asm!("dsb sy", "isb", options(nostack));
311    }
312}
313
314pub fn invalidate_dcache_range(addr: usize, size: usize) {
315    let cache_line_size = 64; // Cortex-A53
316    let start = addr & !(cache_line_size - 1);
317    let end = addr + size;
318    let mut current = start;
319
320    while current < end {
321        unsafe {
322            core::arch::asm!(
323                "dc ivac, {}",
324                in(reg) current,
325                options(nostack)
326            );
327        }
328        current += cache_line_size;
329    }
330
331    unsafe {
332        core::arch::asm!("dsb sy", "isb", options(nostack));
333    }
334}