summaryrefslogtreecommitdiff
path: root/na/mm
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:24:56 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:24:56 +0800
commit1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (patch)
tree0f5e01cd7c7a05abb6b1c7472d7f00f49caf02cc /na/mm
parent63dc025705f6533b91fb1a7d26e6ed864efd9900 (diff)
downloadlume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.gz
lume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.xz
feat: establish bootable new architecture checkpoint
Replace the compressed boot path with a relocatable higher-half RISC-V entry, expand memory and SMP support, and add the driver, TTY, VFS, exec, signal, syscall, initramfs, and self-test infrastructure needed for a bootable kernel checkpoint.
Diffstat (limited to 'na/mm')
-rw-r--r--na/mm/memblock.cc70
-rw-r--r--na/mm/module.mk2
-rw-r--r--na/mm/page_cache.cc252
-rw-r--r--na/mm/pmm.cc180
-rw-r--r--na/mm/slab.cc8
-rw-r--r--na/mm/tlb.cc90
-rw-r--r--na/mm/vma.cc81
-rw-r--r--na/mm/vmm.cc65
8 files changed, 624 insertions, 124 deletions
diff --git a/na/mm/memblock.cc b/na/mm/memblock.cc
index 4cd0b16..aee0245 100644
--- a/na/mm/memblock.cc
+++ b/na/mm/memblock.cc
@@ -16,6 +16,7 @@
#include <lume/fdt.h>
#include <lume/klog.h>
#include <lume/kprintf.h>
+#include <lume/config.h>
#include <lume/addr.h>
extern "C" char _stext[], _kernel_end[];
@@ -24,7 +25,11 @@ extern "C" char _stext[], _kernel_end[];
/* Module state */
/* ------------------------------------------------------------------ */
-static struct {
+namespace {
+
+constexpr uint64 kMB = 1024ULL * 1024ULL;
+
+struct {
uint64 mem_base;
uint64 mem_size;
@@ -41,7 +46,7 @@ static struct {
/* ------------------------------------------------------------------ */
/* Insert a reserved region, keeping the array sorted by base. */
-static void insert_reserved(uint64 base, uint64 size)
+void insert_reserved(uint64 base, uint64 size)
{
if (g_mb.reserved_count >= kMemblockMaxRegions) {
kernel_panic("memblock: too many reserved regions");
@@ -57,7 +62,7 @@ static void insert_reserved(uint64 base, uint64 size)
}
/* Check if [base, base+size) overlaps any reserved region. */
-static bool overlaps_reserved(uint64 base, uint64 size)
+bool overlaps_reserved(uint64 base, uint64 size)
{
uint64 end = base + size;
for (int i = 0; i < g_mb.reserved_count; i++) {
@@ -71,7 +76,7 @@ static bool overlaps_reserved(uint64 base, uint64 size)
}
/* Find the end of the first reserved region that covers `addr`. */
-static uint64 reserved_end_covering(uint64 addr)
+uint64 reserved_end_covering(uint64 addr)
{
for (int i = 0; i < g_mb.reserved_count; i++) {
uint64 rb = g_mb.reserved[i].base;
@@ -83,13 +88,14 @@ static uint64 reserved_end_covering(uint64 addr)
return addr;
}
+} // namespace
+
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
void memblock_init(uint64 fdt_paddr)
{
- (void)fdt_paddr;
/* 1. Get memory layout from FDT */
uint64 mem_base = 0;
@@ -104,21 +110,58 @@ void memblock_init(uint64 fdt_paddr)
g_mb.reserved_count = 0;
g_mb.active = true;
- /* 2. Reserve firmware region [mem_base, kernel_start).
- * OpenSBI occupies the first 2MB of RAM on RISC-V virt. */
- uint64 ks_pa = boot_va_to_pa(virt_addr(
- reinterpret_cast<uint64>(_stext))).raw;
- if (ks_pa > mem_base) {
- memblock_reserve(mem_base, ks_pa - mem_base);
+ /* 2. Reserve firmware region [mem_base, kPhysStart).
+ *
+ * Everything below the kernel's linked load address is firmware
+ * territory (OpenSBI), which is PMP-protected. */
+ {
+ uint64 fw_end = kPhysStart;
+ if (fw_end > mem_base) {
+ memblock_reserve(mem_base, fw_end - mem_base);
+ }
+ }
+
+ /* 2b. Reserve stale pre-relocation kernel image.
+ *
+ * After physical KASLR, the kernel is relocated from its original
+ * load address (kPhysStart) to a new PA. The old copy at
+ * [kPhysStart, kPhysStart + kernel_size) must not be allocated —
+ * it contains stale code/data that would corrupt page tables or
+ * other structures if reused. */
+ {
+ uint64 ks_pa = boot_va_to_pa(virt_addr(
+ reinterpret_cast<uint64>(_stext))).raw;
+ if (ks_pa != kPhysStart) {
+ /* Kernel was relocated — reserve the original location */
+ uint64 kernel_size = boot_va_to_pa(virt_addr(
+ reinterpret_cast<uint64>(_kernel_end))).raw - ks_pa;
+ memblock_reserve(kPhysStart, kernel_size);
+ }
}
/* 3. Reserve kernel image [_stext, _kernel_end) */
+ uint64 ks_pa = boot_va_to_pa(virt_addr(
+ reinterpret_cast<uint64>(_stext))).raw;
uint64 ke_pa = boot_va_to_pa(virt_addr(
reinterpret_cast<uint64>(_kernel_end))).raw;
memblock_reserve(ks_pa, ke_pa - ks_pa);
+ /* 4. Reserve FDT blob — it lives in RAM and must not be overwritten
+ * by vmemmap/page_map or buddy before fdt_unflatten() copies it. */
+ {
+ struct fdt_hdr { uint32 magic; uint32 totalsize; };
+ const auto* fh = reinterpret_cast<const fdt_hdr*>(fdt_paddr);
+ uint32 fdt_size = ((fh->totalsize & 0xFF) << 24) |
+ (((fh->totalsize >> 8) & 0xFF) << 16) |
+ (((fh->totalsize >> 16) & 0xFF) << 8) |
+ ((fh->totalsize >> 24) & 0xFF);
+ memblock_reserve(fdt_paddr, fdt_size);
+ kprintf("[memblock] FDT reserved [0x%llx, 0x%llx)\n",
+ fdt_paddr, fdt_paddr + fdt_size);
+ }
+
kprintf("[memblock] RAM [0x%llx, 0x%llx) = %llu MB\n",
- mem_base, mem_base + mem_size, mem_size / (1024 * 1024));
+ mem_base, mem_base + mem_size, mem_size / kMB);
kprintf("[memblock] kernel reserved [0x%llx, 0x%llx)\n", ks_pa, ke_pa);
}
@@ -196,7 +239,6 @@ void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx),
for (int i = 0; i < g_mb.reserved_count && pos < mem_end; i++) {
uint64 rb = g_mb.reserved[i].base;
if (rb < pos) {
- /* Overlapping/adjacent — skip */
uint64 re = g_mb.reserved[i].base + g_mb.reserved[i].size;
if (re > pos) {
pos = re;
@@ -204,13 +246,11 @@ void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx),
continue;
}
if (rb > pos) {
- /* Free gap [pos, rb) */
cb(pos, rb - pos, ctx);
}
pos = g_mb.reserved[i].base + g_mb.reserved[i].size;
}
- /* Trailing free region after last reserved */
if (pos < mem_end) {
cb(pos, mem_end - pos, ctx);
}
diff --git a/na/mm/module.mk b/na/mm/module.mk
index d497067..93cef80 100644
--- a/na/mm/module.mk
+++ b/na/mm/module.mk
@@ -3,3 +3,5 @@ SRCS_CC += mm/pmm.cc
SRCS_CC += mm/slab.cc
SRCS_CC += mm/vmm.cc
SRCS_CC += mm/vma.cc
+SRCS_CC += mm/page_cache.cc
+SRCS_CC += mm/tlb.cc
diff --git a/na/mm/page_cache.cc b/na/mm/page_cache.cc
new file mode 100644
index 0000000..25c6a60
--- /dev/null
+++ b/na/mm/page_cache.cc
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * page_cache.cc — Minimal page cache implementation
+ *
+ * Open-addressing hash table indexed by (mapping, index) pairs.
+ * Fixed-size bucket array allocated at init time via slab.
+ *
+ * This is intentionally simple: no LRU, no writeback, no eviction.
+ * The interface in page_cache.h is designed so that upgrading to a
+ * radix tree + LRU scheme later only changes this file.
+ *
+ * Concurrency: protected by a single spinlock. Acceptable for
+ * initial bring-up; will be replaced by per-bucket or RCU-based
+ * locking when contention becomes measurable.
+ */
+
+#include <lume/page_cache.h>
+#include <lume/page.h>
+#include <lume/pmm.h>
+#include <lume/errno.h>
+#include <lume/new.h>
+#include <kernel/sync/spinlock.h>
+
+/* ------------------------------------------------------------------ */
+/* Hash table internals */
+/* ------------------------------------------------------------------ */
+
+struct CacheEntry {
+ void* mapping; // nullptr = empty slot
+ uint64 index;
+ Page* page;
+};
+
+namespace {
+
+constexpr uint64 kCacheBuckets = 1024;
+
+CacheEntry g_buckets[kCacheBuckets];
+Spinlock g_cache_lock;
+
+uint64 hash_key(void* mapping, uint64 index)
+{
+ uint64 m = reinterpret_cast<uint64>(mapping);
+ /* FNV-1a inspired mixing */
+ uint64 h = m ^ (index * 0x517cc1b727220a95ULL);
+ h ^= h >> 17;
+ h *= 0xbf58476d1ce4e5b9ULL;
+ h ^= h >> 31;
+ return h % kCacheBuckets;
+}
+
+/* Linear probe to find an entry or an empty slot. */
+CacheEntry* find_slot(void* mapping, uint64 index)
+{
+ uint64 start = hash_key(mapping, index);
+ for (uint64 i = 0; i < kCacheBuckets; i++) {
+ uint64 idx = (start + i) % kCacheBuckets;
+ CacheEntry* e = &g_buckets[idx];
+ if (e->mapping == nullptr) {
+ return e; // Empty slot
+ }
+ if (e->mapping == mapping && e->index == index) {
+ return e; // Found
+ }
+ }
+ return nullptr; // Table full
+}
+
+/* Find an existing entry (not an empty slot). */
+CacheEntry* find_entry(void* mapping, uint64 index)
+{
+ uint64 start = hash_key(mapping, index);
+ for (uint64 i = 0; i < kCacheBuckets; i++) {
+ uint64 idx = (start + i) % kCacheBuckets;
+ CacheEntry* e = &g_buckets[idx];
+ if (e->mapping == nullptr) {
+ return nullptr; // Empty = not found
+ }
+ if (e->mapping == mapping && e->index == index) {
+ return e;
+ }
+ }
+ return nullptr;
+}
+
+/*
+ * Remove an entry and fix up the linear-probe chain.
+ * Standard open-addressing deletion with backward shift.
+ */
+void remove_entry(CacheEntry* entry)
+{
+ uint64 removed = static_cast<uint64>(entry - g_buckets);
+ entry->mapping = nullptr;
+ entry->page = nullptr;
+
+ /* Backward-shift deletion to maintain probe chain integrity */
+ uint64 cur = (removed + 1) % kCacheBuckets;
+ while (g_buckets[cur].mapping != nullptr) {
+ uint64 ideal = hash_key(g_buckets[cur].mapping, g_buckets[cur].index);
+ /* Check if cur's ideal position is "before" removed in probe order */
+ bool needs_shift = false;
+ if (removed <= cur) {
+ needs_shift = (ideal <= removed || ideal > cur);
+ } else {
+ needs_shift = (ideal <= removed && ideal > cur);
+ }
+
+ if (needs_shift) {
+ g_buckets[removed] = g_buckets[cur];
+ g_buckets[cur].mapping = nullptr;
+ g_buckets[cur].page = nullptr;
+ removed = cur;
+ }
+ cur = (cur + 1) % kCacheBuckets;
+ }
+}
+
+} // namespace
+
+/* ------------------------------------------------------------------ */
+/* Public API */
+/* ------------------------------------------------------------------ */
+
+void page_cache_init()
+{
+ g_cache_lock.init("page_cache");
+ for (uint64 i = 0; i < kCacheBuckets; i++) {
+ g_buckets[i].mapping = nullptr;
+ g_buckets[i].index = 0;
+ g_buckets[i].page = nullptr;
+ }
+}
+
+Page* page_cache_get(void* mapping, uint64 index)
+{
+ LockGuard guard(g_cache_lock);
+ CacheEntry* e = find_entry(mapping, index);
+ if (!e) {
+ return nullptr;
+ }
+ page_incref(e->page);
+ return e->page;
+}
+
+int page_cache_insert(void* mapping, uint64 index, Page* page)
+{
+ if (!mapping || !page) {
+ return -EINVAL;
+ }
+
+ LockGuard guard(g_cache_lock);
+
+ /* Check for existing entry */
+ CacheEntry* existing = find_entry(mapping, index);
+ if (existing) {
+ return -EEXIST;
+ }
+
+ /* Find an empty slot */
+ CacheEntry* slot = find_slot(mapping, index);
+ if (!slot || slot->mapping != nullptr) {
+ return -ENOMEM; // Table full
+ }
+
+ slot->mapping = mapping;
+ slot->index = index;
+ slot->page = page;
+ page_incref(page); // Cache holds a reference
+
+ return 0;
+}
+
+void page_cache_put(Page* page)
+{
+ if (!page) {
+ return;
+ }
+ page_decref(page);
+}
+
+Page* page_cache_remove(void* mapping, uint64 index)
+{
+ LockGuard guard(g_cache_lock);
+ CacheEntry* e = find_entry(mapping, index);
+ if (!e) {
+ return nullptr;
+ }
+
+ Page* page = e->page;
+ remove_entry(e);
+ page_decref(page); // Release cache's reference
+ return page;
+}
+
+void page_cache_invalidate(void* mapping)
+{
+ if (!mapping) {
+ return;
+ }
+
+ LockGuard guard(g_cache_lock);
+
+ /* Scan all buckets — acceptable for current table size */
+ for (uint64 i = 0; i < kCacheBuckets; i++) {
+ if (g_buckets[i].mapping == mapping) {
+ Page* page = g_buckets[i].page;
+ remove_entry(&g_buckets[i]);
+ page_decref(page);
+ /* After removal, re-check this index since backward shift
+ * may have moved an entry into this slot. */
+ i--;
+ }
+ }
+}
+
+Page* page_cache_get_or_alloc(void* mapping, uint64 index)
+{
+ /* Fast path: already cached */
+ Page* page = page_cache_get(mapping, index);
+ if (page) {
+ return page;
+ }
+
+ /* Slow path: allocate and insert */
+ page = pmm_alloc_page();
+ if (!page) {
+ return nullptr;
+ }
+
+ int ret = page_cache_insert(mapping, index, page);
+ if (ret == -EEXIST) {
+ /* Race: another thread inserted between our get and insert.
+ * Free our allocation and return the existing page. */
+ pmm_free_page(page);
+ return page_cache_get(mapping, index);
+ }
+ if (ret < 0) {
+ pmm_free_page(page);
+ return nullptr;
+ }
+
+ /* Caller gets a ref (insert already added cache's ref,
+ * we need one more for the caller). */
+ /* Actually, insert did incref for cache. The page already has
+ * refcount=1 from alloc + 1 from insert = 2.
+ * We return it to caller who must put() when done.
+ * So we incref once more for the caller's reference. */
+ page_incref(page);
+ return page;
+}
diff --git a/na/mm/pmm.cc b/na/mm/pmm.cc
index a5a0b0e..b7cf611 100644
--- a/na/mm/pmm.cc
+++ b/na/mm/pmm.cc
@@ -43,16 +43,25 @@ struct PerCpuCache {
/* Module-private state */
/* ------------------------------------------------------------------ */
-static Spinlock g_pmm_lock;
-static list_node g_free_areas[kMaxOrder]; // Buddy free lists [0..10]
-static PerCpuCache g_pcp[kMaxCpus];
+namespace {
-static Page *g_page_map = nullptr; // Flat array of Page descriptors
-static uint64 g_page_map_pa = 0; // Physical base of page_map storage
-static uint64 g_page_map_bytes = 0; // Byte size of page_map storage
-static uint64 g_mem_base = 0; // Physical memory base (from FDT)
-static uint64 g_mem_size = 0; // Total physical memory size
-static uint64 g_num_pages = 0; // Total managed pages
+/* PCP Cache tuning parameters */
+constexpr uint32 kPcpBatchDivisor = 1024;
+constexpr uint32 kPcpMinBatchSize = 16;
+constexpr uint64 kMB = 1024ULL * 1024ULL;
+
+Spinlock g_pmm_lock;
+list_node g_free_areas[kMaxOrder]; // Buddy free lists [0..10]
+PerCpuCache g_pcp[kMaxCpus];
+
+Page *g_page_map = nullptr; // Flat array of Page descriptors
+uint64 g_page_map_pa = 0; // Physical base of page_map storage
+uint64 g_page_map_bytes = 0; // Byte size of page_map storage
+uint64 g_mem_base = 0; // Physical memory base (from FDT)
+uint64 g_mem_size = 0; // Total physical memory size
+uint64 g_num_pages = 0; // Total managed pages
+
+} // namespace
uint32 g_pcp_high_watermark = 0;
uint32 g_pcp_batch_size = 0;
@@ -62,63 +71,68 @@ uint32 g_pcp_batch_size = 0;
/* ------------------------------------------------------------------ */
/*
- * Physical Page Number: index into the page_map array.
- * PFN = (pa - g_mem_base) / kPageSize
+ * PFN (Page Frame Number) — fundamental conversion unit.
+ * When sparse vmemmap is implemented, only these two functions change.
+ * All other PA↔Page conversions go through them.
*/
-static inline uint64 pa_to_page_index(uint64 pa) {
- if (pa < g_mem_base || pa >= g_mem_base + g_num_pages * kPageSize)
- kernel_panic("pa_to_page: physical address outside managed range");
- return (pa - g_mem_base) / kPageSize;
-}
-
-static inline uint64 page_index_to_pa(uint64 idx) {
- if (idx >= g_num_pages)
- kernel_panic("page_to_pa: page index outside managed range");
- return g_mem_base + idx * kPageSize;
-}
-
-PhysAddr page_to_pa(const Page *page) {
+uint64 page_to_pfn(const Page *page) {
uintptr base = reinterpret_cast<uintptr>(g_page_map);
uintptr addr = reinterpret_cast<uintptr>(page);
uintptr end = base + g_num_pages * sizeof(Page);
- if (addr < base || addr >= end)
- kernel_panic("page_to_pa: page outside page_map bounds");
+ if (addr < base || addr >= end) {
+ kernel_panic("page_to_pfn: page outside page_map bounds");
+ }
uint64 idx = (addr - base) / sizeof(Page);
- return phys_addr(page_index_to_pa(idx));
+ return (g_mem_base / kPageSize) + idx;
+}
+
+Page *pfn_to_page(uint64 pfn) {
+ uint64 base_pfn = g_mem_base / kPageSize;
+ if (pfn < base_pfn || pfn >= base_pfn + g_num_pages) {
+ kernel_panic("pfn_to_page: PFN outside managed range");
+ }
+ return &g_page_map[pfn - base_pfn];
+}
+
+PhysAddr page_to_pa(const Page *page) {
+ return pfn_to_pa(Pfn{page_to_pfn(page)});
}
Page *pa_to_page(PhysAddr pa) {
- uint64 idx = pa_to_page_index(pa.raw);
- return &g_page_map[idx];
+ return pfn_to_page(pa_to_pfn(pa).raw);
}
VirtAddr page_to_va(const Page *page) {
return pa_to_va(page_to_pa(page));
}
+
/* ------------------------------------------------------------------ */
/* Buddy system internals (caller must hold g_pmm_lock) */
/* ------------------------------------------------------------------ */
+namespace {
+
/*
* Find a page's buddy at a given order.
* Buddy PFN = PFN ^ (1 << order).
*/
-static inline Page *buddy_of(Page *page, uint8 order) {
+inline Page *buddy_of(Page *page, uint8 order) {
uint64 pfn = static_cast<uint64>(page - g_page_map);
uint64 buddy_pfn = pfn ^ (1ULL << order);
- if (buddy_pfn >= g_num_pages)
+ if (buddy_pfn >= g_num_pages) {
return nullptr;
+ }
return &g_page_map[buddy_pfn];
}
/*
* Insert a free block into the buddy free list at the given order.
*/
-static void buddy_list_add(Page *page, uint8 order) {
- page->state = PageState::Free;
+void buddy_list_add(Page *page, uint8 order) {
+ page->type = PageType::Free;
page->order = order;
list_node *head = &g_free_areas[order];
list_node *node = &page->free.free_link;
@@ -131,7 +145,7 @@ static void buddy_list_add(Page *page, uint8 order) {
/*
* Remove a block from whatever buddy free list it is on.
*/
-static void buddy_list_del(Page *page) {
+void buddy_list_del(Page *page) {
list_node *node = &page->free.free_link;
node->prev->next = node->next;
node->next->prev = node->prev;
@@ -144,11 +158,12 @@ static void buddy_list_del(Page *page) {
* Splits higher-order blocks if no exact match is available.
* Returns nullptr if no memory available.
*/
-static Page *buddy_alloc(uint8 order) {
+Page *buddy_alloc(uint8 order) {
for (int cur = order; cur < kMaxOrder; cur++) {
list_node *head = &g_free_areas[cur];
- if (head->next == head)
+ if (head->next == head) {
continue; // Empty at this order
+ }
// Pop the first block from this order's free list
list_node *node = head->next;
@@ -166,9 +181,13 @@ static Page *buddy_alloc(uint8 order) {
buddy_list_add(split, static_cast<uint8>(cur));
}
- page->state = PageState::Allocated;
+ page->type = PageType::Allocated;
page->order = order;
page->refcount.store(1, __ATOMIC_RELAXED);
+ page->mapcount.store(0, __ATOMIC_RELAXED);
+ page->flags.store(0, __ATOMIC_RELAXED);
+ page->mapping = nullptr;
+ page->index = 0;
return page;
}
return nullptr;
@@ -178,16 +197,18 @@ static Page *buddy_alloc(uint8 order) {
* Return a block of 2^order pages to the buddy system.
* Coalesces with its buddy recursively up to kMaxOrder-1.
*/
-static void buddy_free(Page *page, uint8 order) {
+void buddy_free(Page *page, uint8 order) {
uint64 pfn = static_cast<uint64>(page - g_page_map);
while (order < kMaxOrder - 1) {
Page *buddy = buddy_of(page, order);
- if (!buddy)
+ if (!buddy) {
break;
+ }
// Buddy must be free AND at the same order to coalesce
- if (buddy->state != PageState::Free || buddy->order != order)
+ if (buddy->type != PageType::Free || buddy->order != order) {
break;
+ }
// Remove buddy from its free list and merge
buddy_list_del(buddy);
@@ -212,14 +233,15 @@ static void buddy_free(Page *page, uint8 order) {
* Refill the PCP from the global buddy system.
* Acquires g_pmm_lock internally.
*/
-static void refill_pcp(PerCpuCache *pcp) {
+void refill_pcp(PerCpuCache *pcp) {
LockGuard guard(g_pmm_lock);
for (uint32 i = 0; i < g_pcp_batch_size; i++) {
Page *page = buddy_alloc(0);
- if (!page)
+ if (!page) {
break;
+ }
// Put onto PCP list (reuse the free_link for PCP chain)
- page->state = PageState::PcpCached;
+ page->type = PageType::PcpCached;
list_node *node = &page->free.free_link;
node->next = pcp->free_list.next;
node->prev = &pcp->free_list;
@@ -233,13 +255,14 @@ static void refill_pcp(PerCpuCache *pcp) {
* Drain half the PCP back to the global buddy system.
* Acquires g_pmm_lock internally.
*/
-static void drain_pcp(PerCpuCache *pcp) {
+void drain_pcp(PerCpuCache *pcp) {
LockGuard guard(g_pmm_lock);
int to_drain = pcp->count / 2;
for (int i = 0; i < to_drain; i++) {
list_node *head = &pcp->free_list;
- if (head->next == head)
+ if (head->next == head) {
break;
+ }
list_node *node = head->next;
// Unlink from PCP
node->prev->next = node->next;
@@ -253,6 +276,8 @@ static void drain_pcp(PerCpuCache *pcp) {
}
}
+} // namespace
+
/* ------------------------------------------------------------------ */
/* Public API: single-page (fast path via PCP) */
/* ------------------------------------------------------------------ */
@@ -269,8 +294,9 @@ Page *pmm_alloc_page() {
uint64 cpu = arch::cpu::id();
PerCpuCache *pcp = &g_pcp[cpu];
- if (pcp->count == 0)
+ if (pcp->count == 0) {
refill_pcp(pcp);
+ }
Page *page = nullptr;
if (pcp->count > 0) {
@@ -289,19 +315,25 @@ Page *pmm_alloc_page() {
kernel_panic("pmm_alloc_page: PCP returned out-of-bounds page");
}
- page->state = PageState::Allocated;
+ page->type = PageType::Allocated;
page->order = 0;
page->refcount.store(1, __ATOMIC_RELAXED);
+ page->mapcount.store(0, __ATOMIC_RELAXED);
+ page->flags.store(0, __ATOMIC_RELAXED);
+ page->mapping = nullptr;
+ page->index = 0;
}
- if (was_on)
+ if (was_on) {
arch::cpu::intr_on();
+ }
return page;
}
void pmm_free_page(Page *page) {
- if (!page)
+ if (!page) {
kernel_panic("pmm_free_page: null page");
+ }
bool was_on = arch::cpu::intr_enabled();
arch::cpu::intr_off();
@@ -310,7 +342,7 @@ void pmm_free_page(Page *page) {
PerCpuCache *pcp = &g_pcp[cpu];
// Push onto PCP
- page->state = PageState::PcpCached;
+ page->type = PageType::PcpCached;
page->order = 0;
list_node *node = &page->free.free_link;
node->next = pcp->free_list.next;
@@ -320,11 +352,13 @@ void pmm_free_page(Page *page) {
pcp->count++;
// Drain if over high watermark
- if (pcp->count >= g_pcp_high_watermark)
+ if (pcp->count >= g_pcp_high_watermark) {
drain_pcp(pcp);
+ }
- if (was_on)
+ if (was_on) {
arch::cpu::intr_on();
+ }
}
/* ------------------------------------------------------------------ */
@@ -332,17 +366,20 @@ void pmm_free_page(Page *page) {
/* ------------------------------------------------------------------ */
Page *pmm_alloc_pages(uint8 order) {
- if (order >= kMaxOrder)
+ if (order >= kMaxOrder) {
return nullptr;
+ }
LockGuard guard(g_pmm_lock);
return buddy_alloc(order);
}
void pmm_free_pages(Page *page, uint8 order) {
- if (!page)
+ if (!page) {
kernel_panic("pmm_free_pages: null page");
- if (order >= kMaxOrder)
+ }
+ if (order >= kMaxOrder) {
kernel_panic("pmm_free_pages: order out of range");
+ }
LockGuard guard(g_pmm_lock);
buddy_free(page, order);
}
@@ -358,10 +395,11 @@ void page_decref(Page *page) {
if (old == 1) {
// Refcount dropped to 0 — return to pool based on originally allocated
// order
- if (page->order == 0)
+ if (page->order == 0) {
pmm_free_page(page);
- else
+ } else {
pmm_free_pages(page, page->order);
+ }
}
}
@@ -373,11 +411,14 @@ void page_decref(Page *page) {
* Helper: zero out a range of bytes. We cannot use memset here
* because lib/string may not be linked yet.
*/
-static void zero_range(void *start, uint64 bytes) {
+namespace {
+void zero_range(void *start, uint64 bytes) {
uint8 *p = static_cast<uint8 *>(start);
- for (uint64 i = 0; i < bytes; i++)
+ for (uint64 i = 0; i < bytes; i++) {
p[i] = 0;
+ }
}
+} // namespace
void pmm_init() {
/* 1. Get memory layout from memblock (already scanned FDT) */
@@ -390,9 +431,9 @@ void pmm_init() {
}
/* 2. PCP parameters */
- uint32 calc_batch = static_cast<uint32>(g_num_pages / (kMaxCpus * 1024));
- g_pcp_batch_size = (calc_batch > 16) ? calc_batch : 16;
- g_pcp_high_watermark = g_pcp_batch_size * 2;
+ uint32 calc_batch = static_cast<uint32>(g_num_pages / (kMaxCpus * kPcpBatchDivisor));
+ g_pcp_batch_size = (calc_batch > kPcpMinBatchSize) ? calc_batch : kPcpMinBatchSize;
+ g_pcp_high_watermark = g_pcp_batch_size * 4;
/* 3. page_map was allocated by vmm_init at vmemmap VA.
* No relocation needed — page_map starts life at the right VA. */
@@ -407,8 +448,13 @@ void pmm_init() {
for (uint64 i = 0; i < g_num_pages; i++) {
Page *page = &g_page_map[i];
page->refcount.store(0, __ATOMIC_RELAXED);
- page->state = PageState::Allocated;
+ page->mapcount.store(0, __ATOMIC_RELAXED);
+ page->flags.store(0, __ATOMIC_RELAXED);
+ page->type = PageType::Allocated;
page->order = 0;
+ page->mapping = nullptr;
+ page->index = 0;
+ page->lru.init();
page->free.free_link.init();
}
@@ -438,9 +484,7 @@ void pmm_init() {
uint64 start_pfn = (base - memblock_get_mem_base()) / kPageSize;
uint64 end_pfn = start_pfn + size / kPageSize;
- /* Access page_map via the extern g_page_map (already at vmemmap VA) */
- extern Page *g_page_map;
- extern list_node g_free_areas[];
+ /* Access page_map via the globals directly (they are in anon namespace now) */
uint64 pfn = start_pfn;
while (pfn < end_pfn) {
@@ -454,7 +498,7 @@ void pmm_init() {
}
Page *page = &g_page_map[pfn];
- page->state = PageState::Free;
+ page->type = PageType::Free;
page->order = order;
page->refcount.store(0, __ATOMIC_RELAXED);
page->free.free_link.init();
@@ -474,7 +518,7 @@ void pmm_init() {
&ctx);
kprintf("[pmm] buddy: %llu free pages (%llu MB)\n", ctx.total_pages,
- (ctx.total_pages * kPageSize) / (1024 * 1024));
+ (ctx.total_pages * kPageSize) / kMB);
/* 8. Retire memblock — all future allocations go through PMM */
memblock_retire();
diff --git a/na/mm/slab.cc b/na/mm/slab.cc
index e9b06cc..43d8c4a 100644
--- a/na/mm/slab.cc
+++ b/na/mm/slab.cc
@@ -79,7 +79,7 @@ Page* KmemCache::new_slab()
if (!page)
return nullptr;
- page->state = PageState::Slab;
+ page->type = PageType::Slab;
page->slab.cache = this;
page->slab.obj_count = 0;
@@ -197,7 +197,7 @@ void KmemCache::free(void* obj)
uint64 page_pa = obj_pa & ~(kPageSize - 1);
Page* page = pa_to_page(phys_addr(page_pa));
- if (page->state != PageState::Slab || page->slab.cache != this)
+ if (page->type != PageType::Slab || page->slab.cache != this)
kernel_panic("slab free: object does not belong to this cache");
bool was_on = arch::cpu::intr_enabled();
@@ -274,12 +274,12 @@ void kfree(void* ptr)
uint64 page_pa = pa & ~(kPageSize - 1);
Page* page = pa_to_page(phys_addr(page_pa));
- if (page->state == PageState::Slab) {
+ if (page->type == PageType::Slab) {
KmemCache* cache = page->slab.cache;
if (!cache)
kernel_panic("kfree: slab page has null cache");
cache->free(ptr);
- } else if (page->state == PageState::Allocated) {
+ } else if (page->type == PageType::Allocated) {
/* Large allocation — return directly to PMM */
pmm_free_pages(page, page->order);
} else {
diff --git a/na/mm/tlb.cc b/na/mm/tlb.cc
new file mode 100644
index 0000000..b7a8ba7
--- /dev/null
+++ b/na/mm/tlb.cc
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * tlb.cc — TLB shootdown implementation
+ *
+ * Uses RISC-V SBI Remote Fence extension to perform cross-hart
+ * TLB invalidation. SBI firmware handles the IPI delivery and
+ * synchronization — the call blocks until all target harts have
+ * completed their local sfence.vma.
+ *
+ * When a VmSpace is provided, we use its cpu_mask to target only
+ * the CPUs that have this address space loaded in satp. This avoids
+ * unnecessary IPI storms on unrelated cores.
+ *
+ * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.1
+ */
+
+#include <lume/tlb.h>
+#include <lume/vmm.h>
+#include <lume/config.h>
+#include <arch/mmu.h>
+#include <arch/tlb.h>
+#include <arch/cpu.h>
+
+/*
+ * If a shootdown range exceeds this many pages, do a full flush
+ * instead of per-page invalidation.
+ */
+static constexpr uint64 kShootdownPageThreshold = 32;
+
+/*
+ * Build a cpu mask for all online CPUs (fallback when mm is null).
+ */
+namespace {
+uint64 all_cpus_mask()
+{
+ uint64 mask = 0;
+ for (uint32 i = 0; i < kMaxCpus; i++) {
+ mask |= (1ULL << i);
+ }
+ return mask;
+}
+
+/*
+ * Get the target cpu mask: use mm->cpu_mask when available,
+ * fall back to all_cpus_mask for kernel address space (mm == nullptr).
+ */
+uint64 target_cpu_mask(VmSpace* mm)
+{
+ if (mm) {
+ uint64 mask = mm->cpu_mask.load(__ATOMIC_RELAXED);
+ /* Always include our own CPU — we may still have stale TLB */
+ mask |= (1ULL << arch::cpu::id());
+ return mask;
+ }
+ return all_cpus_mask();
+}
+} // namespace
+
+void tlb_shootdown_range(VmSpace* mm, uint64 start, uint64 end)
+{
+ if (start >= end) {
+ return;
+ }
+
+ uint64 num_pages = (end - start) / kPageSize;
+ uint64 cpu_mask = target_cpu_mask(mm);
+
+ if (num_pages > kShootdownPageThreshold) {
+ /* Full flush on target CPUs via arch backend */
+ arch::tlb::flush_remote(cpu_mask, 0, ~0ULL);
+ return;
+ }
+
+ /* Range flush on target CPUs via arch backend */
+ arch::tlb::flush_remote(cpu_mask, start, end - start);
+}
+
+void tlb_shootdown_page(VmSpace* mm, uint64 va)
+{
+ uint64 cpu_mask = target_cpu_mask(mm);
+ arch::tlb::flush_remote(cpu_mask, va, kPageSize);
+}
+
+void tlb_shootdown_all()
+{
+ uint64 cpu_mask = all_cpus_mask();
+ arch::tlb::flush_remote(cpu_mask, 0, ~0ULL);
+}
diff --git a/na/mm/vma.cc b/na/mm/vma.cc
index 10db65d..da89e08 100644
--- a/na/mm/vma.cc
+++ b/na/mm/vma.cc
@@ -22,7 +22,8 @@
#include <lume/new.h>
#include <lume/klog.h>
#include <arch/pmap.h>
-#include <arch/mmu.h>
+#include <lume/tlb.h>
+#include <lume/mm_layout.h>
/* ------------------------------------------------------------------ */
/* Rbtree callbacks for VMA interval tree */
@@ -65,8 +66,7 @@ static bool range_overlaps(uint64 a_start, uint64 a_end,
return a_start < b_end && b_start < a_end;
}
-static constexpr uint64 kUserVaBase = kPageSize; // Avoid mapping the null page
-static constexpr uint64 kUserVaLimit = 0xFFFFFFC000000000ULL;
+
static bool vma_overlaps_locked(VmSpace* space, uint64 start, uint64 end)
{
@@ -188,11 +188,22 @@ VmSpace* VmSpace::create() {
space->vma_tree = RB_ROOT;
space->lock.init("VmSpace");
+ space->refcount.store(1, __ATOMIC_RELAXED);
space->brk_start = 0;
space->brk_end = 0;
return space;
}
+void VmSpace::incref() {
+ refcount.fetch_add(1, __ATOMIC_RELAXED);
+}
+
+void VmSpace::put() {
+ if (refcount.fetch_sub(1, __ATOMIC_ACQ_REL) == 1) {
+ destroy();
+ }
+}
+
void VmSpace::destroy() {
/* Drain VMA tree under lock, then release BEFORE delete this */
lock.acquire();
@@ -202,11 +213,13 @@ void VmSpace::destroy() {
VmAreaStruct* vma = reinterpret_cast<VmAreaStruct*>(
reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb));
- /* Decref all mapped physical pages */
+ /* Decref and dec mapcount for all mapped physical pages */
for (uint64 addr = vma->start; addr < vma->end; addr += kPageSize) {
uint64 pa;
if (pmap::lookup(root, addr, &pa)) {
- page_decref(pa_to_page(phys_addr(pa)));
+ Page* pg = pa_to_page(phys_addr(pa));
+ page_dec_mapcount(pg);
+ page_decref(pg);
}
}
@@ -285,8 +298,10 @@ int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len) {
for (uint64 addr = unmap_start; addr < unmap_end; addr += kPageSize) {
uint64 pa;
if (pmap::lookup(space->root_pa, addr, &pa)) {
- page_decref(pa_to_page(phys_addr(pa)));
pmap::unmap(space->root_pa, addr);
+ Page* pg = pa_to_page(phys_addr(pa));
+ page_dec_mapcount(pg);
+ page_decref(pg);
}
}
}
@@ -362,7 +377,9 @@ int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm)
}
if (perm == 0) {
- page_decref(pa_to_page(phys_addr(pa)));
+ Page* pg = pa_to_page(phys_addr(pa));
+ page_dec_mapcount(pg);
+ page_decref(pg);
pmap::unmap(space->root_pa, addr);
tlb_flush_needed = true;
continue;
@@ -385,7 +402,7 @@ int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm)
}
if (tlb_flush_needed) {
- arch::mmu::flush_tlb_all();
+ tlb_shootdown_range(space, va, end);
}
return 0;
}
@@ -399,7 +416,11 @@ uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align)
LockGuard guard(space->lock);
- uint64 cursor = align_up_any(kUserVaBase, align);
+ /* Linux: arch_get_unmapped_area() starts from mm->mmap_base.
+ * RISC-V: TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3).
+ * Fall back to kUserVaBase if mmap_base not set (kernel VmSpaces). */
+ uint64 base = space->mmap_base ? space->mmap_base : kUserVaBase;
+ uint64 cursor = align_up_any(base, align);
struct rb_node* node = rb_first(&space->vma_tree);
while (node) {
VmAreaStruct* vma = node_to_vma(node);
@@ -494,22 +515,44 @@ int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause)
return -ENOMEM;
}
- /* Decrement refcount of old page */
- page_decref(pa_to_page(phys_addr(paddr)));
+ /* New page gets mapcount = 1 (we just mapped it) */
+ page_inc_mapcount(page);
+
+ /* Decrement mapcount and refcount of old page */
+ Page* old_page = pa_to_page(phys_addr(paddr));
+ page_dec_mapcount(old_page);
+ page_decref(old_page);
/* Flush TLB to remove old read-only cached translation */
- asm volatile("sfence.vma %0" : : "r"(aligned_addr) : "memory");
+ tlb_shootdown_page(space, aligned_addr);
return 0;
}
+ /* DEBUG: page already mapped but still faulting — dump PTE */
+ {
+ uint64 cur_satp;
+ asm volatile("csrr %0, satp" : "=r"(cur_satp));
+ printk(KERN_ERR "fault: va=%llx pa=%llx cause=%d pte=%llx satp=%llx root=%llx\n",
+ aligned_addr, paddr, static_cast<int>(cause),
+ pmap::lookup_pte(space->root_pa, aligned_addr),
+ cur_satp, space->root_pa);
+ }
return 0; // Already mapped, maybe read fault on existing page
}
- /* Allocate physical page */
+ /* Allocate physical page and zero it.
+ * Linux: do_anonymous_page() uses alloc_zeroed_user_highpage_movable().
+ * Anonymous pages MUST be zeroed — userspace expects MAP_ANONYMOUS
+ * pages to contain all zeros (POSIX requirement). */
Page* page = pmm_alloc_page();
if (!page) return -ENOMEM;
uint64 pa = page_to_pa(page).raw;
+ /* Zero the page before mapping it to userspace */
+ /* Zero the page before mapping it to userspace */
+ uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
+ for (uint64 i = 0; i < kPageSize; i++) kva[i] = 0;
+
/* Translate MI permission flags to hardware PTE bits (MD layer) */
uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm);
@@ -518,7 +561,9 @@ int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause)
return -ENOMEM;
}
- /* Initial mapping, refcount is already 1 from pmm_alloc_page */
+ /* Initial mapping: refcount is already 1 from pmm_alloc_page.
+ * Set mapcount = 1 to track this PTE mapping. */
+ page_inc_mapcount(page);
return 0;
}
@@ -561,7 +606,7 @@ VmSpace* vmm_clone(VmSpace* parent) {
if (child_vma->perm & VM_WRITE) {
pmap::make_cow(parent->root_pa, va);
/* Flush TLB for parent since we reduced permissions */
- asm volatile("sfence.vma %0" : : "r"(va) : "memory");
+ tlb_shootdown_page(parent, va);
}
/* Map in child as COW or read-only */
@@ -580,8 +625,10 @@ VmSpace* vmm_clone(VmSpace* parent) {
return nullptr;
}
- /* Increment physical page refcount */
- page_incref(pa_to_page(phys_addr(pa)));
+ /* Increment physical page refcount and mapcount */
+ Page* shared_page = pa_to_page(phys_addr(pa));
+ page_incref(shared_page);
+ page_inc_mapcount(shared_page);
}
}
diff --git a/na/mm/vmm.cc b/na/mm/vmm.cc
index 0aa2fb0..51617e3 100644
--- a/na/mm/vmm.cc
+++ b/na/mm/vmm.cc
@@ -18,6 +18,7 @@
#include <arch/config.h>
#include <arch/mmu.h>
#include <arch/pmap.h>
+#include <lume/tlb.h>
#include <lume/addr.h>
#include <lume/console.h>
#include <lume/fdt.h>
@@ -28,6 +29,7 @@
#include <lume/pmm.h>
#include <lume/types.h>
#include <lume/vmm.h>
+#include <lume/config.h>
/* Linker-exported section boundaries (virtual addresses) */
extern "C" char _stext[], _etext[];
@@ -36,25 +38,23 @@ extern "C" char _sdata[], _edata[];
extern "C" char _sbss[], _ebss[];
extern "C" char _kernel_end[];
-/* Global kernel page table root PA */
-static uint64 g_kernel_pgtbl = 0;
+/* Global kernel page table root PA — external linkage for task_exit */
+uint64 g_kernel_pgtbl = 0;
-/* VMM exports for PMM: page_map location after vmemmap mapping. */
-static uint64 g_vmemmap_page_map_pa = 0;
-static uint64 g_vmemmap_page_map_size = 0;
-static uint64 g_vmemmap_num_pages = 0;
+namespace {
-uint64 vmm_get_page_map_pa() { return g_vmemmap_page_map_pa; }
-uint64 vmm_get_page_map_size() { return g_vmemmap_page_map_size; }
-uint64 vmm_get_num_pages() { return g_vmemmap_num_pages; }
+/* VMM exports for PMM: page_map location after vmemmap mapping. */
+uint64 g_vmemmap_page_map_pa = 0;
+uint64 g_vmemmap_page_map_size = 0;
+uint64 g_vmemmap_num_pages = 0;
/* Convert a linker symbol to PA (kernel is executing in higher-half VA). */
-static inline uint64 sym_to_pa(const void *sym) {
+inline uint64 sym_to_pa(const void *sym) {
return boot_va_to_pa(virt_addr(reinterpret_cast<uint64>(sym))).raw;
}
/* Map a PA range into the kernel page table via the direct map. */
-static void map_range(uint64 root, uint64 pa_start, uint64 pa_end,
+void map_range(uint64 root, uint64 pa_start, uint64 pa_end,
uint64 perm) {
for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) {
uint64 va = pa_to_va(phys_addr(pa)).raw;
@@ -66,7 +66,7 @@ static void map_range(uint64 root, uint64 pa_start, uint64 pa_end,
}
/* Map a PA range into the kernel page table at the fixed linked VA. */
-static void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end,
+void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end,
uint64 perm) {
for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) {
uint64 va = boot_pa_to_va(phys_addr(pa)).raw;
@@ -78,7 +78,7 @@ static void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end,
}
/* Map a PA range to an explicit VA base, using 2MB pages where possible. */
-static void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start,
+void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start,
uint64 pa_end, uint64 perm) {
constexpr uint64 k2MB = 2ULL * 1024 * 1024;
uint64 pa = pa_start;
@@ -109,6 +109,12 @@ static void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start,
}
}
+} // namespace
+
+uint64 vmm_get_page_map_pa() { return g_vmemmap_page_map_pa; }
+uint64 vmm_get_page_map_size() { return g_vmemmap_page_map_size; }
+uint64 vmm_get_num_pages() { return g_vmemmap_num_pages; }
+
void vmm_init() {
/* 1. Allocate new root page table (from memblock) */
uint64 root = pmap::create();
@@ -137,17 +143,36 @@ void vmm_init() {
*/
map_range_linked(root, text_start, text_end, pmap::PTE_R | pmap::PTE_X);
- /* 4. Map .rodata (R) at fixed linked VA */
- map_range_linked(root, ro_start, ro_end, pmap::PTE_R);
+ /* 4. Map .rodata (R) at fixed linked VA.
+ * We map [_srodata, _sdata) so that the .lume.reloc section (which
+ * sits in its own padded output section between _erodata and _sdata)
+ * is also covered. Relocator only reads it during early boot, but
+ * mapping it keeps post-vmm accesses to __lume_reloc_* symbols sane. */
+ map_range_linked(root, ro_start, page_align_down(sym_to_pa(_sdata)),
+ pmap::PTE_R);
/* 5. Map .data + .bss (RW) at fixed linked VA */
map_range_linked(root, data_start, bss_end, pmap::PTE_R | pmap::PTE_W);
- /* 6. Map remaining physical memory after kernel (RW) */
- uint64 after_kernel = page_align_up(sym_to_pa(_kernel_end));
- if (after_kernel < mem_end) {
- map_range(root, after_kernel, mem_end, pmap::PTE_R | pmap::PTE_W);
+ /* 6. Map usable physical RAM into the KASLR-slid direct map.
+ *
+ * The direct map provides pa_to_va() / va_to_pa() for the physical
+ * address space. We map from kPhysStart (the kernel's linked load
+ * address), skipping [mem_base, kPhysStart) because that region is
+ * PMP-protected firmware (OpenSBI) — S-mode cannot access it.
+ *
+ * Linux does the same on RISC-V: the linear map covers only the
+ * usable RAM range, not firmware-reserved regions.
+ *
+ * Uses 2MB huge pages for efficiency (64 entries for 128MB vs 32K).
+ */
+ uint64 dm_start = kPhysStart;
+ if (dm_start < mem_base) {
+ dm_start = mem_base;
}
+ uint64 dm_va_base = pa_to_va(phys_addr(dm_start)).raw;
+ map_range_at_huge(root, dm_va_base, dm_start, mem_end,
+ pmap::PTE_R | pmap::PTE_W);
/* 7. Allocate page_map from memblock (top of RAM) and map at VMEMMAP.
*
@@ -206,5 +231,5 @@ void vmm_map_kernel_mmio(uint64 pa, uint64 size) {
map_range(g_kernel_pgtbl, aligned_pa, aligned_pa + aligned_size,
pmap::PTE_R | pmap::PTE_W | pmap::PTE_G);
- arch::mmu::flush_tlb_all();
+ tlb_shootdown_all();
}