summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--na/arch/riscv/config.cc59
-rw-r--r--na/arch/riscv/include/arch/config.h40
-rw-r--r--na/arch/riscv/mm/pmap.cc12
-rw-r--r--na/drivers/uart.cc17
-rw-r--r--na/fdt/fdt.cc9
-rw-r--r--na/include/lume/addr.h17
-rw-r--r--na/include/lume/config.h57
-rw-r--r--na/include/lume/console.h7
-rw-r--r--na/include/lume/fdt.h1
-rw-r--r--na/include/lume/pmm.h3
-rw-r--r--na/mm/pmm.cc32
-rw-r--r--na/mm/vmm.cc40
12 files changed, 258 insertions, 36 deletions
diff --git a/na/arch/riscv/config.cc b/na/arch/riscv/config.cc
index 139d932..9b4cc6e 100644
--- a/na/arch/riscv/config.cc
+++ b/na/arch/riscv/config.cc
@@ -1,10 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2026 Richard Qin */
+/*
+ * RISC-V Architecture Configuration — runtime state and KASLR
+ *
+ * Provides the runtime (post-KASLR) values of the higher-half
+ * virtual address bases. kaslr_init() is called once during
+ * early boot, before vmm_init(), to slide these bases by a
+ * random, 1GB-aligned offset.
+ *
+ * KASLR slide budget:
+ * The default Direct Map starts at 0xFFFFFFC0_0000_0000 (VPN[2]=256).
+ * The default Vmemmap starts at 0xFFFFFFD0_0000_0000 (VPN[2]=320).
+ * A 1GB superpage occupies one VPN[2] slot. We allow a slide of
+ * 0..63 slots (0..63 GB) downward, which keeps both regions inside
+ * the upper canonical half and avoids colliding with the kernel
+ * text region (linked at VPN[2] = 258 + 2 = ~0xFFFFFFC0_8020_0000).
+ *
+ * In practice, with 128MB RAM the kernel+direct map only touches
+ * VPN[2] 258-259, so a slide of up to 63GB is safe.
+ */
+
#include <arch/config.h>
+#include <lume/random.h>
+#include <lume/kprintf.h>
namespace arch {
+/* Runtime bases — default to the compile-time constants. */
uint64 g_direct_map_base = kDirectMapBaseDefault;
+uint64 g_vmemmap_base = kVmemmapBaseDefault;
+
+void kaslr_init()
+{
+#ifdef CONFIG_KASLR
+ /*
+ * Generate a random 1GB-aligned slide.
+ *
+ * Strategy (matching Linux's approach):
+ * 1. Pull 64 random bits from the early PRNG.
+ * 2. Reduce to a slot number in [0, kMaxSlots).
+ * 3. Convert to a byte offset (slot * 1GB).
+ * 4. Add to both bases (slide upward in VA space).
+ *
+ * Why 1GB alignment?
+ * SV39 L2 (root) PTEs map 1GB superpages. Keeping the slide
+ * 1GB-aligned means the direct-map can still use gigapages,
+ * avoiding TLB pressure. This matches Linux's KASLR on RISC-V.
+ */
+ constexpr uint64 k1GB = 1ULL << 30;
+ constexpr uint64 kMaxSlots = 64; /* 0..63 GB slide range */
+
+ uint64 raw = get_random_u64();
+ uint64 slot = raw % kMaxSlots;
+ uint64 slide = slot * k1GB;
+
+ g_direct_map_base = kDirectMapBaseDefault + slide;
+ g_vmemmap_base = kVmemmapBaseDefault + slide;
+
+ kprintf("[kaslr] slide = %llu GB (slot %llu/64)\n", slide >> 30, slot);
+ kprintf("[kaslr] direct_map_base = 0x%llx\n", g_direct_map_base);
+ kprintf("[kaslr] vmemmap_base = 0x%llx\n", g_vmemmap_base);
+#else
+ kprintf("[kaslr] CONFIG_KASLR disabled, using fixed layout\n");
+#endif
+}
} // namespace arch
diff --git a/na/arch/riscv/include/arch/config.h b/na/arch/riscv/include/arch/config.h
index c407b25..e7b1027 100644
--- a/na/arch/riscv/include/arch/config.h
+++ b/na/arch/riscv/include/arch/config.h
@@ -8,21 +8,49 @@
*
* These constants define the architecture-specific memory layout.
* Each arch/ implementation must provide this header with the same symbols.
+ *
+ * KASLR: At boot, kaslr_init() randomises g_direct_map_base and
+ * g_vmemmap_base. All MI code must read these runtime variables
+ * (via pa_to_va / vmm helpers) rather than the static defaults.
*/
#include <lume/types.h>
namespace arch {
-/* Direct-map virtual base (higher-half base).
- * SV39: top of 39-bit VA space. VA = PA + direct_map_base. */
+/* ================================================================
+ * Default (compile-time) virtual addresses
+ *
+ * SV39 gives a 512GB virtual address space.
+ * Higher-half layout (defaults before KASLR):
+ *
+ * 0xFFFFFFC0_00000000 (256) Direct map (PA+offset)
+ * 0xFFFFFFD0_00000000 (320) Vmemmap (Frame array)
+ *
+ * The 64GB gap between them is the KASLR slide range.
+ * ================================================================ */
inline constexpr uint64 kDirectMapBaseDefault = 0xFFFFFFC000000000ULL;
-extern uint64 g_direct_map_base;
-
-/* Dedicated VA base for the Frame descriptor array (vmemmap). */
-inline constexpr uint64 kVmemmapBase = 0xFFFFFFD000000000ULL;
+inline constexpr uint64 kVmemmapBaseDefault = 0xFFFFFFD000000000ULL;
/* Physical memory base (where RAM starts on RISC-V virt platform) */
inline constexpr uint64 kPhysBase = 0x80000000ULL;
+/* ================================================================
+ * Runtime (post-KASLR) virtual addresses
+ *
+ * These start equal to the defaults above. If CONFIG_KASLR is
+ * enabled, kaslr_init() adds a random, 1GB-aligned offset
+ * to each, sliding both regions upward in VA space.
+ * ================================================================ */
+extern uint64 g_direct_map_base;
+extern uint64 g_vmemmap_base;
+
+/* ================================================================
+ * KASLR entry point
+ *
+ * Must be called AFTER random_early_init() and BEFORE vmm_init().
+ * When CONFIG_KASLR is disabled this is a no-op.
+ * ================================================================ */
+void kaslr_init();
+
} // namespace arch
diff --git a/na/arch/riscv/mm/pmap.cc b/na/arch/riscv/mm/pmap.cc
index 4ed3bca..dafbeaf 100644
--- a/na/arch/riscv/mm/pmap.cc
+++ b/na/arch/riscv/mm/pmap.cc
@@ -24,6 +24,12 @@
namespace pmap {
+/*
+ * Before the first pmap::activate(), CPU runs on entry.S bootstrap page table
+ * (fixed direct-map base). After activation, runtime (KASLR) base is valid.
+ */
+static bool g_runtime_mapping_ready = false;
+
/* PTE bit definitions */
inline constexpr uint64 PTE_V = 1ULL << 0;
inline constexpr uint64 PTE_R = 1ULL << 1;
@@ -59,7 +65,10 @@ static inline uint64 pa_to_pte(uint64 pa, uint64 flags)
/* Convert PA to kernel VA for accessing page table contents */
static inline uint64 *pa_to_ptr(uint64 pa)
{
- return reinterpret_cast<uint64 *>(pa_to_va(phys_addr(pa)).raw);
+ uint64 va = g_runtime_mapping_ready
+ ? pa_to_va(phys_addr(pa)).raw
+ : boot_pa_to_va(phys_addr(pa)).raw;
+ return reinterpret_cast<uint64 *>(va);
}
/* Allocate a zeroed page for a page table level.
@@ -203,6 +212,7 @@ void activate(uint64 root_pa)
{
arch::mmu::set_page_table(root_pa);
arch::mmu::flush_tlb_all();
+ g_runtime_mapping_ready = true;
}
uint64 vm_perm_to_pte(uint64 vm_perm)
diff --git a/na/drivers/uart.cc b/na/drivers/uart.cc
index 6a35588..2e553d6 100644
--- a/na/drivers/uart.cc
+++ b/na/drivers/uart.cc
@@ -15,10 +15,21 @@
static uint64 g_uart_pa = 0;
static uint64 g_uart_size = 0;
+static bool g_runtime_mapping_ready = false;
extern "C" void early_putc(char c) {
if (!g_uart_pa) return;
- volatile auto* uart = reinterpret_cast<volatile uint8*>(pa_to_va(phys_addr(g_uart_pa)).raw);
+
+ /*
+ * Before vmm_init() activates the final page table, UART MMIO is only
+ * reachable via the bootstrap fixed direct map from entry.S.
+ * After activation, switch to the runtime (possibly KASLR-slid) base.
+ */
+ uint64 uart_va = g_runtime_mapping_ready
+ ? pa_to_va(phys_addr(g_uart_pa)).raw
+ : boot_pa_to_va(phys_addr(g_uart_pa)).raw;
+
+ volatile auto* uart = reinterpret_cast<volatile uint8*>(uart_va);
*uart = static_cast<uint8>(c);
}
@@ -45,3 +56,7 @@ void console_init() {
vmm_map_kernel_mmio(g_uart_pa, g_uart_size);
}
}
+
+void console_use_runtime_mapping() {
+ g_runtime_mapping_ready = true;
+}
diff --git a/na/fdt/fdt.cc b/na/fdt/fdt.cc
index ef614e4..fbec245 100644
--- a/na/fdt/fdt.cc
+++ b/na/fdt/fdt.cc
@@ -126,7 +126,7 @@ static inline uint64 read_cells(const uint8 *data, uint32 cells) {
/* ------------------------------------------------------------------ */
FdtManager::FdtManager(uint64 fdt_paddr)
- : fdt_paddr_(pa_to_va(phys_addr(fdt_paddr)).raw) {}
+ : fdt_paddr_(fdt_paddr) {}
/* ------------------------------------------------------------------ */
/* Phase 1: Early scan — memory layout */
@@ -352,14 +352,15 @@ void FdtManager::early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr,
/* ------------------------------------------------------------------ */
void FdtManager::unflatten() {
- const fdt_header *hdr = reinterpret_cast<const fdt_header *>(fdt_paddr_);
+ const uint64 fdt_va = pa_to_va(phys_addr(fdt_paddr_)).raw;
+ const fdt_header *hdr = reinterpret_cast<const fdt_header *>(fdt_va);
if (bswap32(hdr->magic) != FDT_MAGIC)
kernel_panic("fdt unflatten: invalid magic", nullptr);
const uint8 *dt_struct =
- reinterpret_cast<const uint8 *>(fdt_paddr_) + bswap32(hdr->off_dt_struct);
+ reinterpret_cast<const uint8 *>(fdt_va) + bswap32(hdr->off_dt_struct);
const char *dt_strings =
- reinterpret_cast<const char *>(fdt_paddr_) + bswap32(hdr->off_dt_strings);
+ reinterpret_cast<const char *>(fdt_va) + bswap32(hdr->off_dt_strings);
uint32 offset = 0;
diff --git a/na/include/lume/addr.h b/na/include/lume/addr.h
index 9c20d66..572b6fe 100644
--- a/na/include/lume/addr.h
+++ b/na/include/lume/addr.h
@@ -29,6 +29,23 @@ inline PhysAddr va_to_pa(VirtAddr va)
return PhysAddr{va.raw - arch::g_direct_map_base};
}
+/*
+ * Early-boot conversions pinned to the bootstrap direct-map base.
+ *
+ * Use these only before vmm_init() activates the final kernel page table.
+ * They are required when KASLR has already randomized g_direct_map_base,
+ * but the CPU is still running on the bootstrap mapping built in entry.S.
+ */
+inline VirtAddr boot_pa_to_va(PhysAddr pa)
+{
+ return VirtAddr{pa.raw + arch::kDirectMapBaseDefault};
+}
+
+inline PhysAddr boot_va_to_pa(VirtAddr va)
+{
+ return PhysAddr{va.raw - arch::kDirectMapBaseDefault};
+}
+
/* Align up to page boundary */
inline uint64 page_align_up(uint64 addr)
{
diff --git a/na/include/lume/config.h b/na/include/lume/config.h
index acd76ef..40787cc 100644
--- a/na/include/lume/config.h
+++ b/na/include/lume/config.h
@@ -12,12 +12,59 @@
* This header is safe to include from both C++ and assembly files.
* Assembly-visible constants use #define; C++ code gets typed constexpr.
*
+ * Values are injected by the build system via -DCONFIG_xxx flags
+ * (see scripts/Kconfig.mk). The #ifndef guards below provide
+ * safe defaults so the header is self-contained for IDE analysis.
+ *
* Reference: docs/specs/00_conventions.md §1.3
*/
-/* These macros are shared with assembly (entry.S) */
-#define LUME_MAX_CPUS 8
-#define LUME_PAGE_SIZE 4096
+/* ================================================================
+ * Defaults — only used when CONFIG_xxx is NOT supplied by -D flag.
+ * In a normal build, scripts/Kconfig.mk always provides them.
+ * ================================================================ */
+
+#ifndef CONFIG_NR_CPUS
+#define CONFIG_NR_CPUS 8
+#endif
+
+#ifndef CONFIG_PAGE_SIZE
+#define CONFIG_PAGE_SIZE 4096
+#endif
+
+#ifndef CONFIG_MAX_ORDER
+#define CONFIG_MAX_ORDER 11
+#endif
+
+#ifndef CONFIG_KERNEL_STACK_SIZE
+#define CONFIG_KERNEL_STACK_SIZE 8192
+#endif
+
+#ifndef CONFIG_HZ
+#define CONFIG_HZ 100
+#endif
+
+#ifndef CONFIG_NR_PRIORITIES
+#define CONFIG_NR_PRIORITIES 32
+#endif
+
+#ifndef CONFIG_DEFAULT_PRIORITY
+#define CONFIG_DEFAULT_PRIORITY (CONFIG_NR_PRIORITIES / 2)
+#endif
+
+#ifndef CONFIG_IDLE_PRIORITY
+#define CONFIG_IDLE_PRIORITY (CONFIG_NR_PRIORITIES - 1)
+#endif
+
+#ifndef CONFIG_DEFAULT_TIMESLICE
+#define CONFIG_DEFAULT_TIMESLICE (CONFIG_HZ / 10)
+#endif
+
+/* ================================================================
+ * Legacy compatibility aliases (shared with assembly)
+ * ================================================================ */
+#define LUME_MAX_CPUS CONFIG_NR_CPUS
+#define LUME_PAGE_SIZE CONFIG_PAGE_SIZE
#ifndef __ASSEMBLER__
@@ -26,9 +73,9 @@
/* Maximum number of CPUs supported by the kernel.
* Compile-time limit for static arrays (boot stacks, per-cpu data).
* Actual CPU count is detected at runtime via FDT. */
-inline constexpr int kMaxCpus = LUME_MAX_CPUS;
+inline constexpr int kMaxCpus = CONFIG_NR_CPUS;
/* Virtual memory page size (4KB, matching SV39 base page). */
-inline constexpr uint64 kPageSize = LUME_PAGE_SIZE;
+inline constexpr uint64 kPageSize = CONFIG_PAGE_SIZE;
#endif /* __ASSEMBLER__ */ \ No newline at end of file
diff --git a/na/include/lume/console.h b/na/include/lume/console.h
index ff9f019..df129cf 100644
--- a/na/include/lume/console.h
+++ b/na/include/lume/console.h
@@ -13,3 +13,10 @@ void console_early_init(uint64 fdt_paddr);
/* Full console initialization. Maps MMIO. */
void console_init();
+
+/*
+ * Notify console that the runtime kernel page table is active.
+ * After this point early_putc() must use runtime direct-map base
+ * instead of the bootstrap fixed base.
+ */
+void console_use_runtime_mapping();
diff --git a/na/include/lume/fdt.h b/na/include/lume/fdt.h
index 63d185b..626cc48 100644
--- a/na/include/lume/fdt.h
+++ b/na/include/lume/fdt.h
@@ -82,3 +82,4 @@ DeviceNode *fdt_get_node_by_path(const char *path);
// Early scan wrappers (delegate to FdtManager static methods).
void fdt_early_get_mem_info(uint64 *base, uint64 *size);
void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size);
+void fdt_early_get_kaslr_seed(uint64 *seed);
diff --git a/na/include/lume/pmm.h b/na/include/lume/pmm.h
index a9249ac..266a83c 100644
--- a/na/include/lume/pmm.h
+++ b/na/include/lume/pmm.h
@@ -22,9 +22,10 @@
#include <lume/types.h>
#include <lume/frame.h>
#include <lume/addr_types.h>
+#include <lume/config.h>
/* Buddy system constants */
-inline constexpr int kMaxOrder = 11; // Orders 0..10 → 4KB..4MB
+inline constexpr int kMaxOrder = CONFIG_MAX_ORDER; // Orders 0..(N-1)
/* Per-CPU cache dynamic parameters (calculated in pmm_init) */
extern uint32 g_pcp_high_watermark;
diff --git a/na/mm/pmm.cc b/na/mm/pmm.cc
index 1400448..633bd2f 100644
--- a/na/mm/pmm.cc
+++ b/na/mm/pmm.cc
@@ -7,11 +7,9 @@
* Buddy allocator with Per-CPU single-page caches (PCP).
*
* Initialization sequence (called by BSP in single-core period):
- * 1. Query FDT for physical memory base and size.
- * 2. Carve out space for the frame_map array at the start of free memory
- * (immediately after _kernel_end, page-aligned).
- * 3. Initialize all Frame descriptors as Free.
- * 4. Feed free frames into the buddy system at the highest possible order.
+ * 1. memblock + vmm_init allocate and map the frame_map at VMEMMAP.
+ * 2. Zero and mark all Frame descriptors reserved by default.
+ * 3. Carve free regions from memblock into the buddy system.
*
* The frame_map is a flat array indexed by physical page frame number (PFN).
* PFN = (pa - mem_base) / PAGE_SIZE. This keeps address conversion O(1).
@@ -68,17 +66,26 @@ uint32 g_pcp_batch_size = 0;
* PFN = (pa - g_mem_base) / kPageSize
*/
static inline uint64 pa_to_frame_index(uint64 pa) {
+ if (pa < g_mem_base || pa >= g_mem_base + g_num_frames * kPageSize)
+ kernel_panic("pa_to_frame: physical address outside managed range");
return (pa - g_mem_base) / kPageSize;
}
static inline uint64 frame_index_to_pa(uint64 idx) {
+ if (idx >= g_num_frames)
+ kernel_panic("frame_to_pa: frame index outside managed range");
return g_mem_base + idx * kPageSize;
}
PhysAddr frame_to_pa(const Frame *frame) {
- uint64 idx = static_cast<uint64>(frame - g_frame_map);
- if (frame < g_frame_map || idx >= g_num_frames)
+ uintptr base = reinterpret_cast<uintptr>(g_frame_map);
+ uintptr addr = reinterpret_cast<uintptr>(frame);
+ uintptr end = base + g_num_frames * sizeof(Frame);
+
+ if (addr < base || addr >= end)
kernel_panic("frame_to_pa: frame outside frame_map bounds");
+
+ uint64 idx = (addr - base) / sizeof(Frame);
return phys_addr(frame_index_to_pa(idx));
}
@@ -391,11 +398,20 @@ void pmm_init() {
* No relocation needed — frame_map starts life at the right VA. */
g_frame_map_pa = vmm_get_frame_map_pa();
g_frame_map_bytes = vmm_get_frame_map_size();
- g_frame_map = reinterpret_cast<Frame *>(arch::kVmemmapBase);
+ g_frame_map = reinterpret_cast<Frame *>(arch::g_vmemmap_base);
/* 4. Zero out the entire frame_map */
zero_range(g_frame_map, g_frame_map_bytes);
+ /* 4b. Default every frame descriptor to reserved/non-free. */
+ for (uint64 i = 0; i < g_num_frames; i++) {
+ Frame *f = &g_frame_map[i];
+ f->refcount.store(0, __ATOMIC_RELAXED);
+ f->state = FrameState::Allocated;
+ f->order = 0;
+ f->free.free_link.init();
+ }
+
/* 5. Initialize buddy free list heads */
g_pmm_lock.init("pmm");
for (int i = 0; i < kMaxOrder; i++) {
diff --git a/na/mm/vmm.cc b/na/mm/vmm.cc
index 041413d..c1436d1 100644
--- a/na/mm/vmm.cc
+++ b/na/mm/vmm.cc
@@ -50,7 +50,7 @@ uint64 vmm_get_num_frames() { return g_vmemmap_num_frames; }
/* Convert a linker symbol to PA (kernel is executing in higher-half VA). */
static inline uint64 sym_to_pa(const void *sym) {
- return va_to_pa(virt_addr(reinterpret_cast<uint64>(sym))).raw;
+ 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. */
@@ -65,6 +65,18 @@ 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,
+ uint64 perm) {
+ for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) {
+ uint64 va = boot_pa_to_va(phys_addr(pa)).raw;
+ int ret = pmap::map(root, va, pa, perm | pmap::PTE_G);
+ if (ret != 0) {
+ kernel_panic("vmm_init: pmap::map (linked) failed");
+ }
+ }
+}
+
/* 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,
uint64 pa_end, uint64 perm) {
@@ -117,14 +129,19 @@ void vmm_init() {
uint64 mem_size = memblock_get_mem_size();
uint64 mem_end = mem_base + mem_size;
- /* 3. Map kernel .text (RX) */
- map_range(root, text_start, text_end, pmap::PTE_R | pmap::PTE_X);
+ /* 3. Map kernel image at FIXED linked VA.
+ *
+ * Kernel text/data are linked at the bootstrap higher-half addresses
+ * (kDirectMapBaseDefault + PA). They must remain executable/readable at
+ * those addresses until full kernel-image relocation is implemented.
+ */
+ map_range_linked(root, text_start, text_end, pmap::PTE_R | pmap::PTE_X);
- /* 4. Map .rodata (R) */
- map_range(root, ro_start, ro_end, pmap::PTE_R);
+ /* 4. Map .rodata (R) at fixed linked VA */
+ map_range_linked(root, ro_start, ro_end, pmap::PTE_R);
- /* 5. Map .data + .bss (RW) */
- map_range(root, data_start, bss_end, pmap::PTE_R | pmap::PTE_W);
+ /* 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));
@@ -150,8 +167,8 @@ void vmm_init() {
kprintf("[vmm] vmemmap: %llu frames, %llu KB at PA 0x%llx\n", num_frames,
frame_map_span / 1024, frame_map_pa);
- /* Map frame_map at kVmemmapBase, using 2MB pages where possible */
- map_range_at_huge(root, arch::kVmemmapBase, frame_map_pa,
+ /* Map frame_map at g_vmemmap_base (randomised by KASLR), using 2MB pages where possible */
+ map_range_at_huge(root, arch::g_vmemmap_base, frame_map_pa,
frame_map_pa + frame_map_span, pmap::PTE_R | pmap::PTE_W);
/* Export for pmm_init() */
@@ -166,7 +183,10 @@ void vmm_init() {
/* 9. Activate the new page table */
pmap::activate(root);
- kprintf("[vmm] vmm_init complete (vmemmap at 0x%llx)\n", arch::kVmemmapBase);
+ /* Console MMIO access must now use runtime (KASLR) direct-map base. */
+ console_use_runtime_mapping();
+
+ kprintf("[vmm] vmm_init complete (vmemmap at 0x%llx)\n", arch::g_vmemmap_base);
}
void vmm_init_ap() {