summaryrefslogtreecommitdiff
path: root/mm/memblock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'mm/memblock.cc')
-rw-r--r--mm/memblock.cc257
1 files changed, 257 insertions, 0 deletions
diff --git a/mm/memblock.cc b/mm/memblock.cc
new file mode 100644
index 0000000..aee0245
--- /dev/null
+++ b/mm/memblock.cc
@@ -0,0 +1,257 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * memblock.cc — Boot-time physical memory allocator
+ *
+ * Simple region tracker: one memory region (from FDT) plus an array
+ * of reserved regions. Allocation searches for gaps between reserved
+ * regions. All operations are O(n_reserved), which is fine for the
+ * ~20 allocations that happen during early boot.
+ *
+ * Reference: inspired by Linux kernel's mm/memblock.c
+ */
+
+#include <lume/memblock.h>
+#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[];
+
+/* ------------------------------------------------------------------ */
+/* Module state */
+/* ------------------------------------------------------------------ */
+
+namespace {
+
+constexpr uint64 kMB = 1024ULL * 1024ULL;
+
+struct {
+ uint64 mem_base;
+ uint64 mem_size;
+
+ struct {
+ uint64 base;
+ uint64 size;
+ } reserved[kMemblockMaxRegions];
+ int reserved_count;
+ bool active;
+} g_mb;
+
+/* ------------------------------------------------------------------ */
+/* Internal helpers */
+/* ------------------------------------------------------------------ */
+
+/* Insert a reserved region, keeping the array sorted by base. */
+void insert_reserved(uint64 base, uint64 size)
+{
+ if (g_mb.reserved_count >= kMemblockMaxRegions) {
+ kernel_panic("memblock: too many reserved regions");
+ }
+
+ int i = g_mb.reserved_count;
+ while (i > 0 && g_mb.reserved[i - 1].base > base) {
+ g_mb.reserved[i] = g_mb.reserved[i - 1];
+ i--;
+ }
+ g_mb.reserved[i] = {base, size};
+ g_mb.reserved_count++;
+}
+
+/* Check if [base, base+size) overlaps any reserved region. */
+bool overlaps_reserved(uint64 base, uint64 size)
+{
+ uint64 end = base + size;
+ for (int i = 0; i < g_mb.reserved_count; i++) {
+ uint64 rb = g_mb.reserved[i].base;
+ uint64 re = rb + g_mb.reserved[i].size;
+ if (base < re && end > rb) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Find the end of the first reserved region that covers `addr`. */
+uint64 reserved_end_covering(uint64 addr)
+{
+ for (int i = 0; i < g_mb.reserved_count; i++) {
+ uint64 rb = g_mb.reserved[i].base;
+ uint64 re = rb + g_mb.reserved[i].size;
+ if (addr >= rb && addr < re) {
+ return re;
+ }
+ }
+ return addr;
+}
+
+} // namespace
+
+/* ------------------------------------------------------------------ */
+/* Public API */
+/* ------------------------------------------------------------------ */
+
+void memblock_init(uint64 fdt_paddr)
+{
+
+ /* 1. Get memory layout from FDT */
+ uint64 mem_base = 0;
+ uint64 mem_size = 0;
+ fdt_early_get_mem_info(&mem_base, &mem_size);
+ if (mem_size == 0) {
+ kernel_panic("memblock_init: FDT reports zero memory");
+ }
+
+ g_mb.mem_base = mem_base;
+ g_mb.mem_size = mem_size;
+ g_mb.reserved_count = 0;
+ g_mb.active = true;
+
+ /* 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 / kMB);
+ kprintf("[memblock] kernel reserved [0x%llx, 0x%llx)\n", ks_pa, ke_pa);
+}
+
+void memblock_reserve(uint64 base, uint64 size)
+{
+ uint64 aligned_base = page_align_down(base);
+ uint64 aligned_end = page_align_up(base + size);
+ insert_reserved(aligned_base, aligned_end - aligned_base);
+}
+
+uint64 memblock_alloc_top(uint64 size, uint64 align)
+{
+ uint64 mem_end = g_mb.mem_base + g_mb.mem_size;
+
+ /* Search from top of RAM downward */
+ uint64 end = page_align_down(mem_end);
+ uint64 start = (end - size) & ~(align - 1);
+
+ while (start >= g_mb.mem_base && start + size <= mem_end) {
+ if (!overlaps_reserved(start, size)) {
+ memblock_reserve(start, size);
+ return start;
+ }
+ if (start < align) {
+ break;
+ }
+ start -= align;
+ }
+
+ kernel_panic("memblock_alloc_top: out of memory");
+ return 0;
+}
+
+uint64 memblock_alloc(uint64 size, uint64 align)
+{
+ uint64 mem_end = g_mb.mem_base + g_mb.mem_size;
+ uint64 start = page_align_up(g_mb.mem_base);
+
+ while (start + size <= mem_end) {
+ start = (start + align - 1) & ~(align - 1);
+ if (start + size > mem_end) {
+ break;
+ }
+
+ if (!overlaps_reserved(start, size)) {
+ memblock_reserve(start, size);
+ return start;
+ }
+
+ /* Skip past the overlapping reserved region */
+ uint64 skip = reserved_end_covering(start);
+ if (skip <= start) {
+ start += align;
+ } else {
+ start = skip;
+ }
+ }
+
+ kernel_panic("memblock_alloc: out of memory");
+ return 0;
+}
+
+uint64 memblock_get_mem_base() { return g_mb.mem_base; }
+uint64 memblock_get_mem_size() { return g_mb.mem_size; }
+
+bool memblock_is_active() { return g_mb.active; }
+void memblock_retire() { g_mb.active = false; }
+
+void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx),
+ void* ctx)
+{
+ uint64 pos = g_mb.mem_base;
+ uint64 mem_end = g_mb.mem_base + g_mb.mem_size;
+
+ for (int i = 0; i < g_mb.reserved_count && pos < mem_end; i++) {
+ uint64 rb = g_mb.reserved[i].base;
+ if (rb < pos) {
+ uint64 re = g_mb.reserved[i].base + g_mb.reserved[i].size;
+ if (re > pos) {
+ pos = re;
+ }
+ continue;
+ }
+ if (rb > pos) {
+ cb(pos, rb - pos, ctx);
+ }
+ pos = g_mb.reserved[i].base + g_mb.reserved[i].size;
+ }
+
+ if (pos < mem_end) {
+ cb(pos, mem_end - pos, ctx);
+ }
+}