:: limine / common / mm / vmm.c 18.5 KB raw

1
#include <stdint.h>
2
#include <stddef.h>
3
#include <mm/vmm.h>
4
#include <mm/pmm.h>
5
#include <lib/misc.h>
6
#include <lib/print.h>
7
#include <sys/cpu.h>
8
9
#define PT_SIZE ((uint64_t)0x1000)
10
11
typedef uint64_t pt_entry_t;
12
13
// Maps level indexes to the page size for that level.
14
_Static_assert(VMM_MAX_LEVEL <= 5, "6-level paging not supported");
15
static uint64_t page_sizes[5] = {
16
    0x1000,
17
    0x200000,
18
    0x40000000,
19
    0x8000000000,
20
    0x1000000000000,
21
};
22
23
static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level,
24
                                  uint64_t virt, enum page_size desired_sz,
25
                                  size_t level_idx, size_t entry);
26
27
void map_pages(pagemap_t pagemap, uint64_t virt, uint64_t phys, uint64_t flags, uint64_t count) {
28
    if (virt % 0x1000 != 0 || phys % 0x1000 != 0 || count % 0x1000 != 0) {
29
        panic(true, "vmm: Misaligned call to map_pages()");
30
    }
31
32
    for (uint64_t i = 0; i < count; ) {
33
        if (((phys + i) & (0x40000000 - 1)) == 0 && ((virt + i) & (0x40000000 - 1)) == 0 && count - i >= 0x40000000) {
34
            map_page(pagemap, virt + i, phys + i, flags, Size1GiB);
35
            i += 0x40000000;
36
            continue;
37
        }
38
        if (((phys + i) & (0x200000 - 1)) == 0 && ((virt + i) & (0x200000 - 1)) == 0 && count - i >= 0x200000) {
39
            map_page(pagemap, virt + i, phys + i, flags, Size2MiB);
40
            i += 0x200000;
41
            continue;
42
        }
43
        map_page(pagemap, virt + i, phys + i, flags, Size4KiB);
44
        i += 0x1000;
45
    }
46
}
47
48
#if defined (__x86_64__) || defined (__i386__)
49
50
#define PT_FLAG_VALID    ((uint64_t)1 << 0)
51
#define PT_FLAG_WRITE    ((uint64_t)1 << 1)
52
#define PT_FLAG_USER     ((uint64_t)1 << 2)
53
#define PT_FLAG_LARGE    ((uint64_t)1 << 7)
54
#define PT_FLAG_NX       ((uint64_t)1 << 63)
55
#define PT_PADDR_MASK    ((uint64_t)0x0000FFFFFFFFF000)
56
57
#define PT_TABLE_FLAGS   (PT_FLAG_VALID | PT_FLAG_WRITE | PT_FLAG_USER)
58
#define PT_IS_TABLE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_LARGE)) == PT_FLAG_VALID)
59
#define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_LARGE)) == (PT_FLAG_VALID | PT_FLAG_LARGE))
60
#define PT_TO_VMM_FLAGS(x) ((x) & (PT_FLAG_WRITE | PT_FLAG_NX | VMM_FLAG_FB))
61
62
#define pte_new(addr, flags)    ((pt_entry_t)(addr) | (flags))
63
#define pte_addr(pte)           ((pte) & PT_PADDR_MASK)
64
65
pagemap_t new_pagemap(int paging_mode) {
66
    pagemap_t pagemap;
67
    pagemap.levels    = paging_mode == PAGING_MODE_X86_64_5LVL ? 5 : 4;
68
    pagemap.top_level = ext_mem_alloc(PT_SIZE);
69
    return pagemap;
70
}
71
72
static bool is_1gib_page_supported(void) {
73
    // Cache the cpuid result :^)
74
    static bool CACHE_INIT = false;
75
    static bool CACHE = false;
76
77
    if (!CACHE_INIT) {
78
        // Check if 1GiB pages are supported:
79
        uint32_t eax, ebx, ecx, edx;
80
81
        CACHE = cpuid(0x80000001, 0, &eax, &ebx, &ecx, &edx) && ((edx & 1 << 26) == 1 << 26);
82
        CACHE_INIT = true;
83
84
        printv("paging: 1GiB pages are %s!\n", CACHE ? "supported" : "not supported");
85
    }
86
87
    return CACHE;
88
}
89
90
void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size pg_size) {
91
    // Calculate the indices in the various tables using the virtual address
92
    size_t pml5_entry = (virt_addr & ((uint64_t)0x1ff << 48)) >> 48;
93
    size_t pml4_entry = (virt_addr & ((uint64_t)0x1ff << 39)) >> 39;
94
    size_t pml3_entry = (virt_addr & ((uint64_t)0x1ff << 30)) >> 30;
95
    size_t pml2_entry = (virt_addr & ((uint64_t)0x1ff << 21)) >> 21;
96
    size_t pml1_entry = (virt_addr & ((uint64_t)0x1ff << 12)) >> 12;
97
98
    pt_entry_t *pml5, *pml4, *pml3, *pml2, *pml1;
99
100
    static bool pat_supported = false, pat_supported_got = false;
101
    if (!pat_supported_got) {
102
        uint32_t eax, ebx, ecx, edx;
103
        if (cpuid(1, 0, &eax, &ebx, &ecx, &edx) && (edx & (1 << 16))) {
104
            pat_supported = true;
105
        }
106
        pat_supported_got = true;
107
    }
108
109
    flags |= PT_FLAG_VALID; // Always present
110
    if ((flags & VMM_FLAG_FB) && !pat_supported) {
111
        flags &= ~(uint64_t)VMM_FLAG_FB;
112
    }
113
114
    // Paging levels
115
    switch (pagemap.levels) {
116
        case 5:
117
            pml5 = pagemap.top_level;
118
            goto level5;
119
        case 4:
120
            pml4 = pagemap.top_level;
121
            goto level4;
122
        default:
123
            __builtin_unreachable();
124
    }
125
126
level5:
127
    pml4 = get_next_level(pagemap, pml5, virt_addr, pg_size, 4, pml5_entry);
128
level4:
129
    pml3 = get_next_level(pagemap, pml4, virt_addr, pg_size, 3, pml4_entry);
130
131
    if (pg_size == Size1GiB) {
132
        // Check if 1GiB pages are available.
133
        if (is_1gib_page_supported()) {
134
            pml3[pml3_entry] = (pt_entry_t)(phys_addr | flags | PT_FLAG_LARGE);
135
        } else {
136
            // If 1GiB pages are not supported then emulate it by splitting them into
137
            // 2MiB pages.
138
            for (uint64_t i = 0; i < 0x40000000; i += 0x200000) {
139
                map_page(pagemap, virt_addr + i, phys_addr + i, flags, Size2MiB);
140
            }
141
        }
142
143
        return;
144
    }
145
146
    pml2 = get_next_level(pagemap, pml3, virt_addr, pg_size, 2, pml3_entry);
147
148
    if (pg_size == Size2MiB) {
149
        pml2[pml2_entry] = (pt_entry_t)(phys_addr | flags | PT_FLAG_LARGE);
150
        return;
151
    }
152
153
    pml1 = get_next_level(pagemap, pml2, virt_addr, pg_size, 1, pml2_entry);
154
155
    // PML1 wants PAT bit at 7 instead of 12
156
    if (flags & ((uint64_t)1 << 12)) {
157
        flags &= ~((uint64_t)1 << 12);
158
        flags |= ((uint64_t)1 << 7);
159
    }
160
161
    pml1[pml1_entry] = (pt_entry_t)(phys_addr | flags);
162
}
163
164
#elif defined (__aarch64__)
165
166
// Here we operate under the assumption that 4K pages are supported by the CPU.
167
// This appears to be guaranteed by UEFI, as section 2.3.6 "AArch64 Platforms"
168
// states that the primary processor core configuration includes 4K translation
169
// granules (TCR_EL1.TG0 = 0).
170
// Support for 4K pages also implies 2M, 1G and 512G blocks.
171
172
// Sanity check that 4K pages are supported.
173
void vmm_assert_4k_pages(void) {
174
    uint64_t aa64mmfr0;
175
    asm volatile ("mrs %0, id_aa64mmfr0_el1" : "=r"(aa64mmfr0));
176
177
    if (((aa64mmfr0 >> 28) & 0b1111) == 0b1111) {
178
        panic(false, "vmm: CPU does not support 4K pages, please make a bug report about this.");
179
    }
180
}
181
182
#define PT_FLAG_VALID    ((uint64_t)1 << 0)
183
#define PT_FLAG_TABLE    ((uint64_t)1 << 1)
184
#define PT_FLAG_4K_PAGE  ((uint64_t)1 << 1)
185
#define PT_FLAG_BLOCK    ((uint64_t)0 << 1)
186
#define PT_FLAG_USER     ((uint64_t)1 << 6)
187
#define PT_FLAG_READONLY ((uint64_t)1 << 7)
188
#define PT_FLAG_INNER_SH ((uint64_t)3 << 8)
189
#define PT_FLAG_ACCESS   ((uint64_t)1 << 10)
190
#define PT_FLAG_XN       ((uint64_t)1 << 54)
191
#define PT_FLAG_WB       ((uint64_t)0 << 2)
192
#define PT_FLAG_FB       ((uint64_t)1 << 2)
193
#define PT_PADDR_MASK    ((uint64_t)0x0000FFFFFFFFF000)
194
195
#define PT_TABLE_FLAGS   (PT_FLAG_VALID | PT_FLAG_TABLE)
196
197
#define PT_IS_TABLE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_TABLE)) == (PT_FLAG_VALID | PT_FLAG_TABLE))
198
#define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_TABLE)) == PT_FLAG_VALID)
199
#define PT_TO_VMM_FLAGS(x) (pt_to_vmm_flags_internal(x))
200
201
#define pte_new(addr, flags)    ((pt_entry_t)(addr) | (flags))
202
#define pte_addr(pte)           ((pte) & PT_PADDR_MASK)
203
204
static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) {
205
    uint64_t flags = 0;
206
207
    if (!(entry & PT_FLAG_READONLY))
208
        flags |= VMM_FLAG_WRITE;
209
    if (entry & PT_FLAG_XN)
210
        flags |= VMM_FLAG_NOEXEC;
211
    if (entry & PT_FLAG_FB)
212
        flags |= VMM_FLAG_FB;
213
214
    return flags;
215
}
216
217
pagemap_t new_pagemap(int paging_mode) {
218
    pagemap_t pagemap;
219
    pagemap.levels       = paging_mode == PAGING_MODE_AARCH64_5LVL ? 5 : 4;
220
    pagemap.top_level[0] = ext_mem_alloc(PT_SIZE);
221
    pagemap.top_level[1] = ext_mem_alloc(PT_SIZE);
222
    return pagemap;
223
}
224
225
void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size pg_size) {
226
    // Calculate the indices in the various tables using the virtual address
227
    size_t pml5_entry = (virt_addr & ((uint64_t)0xf << 48)) >> 48;
228
    size_t pml4_entry = (virt_addr & ((uint64_t)0x1ff << 39)) >> 39;
229
    size_t pml3_entry = (virt_addr & ((uint64_t)0x1ff << 30)) >> 30;
230
    size_t pml2_entry = (virt_addr & ((uint64_t)0x1ff << 21)) >> 21;
231
    size_t pml1_entry = (virt_addr & ((uint64_t)0x1ff << 12)) >> 12;
232
233
    pt_entry_t *pml5, *pml4, *pml3, *pml2, *pml1;
234
235
    bool is_higher_half = virt_addr & ((uint64_t)1 << 63);
236
237
    uint64_t real_flags = PT_FLAG_VALID | PT_FLAG_INNER_SH | PT_FLAG_ACCESS | PT_FLAG_WB;
238
    if (!(flags & VMM_FLAG_WRITE))
239
        real_flags |= PT_FLAG_READONLY;
240
    if (flags & VMM_FLAG_NOEXEC)
241
        real_flags |= PT_FLAG_XN;
242
    if (flags & VMM_FLAG_FB)
243
        real_flags |= PT_FLAG_FB;
244
245
    // Paging levels
246
    switch (pagemap.levels) {
247
        case 5:
248
            pml5 = pagemap.top_level[is_higher_half];
249
            goto level5;
250
        case 4:
251
            pml4 = pagemap.top_level[is_higher_half];
252
            goto level4;
253
        default:
254
            __builtin_unreachable();
255
    }
256
257
level5:
258
    pml4 = get_next_level(pagemap, pml5, virt_addr, pg_size, 4, pml5_entry);
259
level4:
260
    pml3 = get_next_level(pagemap, pml4, virt_addr, pg_size, 3, pml4_entry);
261
262
    if (pg_size == Size1GiB) {
263
        pml3[pml3_entry] = (pt_entry_t)(phys_addr | real_flags | PT_FLAG_BLOCK);
264
        return;
265
    }
266
267
    pml2 = get_next_level(pagemap, pml3, virt_addr, pg_size, 2, pml3_entry);
268
269
    if (pg_size == Size2MiB) {
270
        pml2[pml2_entry] = (pt_entry_t)(phys_addr | real_flags | PT_FLAG_BLOCK);
271
        return;
272
    }
273
274
    pml1 = get_next_level(pagemap, pml2, virt_addr, pg_size, 1, pml2_entry);
275
276
    pml1[pml1_entry] = (pt_entry_t)(phys_addr | real_flags | PT_FLAG_4K_PAGE);
277
}
278
279
#elif defined (__riscv)
280
281
#define PT_FLAG_VALID       ((uint64_t)1 << 0)
282
#define PT_FLAG_READ        ((uint64_t)1 << 1)
283
#define PT_FLAG_WRITE       ((uint64_t)1 << 2)
284
#define PT_FLAG_EXEC        ((uint64_t)1 << 3)
285
#define PT_FLAG_USER        ((uint64_t)1 << 4)
286
#define PT_FLAG_ACCESSED    ((uint64_t)1 << 6)
287
#define PT_FLAG_DIRTY       ((uint64_t)1 << 7)
288
#define PT_FLAG_PBMT_NC     ((uint64_t)1 << 62)
289
#define PT_PADDR_MASK       ((uint64_t)0x003ffffffffffc00)
290
291
#define PT_FLAG_RWX         (PT_FLAG_READ | PT_FLAG_WRITE | PT_FLAG_EXEC)
292
293
#define PT_TABLE_FLAGS      PT_FLAG_VALID
294
#define PT_IS_TABLE(x)      (((x) & (PT_FLAG_VALID | PT_FLAG_RWX)) == PT_FLAG_VALID)
295
#define PT_IS_LARGE(x)      (((x) & (PT_FLAG_VALID | PT_FLAG_RWX)) > PT_FLAG_VALID)
296
#define PT_TO_VMM_FLAGS(x)  (pt_to_vmm_flags_internal(x))
297
298
#define pte_new(addr, flags)    ((((pt_entry_t)(addr) >> 2) & PT_PADDR_MASK) | (flags))
299
#define pte_addr(pte)           (((pte) & PT_PADDR_MASK) << 2)
300
301
static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) {
302
    uint64_t flags = 0;
303
304
    if (entry & PT_FLAG_WRITE)
305
        flags |= VMM_FLAG_WRITE;
306
    if (!(entry & PT_FLAG_EXEC))
307
        flags |= VMM_FLAG_NOEXEC;
308
    if (entry & PT_FLAG_PBMT_NC)
309
        flags |= VMM_FLAG_FB;
310
311
    return flags;
312
}
313
314
uint64_t paging_mode_higher_half(int paging_mode) {
315
    switch (paging_mode) {
316
        case PAGING_MODE_RISCV_SV39:
317
            return 0xffffffc000000000;
318
        case PAGING_MODE_RISCV_SV48:
319
            return 0xffff800000000000;
320
        case PAGING_MODE_RISCV_SV57:
321
            return 0xff00000000000000;
322
        default:
323
            panic(false, "paging_mode_higher_half: invalid mode");
324
    }
325
}
326
327
int paging_mode_va_bits(int paging_mode) {
328
    switch (paging_mode) {
329
        case PAGING_MODE_RISCV_SV39:
330
            return 39;
331
        case PAGING_MODE_RISCV_SV48:
332
            return 48;
333
        case PAGING_MODE_RISCV_SV57:
334
            return 57;
335
        default:
336
            panic(false, "paging_mode_va_bits: invalid mode");
337
    }
338
}
339
340
int vmm_max_paging_mode(void)
341
{
342
    if (bsp_hart == NULL || !(bsp_hart->flags & RISCV_HART_HAS_MMU)) {
343
        panic(false, "vmm: BSP hart does not advertise MMU support");
344
    }
345
346
    return PAGING_MODE_RISCV_SV39 + bsp_hart->mmu_type;
347
}
348
349
static pt_entry_t pbmt_nc = 0;
350
351
pagemap_t new_pagemap(int paging_mode) {
352
    pagemap_t pagemap;
353
    pagemap.paging_mode   = paging_mode;
354
    pagemap.max_page_size = paging_mode - 6;
355
    pagemap.top_level     = ext_mem_alloc(PT_SIZE);
356
357
    if (riscv_check_isa_extension("svpbmt", NULL, NULL)) {
358
        printv("riscv: Svpbmt extension is supported.\n");
359
        pbmt_nc = PT_FLAG_PBMT_NC;
360
    }
361
362
    return pagemap;
363
}
364
365
void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size) {
366
    // Truncate the requested page size to the maximum supported.
367
    if (page_size > pagemap.max_page_size)
368
        page_size = pagemap.max_page_size;
369
370
    // Convert VMM_FLAG_* into PT_FLAG_*.
371
    // Set the ACCESSED and DIRTY flags to avoid faults.
372
    pt_entry_t ptflags = PT_FLAG_VALID | PT_FLAG_READ | PT_FLAG_ACCESSED | PT_FLAG_DIRTY;
373
    if (flags & VMM_FLAG_WRITE)
374
        ptflags |= PT_FLAG_WRITE;
375
    if (!(flags & VMM_FLAG_NOEXEC))
376
        ptflags |= PT_FLAG_EXEC;
377
    if (flags & VMM_FLAG_FB)
378
        ptflags |= pbmt_nc;
379
380
    // Start at the highest level.
381
    // The values of `enum page_size` map to the level index at which that size is mapped.
382
    int level = pagemap.max_page_size;
383
    pt_entry_t *table = pagemap.top_level;
384
    for (;;) {
385
        int index = (virt_addr >> (12 + 9 * level)) & 0x1ff;
386
387
        // Stop when we reach the level for the requested page size.
388
        if (level == (int)page_size) {
389
            table[index] = pte_new(phys_addr, ptflags);
390
            break;
391
        }
392
393
        table = get_next_level(pagemap, table, virt_addr, page_size, level, index);
394
        level--;
395
    }
396
}
397
398
#elif defined (__loongarch64)
399
400
#define INVALID_PAGE    0
401
402
#define PT_FLAG_VALID   ((uint64_t)1 << 0)
403
#define PT_FLAG_DIRTY   ((uint64_t)1 << 1)
404
#define PT_FLAG_MAT_CC  ((uint64_t)1 << 4)
405
#define PT_FLAG_MAT_WUC ((uint64_t)1 << 5)
406
#define PT_FLAG_GLOBAL  ((uint64_t)1 << 6)
407
#define PT_FLAG_HUGE    ((uint64_t)1 << 6)
408
#define PT_FLAG_WRITE   ((uint64_t)1 << 8)
409
#define PT_FLAG_HGLOBAL ((uint64_t)1 << 12)
410
#define PT_FLAG_NX      ((uint64_t)1 << 62)
411
#define PT_PADDR_MASK   ((uint64_t)0x0000FFFFFFFFF000)
412
#define PT_PADDR_HMASK  ((uint64_t)0x0000FFFFFF000000)
413
414
#define PT_TABLE_FLAGS      0
415
#define PT_IS_TABLE(x)      ((level_idx > 0) && (((x) & PT_FLAG_VALID) == 0) && ((x) != INVALID_PAGE))
416
#define PT_IS_LARGE(x)      (((x) & (PT_FLAG_HGLOBAL | PT_FLAG_HUGE)) == (PT_FLAG_HGLOBAL | PT_FLAG_HUGE))
417
#define PT_TO_VMM_FLAGS(x)  (pt_to_vmm_flags_internal(x))
418
419
#define pte_new(addr, flags)    (pt_entry_t)((addr) | (flags))
420
421
static inline uint64_t pte_addr(uint64_t pte) {
422
    if (PT_IS_LARGE(pte)) {
423
        return pte & PT_PADDR_HMASK;
424
    }
425
    return pte & PT_PADDR_MASK;
426
}
427
428
static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) {
429
    uint64_t flags = 0;
430
431
    if (entry & PT_FLAG_WRITE)
432
        flags |= VMM_FLAG_WRITE;
433
    if (entry & PT_FLAG_NX)
434
        flags |= VMM_FLAG_NOEXEC;
435
    if (entry & PT_FLAG_MAT_WUC)
436
        flags |= VMM_FLAG_FB;
437
438
    return flags;
439
}
440
441
pagemap_t new_pagemap(int paging_mode) {
442
    (void)paging_mode;
443
444
    uint32_t cpucfg_val;
445
446
    asm volatile (
447
        "cpucfg %0, %1"
448
        : "=r"(cpucfg_val)
449
        : "r"(1)
450
    );
451
452
    uint32_t valen = ((cpucfg_val >> 12) & 0xff) + 1;
453
    if (valen < 48) {
454
        panic(true, "vmm: VALEN values < 48 not currently supported");
455
    }
456
457
    (void)paging_mode;
458
    pagemap_t pagemap;
459
    pagemap.pgd[0] = ext_mem_alloc(PT_SIZE);
460
    pagemap.pgd[1] = ext_mem_alloc(PT_SIZE);
461
    return pagemap;
462
}
463
464
void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size pg_size) {
465
    size_t pml4_entry = (virt_addr & ((uint64_t)0x1ff << 39)) >> 39;
466
    size_t pml3_entry = (virt_addr & ((uint64_t)0x1ff << 30)) >> 30;
467
    size_t pml2_entry = (virt_addr & ((uint64_t)0x1ff << 21)) >> 21;
468
    size_t pml1_entry = (virt_addr & ((uint64_t)0x1ff << 12)) >> 12;
469
470
    pt_entry_t *pml4, *pml3, *pml2, *pml1;
471
472
    bool is_higher_half = virt_addr & ((uint64_t)1 << 63);
473
474
    uint64_t real_flags = PT_FLAG_VALID | PT_FLAG_GLOBAL;
475
    if (flags & VMM_FLAG_WRITE)
476
        real_flags |= PT_FLAG_DIRTY | PT_FLAG_WRITE;
477
    if (flags & VMM_FLAG_NOEXEC)
478
        real_flags |= PT_FLAG_NX;
479
    if (flags & VMM_FLAG_FB)
480
        real_flags |= PT_FLAG_MAT_WUC;
481
    else
482
        real_flags |= PT_FLAG_MAT_CC;
483
484
    pml4 = pagemap.pgd[is_higher_half];
485
486
    pml3 = get_next_level(pagemap, pml4, virt_addr, pg_size, 3, pml4_entry);
487
488
    if (pg_size == Size1GiB) {
489
        pml3[pml3_entry] = pte_new(phys_addr, real_flags | PT_FLAG_HGLOBAL | PT_FLAG_HUGE);
490
        return;
491
    }
492
493
    pml2 = get_next_level(pagemap, pml3, virt_addr, pg_size, 2, pml3_entry);
494
495
    if (pg_size == Size2MiB) {
496
        pml2[pml2_entry] = pte_new(phys_addr, real_flags | PT_FLAG_HGLOBAL | PT_FLAG_HUGE);
497
        return;
498
    }
499
500
    pml1 = get_next_level(pagemap, pml2, virt_addr, pg_size, 1, pml2_entry);
501
502
    pml1[pml1_entry] = pte_new(phys_addr, real_flags);
503
}
504
505
#else
506
#error Unknown architecture
507
#endif
508
509
static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level,
510
                                  uint64_t virt, enum page_size desired_sz,
511
                                  size_t level_idx, size_t entry) {
512
    pt_entry_t *ret;
513
514
    if (PT_IS_TABLE(current_level[entry])) {
515
        ret = (pt_entry_t *)(size_t)pte_addr(current_level[entry]);
516
    } else {
517
        if (PT_IS_LARGE(current_level[entry])) {
518
            // We are replacing an existing large page with a smaller page.
519
            // Split the previous mapping into mappings of the newly requested size
520
            // before performing the requested map operation.
521
522
523
            if ((level_idx >= VMM_MAX_LEVEL) || (level_idx == 0))
524
                panic(false, "Unexpected level in get_next_level");
525
            if (desired_sz >= VMM_MAX_LEVEL)
526
                panic(false, "Unexpected page size in get_next_level");
527
528
            uint64_t old_page_size = page_sizes[level_idx];
529
            uint64_t new_page_size = page_sizes[desired_sz];
530
531
            // Save all the information from the old entry at this level
532
            uint64_t old_flags = PT_TO_VMM_FLAGS(current_level[entry]);
533
            uint64_t old_phys = pte_addr(current_level[entry]);
534
            uint64_t old_virt = virt & ~(old_page_size - 1);
535
536
            if (old_phys & (old_page_size - 1))
537
                panic(false, "Unexpected page table entry address in get_next_level");
538
539
            // Allocate a table for the next level
540
            ret = ext_mem_alloc(PT_SIZE);
541
            current_level[entry] = pte_new((size_t)ret, PT_TABLE_FLAGS);
542
543
            // Recreate the old mapping with smaller pages
544
            for (uint64_t i = 0; i < old_page_size; i += new_page_size) {
545
                map_page(pagemap, old_virt + i, old_phys + i, old_flags, desired_sz);
546
            }
547
        } else {
548
            // Allocate a table for the next level
549
            ret = ext_mem_alloc(PT_SIZE);
550
            current_level[entry] = pte_new((size_t)ret, PT_TABLE_FLAGS);
551
        }
552
    }
553
554
    return ret;
555
}
tab: 248 wrap: offon