diff --git a/Makefile b/Makefile index 643a050..d5afe90 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ TARGET = BOOTX64.EFI TARGET_SO = bootx64.so OBJ = $(BUILD_DIR)/main.o KERNEL_TARGET = kernel.elf -KERNEL_OBJS = $(BUILD_DIR)/kernel.o $(BUILD_DIR)/string_utils.o $(BUILD_DIR)/commands.o $(BUILD_DIR)/idt.o $(BUILD_DIR)/isr.o +KERNEL_OBJS = $(BUILD_DIR)/kernel.o $(BUILD_DIR)/string_utils.o $(BUILD_DIR)/commands.o $(BUILD_DIR)/idt.o $(BUILD_DIR)/isr.o $(BUILD_DIR)/memory.o KERNEL_LD = kernel.ld # QEMU settings @@ -121,6 +121,11 @@ $(BUILD_DIR)/isr.o: $(SRC_DIR)/isr.S $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ +$(BUILD_DIR)/memory.o: $(SRC_DIR)/memory.c + @echo "Compiling memory.c..." + $(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \ + -mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@ + # Link kernel ELF $(BUILD_DIR)/$(KERNEL_TARGET): $(KERNEL_OBJS) $(KERNEL_LD) @echo "Linking kernel ELF..." diff --git a/boot_info.h b/boot_info.h index 1c44c1f..8b8bf27 100644 --- a/boot_info.h +++ b/boot_info.h @@ -12,6 +12,8 @@ typedef struct { EFI_STATUS (*set_attribute)(UINTN Attribute); EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key); void (*shutdown)(void); + EFI_STATUS (*alloc_pages)(UINTN pages, EFI_PHYSICAL_ADDRESS *addr); + EFI_STATUS (*free_pages)(EFI_PHYSICAL_ADDRESS addr, UINTN pages); } BootInfo; typedef void (*KernelEntryFn)(BootInfo *Boot); diff --git a/commands.c b/commands.c index 67b13c4..97105ad 100644 --- a/commands.c +++ b/commands.c @@ -1,6 +1,7 @@ #include #include "commands.h" #include "string_utils.h" +#include "memory.h" #define SAFE_PRINT(Boot, ...) \ do { \ @@ -15,6 +16,7 @@ static void cmd_help(BootInfo *Boot, CHAR16 *Args); static void cmd_man(BootInfo *Boot, CHAR16 *Args); static void cmd_clear(BootInfo *Boot, CHAR16 *Args); static void cmd_about(BootInfo *Boot, CHAR16 *Args); +static void cmd_mem(BootInfo *Boot, CHAR16 *Args); // Command registry static Command commands[] = { @@ -48,6 +50,12 @@ static Command commands[] = { L"Usage: about\n\r Shows information about this operating system.", cmd_about }, + { + L"mem", + L"Display memory statistics", + L"Usage: mem\n\r Shows physical memory, heap, and paging information.", + cmd_mem + }, {NULL, NULL, NULL, NULL} // Sentinel }; @@ -144,12 +152,19 @@ static void cmd_about(BootInfo *Boot, CHAR16 *Args) SAFE_PRINT(Boot, L" - UEFI Boot\n\r"); SAFE_PRINT(Boot, L" - Console I/O\n\r"); SAFE_PRINT(Boot, L" - ELF64 Kernel Loader\n\r"); + SAFE_PRINT(Boot, L" - Memory Management (PMM + Heap)\n\r"); SAFE_PRINT(Boot, L" - Interactive Command Interface\n\r"); SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L"Type 'help' for available commands.\n\r"); SAFE_PRINT(Boot, L"\n\r"); } +static void cmd_mem(BootInfo *Boot, CHAR16 *Args) +{ + (void)Args; + memory_print_stats(Boot); +} + void show_help(BootInfo *Boot) { UINTN i = 0; diff --git a/kernel.c b/kernel.c index ffb6d3f..26333b8 100644 --- a/kernel.c +++ b/kernel.c @@ -3,6 +3,7 @@ #include "boot_info.h" #include "commands.h" #include "idt.h" +#include "memory.h" #define SAFE_PRINT(Boot, ...) \ do { \ @@ -36,6 +37,7 @@ void kmain(BootInfo *Boot) } idt_init(Boot); + memory_init(Boot); SAFE_PRINT(Boot, L"================================================\n\r"); SAFE_PRINT(Boot, L" Welcome to Simple UEFI Operating System!\n\r"); diff --git a/main.c b/main.c index d575ddb..6d240bd 100644 --- a/main.c +++ b/main.c @@ -207,6 +207,17 @@ static void loader_shutdown(void) uefi_call_wrapper(RT->ResetSystem, 4, EfiResetShutdown, EFI_SUCCESS, 0, NULL); } +static EFI_STATUS loader_alloc_pages(UINTN pages, EFI_PHYSICAL_ADDRESS *addr) +{ + return uefi_call_wrapper(BS->AllocatePages, 4, + AllocateAnyPages, EfiLoaderData, pages, addr); +} + +static EFI_STATUS loader_free_pages(EFI_PHYSICAL_ADDRESS addr, UINTN pages) +{ + return uefi_call_wrapper(BS->FreePages, 2, addr, pages); +} + EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) @@ -241,6 +252,8 @@ efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) Boot.set_attribute = loader_set_attribute; Boot.read_key = loader_read_key; Boot.shutdown = loader_shutdown; + Boot.alloc_pages = loader_alloc_pages; + Boot.free_pages = loader_free_pages; EntryFn = (KernelEntryFn)(UINTN)KernelEntry; EntryFn(&Boot); diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..658df7e --- /dev/null +++ b/memory.c @@ -0,0 +1,490 @@ +#include "memory.h" + +#define SAFE_PRINT(Boot, ...) \ + do { \ + if ((Boot) != NULL && (Boot)->print != NULL) { \ + (Boot)->print(__VA_ARGS__); \ + } \ + } while (0) + +/* ================================================================ + * Physical Memory Manager – bitmap-based page-frame allocator + * ================================================================ */ + +static UINT64 pmm_pool_base = 0; +static UINTN pmm_total_pages = 0; +static UINTN pmm_free_count = 0; +static UINT8 pmm_bitmap[PMM_POOL_PAGES / 8]; +static BOOLEAN pmm_ready = FALSE; + +static void pmm_set_bit(UINTN idx) +{ + pmm_bitmap[idx / 8] |= (UINT8)(1U << (idx % 8)); +} + +static void pmm_clear_bit(UINTN idx) +{ + pmm_bitmap[idx / 8] &= (UINT8)~(1U << (idx % 8)); +} + +static BOOLEAN pmm_test_bit(UINTN idx) +{ + return (pmm_bitmap[idx / 8] & (1U << (idx % 8))) != 0; +} + +void pmm_init(BootInfo *Boot) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS pool_addr = 0; + UINTN i; + + /* Zero the bitmap – all pages start free */ + for (i = 0; i < sizeof(pmm_bitmap); i++) { + pmm_bitmap[i] = 0; + } + + if (Boot == NULL || Boot->alloc_pages == NULL) { + SAFE_PRINT(Boot, L"PMM: page allocator unavailable\n\r"); + return; + } + + Status = Boot->alloc_pages(PMM_POOL_PAGES, &pool_addr); + if (EFI_ERROR(Status)) { + SAFE_PRINT(Boot, L"PMM: failed to allocate pool (%d pages): %r\n\r", + (UINTN)PMM_POOL_PAGES, Status); + return; + } + + pmm_pool_base = (UINT64)pool_addr; + pmm_total_pages = PMM_POOL_PAGES; + pmm_free_count = PMM_POOL_PAGES; + pmm_ready = TRUE; + + SAFE_PRINT(Boot, L" PMM : %d pages (%d KB) at 0x%lx\n\r", + pmm_total_pages, + (pmm_total_pages * PAGE_SIZE) / 1024, + pmm_pool_base); +} + +UINT64 pmm_alloc_page(void) +{ + UINTN i; + + if (!pmm_ready || pmm_free_count == 0) { + return 0; + } + + for (i = 0; i < pmm_total_pages; i++) { + if (!pmm_test_bit(i)) { + pmm_set_bit(i); + pmm_free_count--; + return pmm_pool_base + ((UINT64)i * PAGE_SIZE); + } + } + + return 0; +} + +void pmm_free_page(UINT64 phys_addr) +{ + UINTN idx; + + if (!pmm_ready) return; + if (phys_addr < pmm_pool_base) return; + + idx = (UINTN)((phys_addr - pmm_pool_base) / PAGE_SIZE); + if (idx >= pmm_total_pages) return; + if (!pmm_test_bit(idx)) return; /* already free */ + + pmm_clear_bit(idx); + pmm_free_count++; +} + +UINT64 pmm_alloc_pages(UINTN count) +{ + UINTN i, j; + BOOLEAN found; + + if (!pmm_ready || count == 0 || count > pmm_total_pages + || pmm_free_count < count) { + return 0; + } + + for (i = 0; i + count <= pmm_total_pages; i++) { + found = TRUE; + for (j = 0; j < count; j++) { + if (pmm_test_bit(i + j)) { + found = FALSE; + i += j; /* skip past the used page */ + break; + } + } + if (found) { + for (j = 0; j < count; j++) { + pmm_set_bit(i + j); + } + pmm_free_count -= count; + return pmm_pool_base + ((UINT64)i * PAGE_SIZE); + } + } + + return 0; +} + +void pmm_free_pages(UINT64 phys_addr, UINTN count) +{ + UINTN i; + for (i = 0; i < count; i++) { + pmm_free_page(phys_addr + ((UINT64)i * PAGE_SIZE)); + } +} + +UINTN pmm_get_free_pages(void) { return pmm_free_count; } +UINTN pmm_get_total_pages(void) { return pmm_total_pages; } + +/* ================================================================ + * Paging – manipulate the live 4-level x86-64 page tables + * ================================================================ */ + +static UINT64 read_cr3(void) +{ + UINT64 cr3; + __asm__ __volatile__("mov %%cr3, %0" : "=r"(cr3)); + return cr3; +} + +static void invlpg(UINT64 addr) +{ + __asm__ __volatile__("invlpg (%0)" :: "r"(addr) : "memory"); +} + +static UINT64 *get_pml4(void) +{ + return (UINT64 *)(UINTN)(read_cr3() & PTE_ADDR_MASK); +} + +/* + * Walk one level of the page table hierarchy. + * If `create` is TRUE and the entry is missing, a fresh zeroed page is + * allocated from the PMM and installed. + */ +static UINT64 *paging_walk_level(UINT64 *table, UINTN index, BOOLEAN create) +{ + UINT64 *next; + UINTN i; + UINT64 page; + + if (table[index] & PTE_PRESENT) { + return (UINT64 *)(UINTN)(table[index] & PTE_ADDR_MASK); + } + + if (!create) { + return NULL; + } + + page = pmm_alloc_page(); + if (page == 0) { + return NULL; + } + + /* Zero the freshly-allocated page table */ + next = (UINT64 *)(UINTN)page; + for (i = 0; i < PAGE_SIZE / sizeof(UINT64); i++) { + next[i] = 0; + } + + table[index] = page | PTE_PRESENT | PTE_WRITABLE; + return next; +} + +void paging_init(BootInfo *Boot) +{ + SAFE_PRINT(Boot, L" Page: CR3 = 0x%lx (UEFI identity-mapped)\n\r", + read_cr3()); +} + +BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags) +{ + UINT64 *pml4, *pdpt, *pd, *pt; + UINTN pml4i, pdpti, pdi, pti; + + pml4i = (virt >> 39) & 0x1FF; + pdpti = (virt >> 30) & 0x1FF; + pdi = (virt >> 21) & 0x1FF; + pti = (virt >> 12) & 0x1FF; + + pml4 = get_pml4(); + + pdpt = paging_walk_level(pml4, pml4i, TRUE); + if (pdpt == NULL) return FALSE; + + /* 1 GB huge page – cannot carve a 4 KB mapping inside it */ + if (pdpt[pdpti] & PTE_HUGE) return FALSE; + + pd = paging_walk_level(pdpt, pdpti, TRUE); + if (pd == NULL) return FALSE; + + /* 2 MB huge page – cannot carve a 4 KB mapping inside it */ + if (pd[pdi] & PTE_HUGE) return FALSE; + + pt = paging_walk_level(pd, pdi, TRUE); + if (pt == NULL) return FALSE; + + pt[pti] = (phys & PTE_ADDR_MASK) | flags | PTE_PRESENT; + invlpg(virt); + return TRUE; +} + +void paging_unmap_page(UINT64 virt) +{ + UINT64 *pml4, *pdpt, *pd, *pt; + UINTN pml4i, pdpti, pdi, pti; + + pml4i = (virt >> 39) & 0x1FF; + pdpti = (virt >> 30) & 0x1FF; + pdi = (virt >> 21) & 0x1FF; + pti = (virt >> 12) & 0x1FF; + + pml4 = get_pml4(); + + pdpt = paging_walk_level(pml4, pml4i, FALSE); + if (pdpt == NULL) return; + if (pdpt[pdpti] & PTE_HUGE) return; + + pd = paging_walk_level(pdpt, pdpti, FALSE); + if (pd == NULL) return; + if (pd[pdi] & PTE_HUGE) return; + + pt = paging_walk_level(pd, pdi, FALSE); + if (pt == NULL) return; + + pt[pti] = 0; + invlpg(virt); +} + +UINT64 paging_get_phys(UINT64 virt) +{ + UINT64 *pml4, *pdpt, *pd, *pt; + UINTN pml4i, pdpti, pdi, pti; + + pml4i = (virt >> 39) & 0x1FF; + pdpti = (virt >> 30) & 0x1FF; + pdi = (virt >> 21) & 0x1FF; + pti = (virt >> 12) & 0x1FF; + + pml4 = get_pml4(); + + if (!(pml4[pml4i] & PTE_PRESENT)) return 0; + pdpt = (UINT64 *)(UINTN)(pml4[pml4i] & PTE_ADDR_MASK); + + if (!(pdpt[pdpti] & PTE_PRESENT)) return 0; + if (pdpt[pdpti] & PTE_HUGE) { + /* 1 GB page */ + return (pdpt[pdpti] & 0x000FFFFFC0000000ULL) | (virt & 0x3FFFFFFFULL); + } + pd = (UINT64 *)(UINTN)(pdpt[pdpti] & PTE_ADDR_MASK); + + if (!(pd[pdi] & PTE_PRESENT)) return 0; + if (pd[pdi] & PTE_HUGE) { + /* 2 MB page */ + return (pd[pdi] & 0x000FFFFFFFE00000ULL) | (virt & 0x1FFFFFULL); + } + pt = (UINT64 *)(UINTN)(pd[pdi] & PTE_ADDR_MASK); + + if (!(pt[pti] & PTE_PRESENT)) return 0; + return (pt[pti] & PTE_ADDR_MASK) | (virt & 0xFFFULL); +} + +/* ================================================================ + * Heap Allocator – first-fit free-list with coalescing + * ================================================================ */ + +static HeapBlock *heap_start = NULL; +static BOOLEAN heap_ready = FALSE; + +static UINTN align_up(UINTN val, UINTN align) +{ + return (val + align - 1) & ~(align - 1); +} + +void heap_init(BootInfo *Boot) +{ + UINT64 phys; + UINTN heap_size; + + phys = pmm_alloc_pages(HEAP_INITIAL_PAGES); + if (phys == 0) { + SAFE_PRINT(Boot, L" Heap: failed to allocate pages\n\r"); + return; + } + + heap_size = HEAP_INITIAL_PAGES * PAGE_SIZE; + heap_start = (HeapBlock *)(UINTN)phys; + + heap_start->magic = HEAP_BLOCK_MAGIC; + heap_start->state = HEAP_BLOCK_FREE; + heap_start->size = heap_size - sizeof(HeapBlock); + heap_start->next = NULL; + heap_start->prev = NULL; + + heap_ready = TRUE; + + SAFE_PRINT(Boot, L" Heap: %d KB at 0x%lx\n\r", + heap_size / 1024, phys); +} + +void *kmalloc(UINTN size) +{ + HeapBlock *block, *split; + UINTN aligned; + + if (!heap_ready || size == 0) { + return NULL; + } + + aligned = align_up(size, HEAP_ALIGN); + + for (block = heap_start; block != NULL; block = block->next) { + if (block->magic != HEAP_BLOCK_MAGIC) { + return NULL; /* heap corruption */ + } + + if (block->state != HEAP_BLOCK_FREE || block->size < aligned) { + continue; + } + + /* Try to split if there is room for another header + 16 bytes */ + if (block->size >= aligned + sizeof(HeapBlock) + HEAP_ALIGN) { + split = (HeapBlock *)((UINT8 *)block + sizeof(HeapBlock) + aligned); + split->magic = HEAP_BLOCK_MAGIC; + split->state = HEAP_BLOCK_FREE; + split->size = block->size - aligned - sizeof(HeapBlock); + split->next = block->next; + split->prev = block; + + if (block->next != NULL) { + block->next->prev = split; + } + + block->next = split; + block->size = aligned; + } + + block->state = HEAP_BLOCK_USED; + return (void *)((UINT8 *)block + sizeof(HeapBlock)); + } + + return NULL; /* out of heap memory */ +} + +void kfree(void *ptr) +{ + HeapBlock *block; + + if (ptr == NULL || !heap_ready) { + return; + } + + block = (HeapBlock *)((UINT8 *)ptr - sizeof(HeapBlock)); + + if (block->magic != HEAP_BLOCK_MAGIC || block->state != HEAP_BLOCK_USED) { + return; /* bad pointer or double-free */ + } + + block->state = HEAP_BLOCK_FREE; + + /* Coalesce with next neighbour */ + if (block->next != NULL + && block->next->magic == HEAP_BLOCK_MAGIC + && block->next->state == HEAP_BLOCK_FREE) { + block->size += sizeof(HeapBlock) + block->next->size; + block->next = block->next->next; + if (block->next != NULL) { + block->next->prev = block; + } + } + + /* Coalesce with previous neighbour */ + if (block->prev != NULL + && block->prev->magic == HEAP_BLOCK_MAGIC + && block->prev->state == HEAP_BLOCK_FREE) { + block->prev->size += sizeof(HeapBlock) + block->size; + block->prev->next = block->next; + if (block->next != NULL) { + block->next->prev = block->prev; + } + } +} + +void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem, + UINTN *num_blocks) +{ + HeapBlock *b; + + *total = 0; *used = 0; *free_mem = 0; *num_blocks = 0; + + if (!heap_ready) return; + + for (b = heap_start; b != NULL && b->magic == HEAP_BLOCK_MAGIC; + b = b->next) { + (*num_blocks)++; + *total += b->size; + if (b->state == HEAP_BLOCK_USED) { + *used += b->size; + } else { + *free_mem += b->size; + } + } +} + +/* ================================================================ + * Top-level helpers + * ================================================================ */ + +void memory_init(BootInfo *Boot) +{ + SAFE_PRINT(Boot, L"Initializing memory management...\n\r"); + pmm_init(Boot); + paging_init(Boot); + heap_init(Boot); + SAFE_PRINT(Boot, L"Memory management ready.\n\r\n\r"); +} + +void memory_print_stats(BootInfo *Boot) +{ + UINTN h_total, h_used, h_free, h_blocks; + UINTN p_total, p_free, p_used; + + p_total = pmm_get_total_pages(); + p_free = pmm_get_free_pages(); + p_used = p_total - p_free; + + heap_get_stats(&h_total, &h_used, &h_free, &h_blocks); + + SAFE_PRINT(Boot, L"\n\r"); + SAFE_PRINT(Boot, L"Memory Statistics\n\r"); + SAFE_PRINT(Boot, L"================================================\n\r"); + SAFE_PRINT(Boot, L"\n\r"); + + SAFE_PRINT(Boot, L"Physical Memory Manager:\n\r"); + SAFE_PRINT(Boot, L" Pool Base: 0x%lx\n\r", pmm_pool_base); + SAFE_PRINT(Boot, L" Total Pages: %d (%d KB)\n\r", + p_total, (p_total * PAGE_SIZE) / 1024); + SAFE_PRINT(Boot, L" Used Pages: %d (%d KB)\n\r", + p_used, (p_used * PAGE_SIZE) / 1024); + SAFE_PRINT(Boot, L" Free Pages: %d (%d KB)\n\r", + p_free, (p_free * PAGE_SIZE) / 1024); + SAFE_PRINT(Boot, L"\n\r"); + + SAFE_PRINT(Boot, L"Heap Allocator:\n\r"); + SAFE_PRINT(Boot, L" Total: %d bytes\n\r", h_total); + SAFE_PRINT(Boot, L" Used: %d bytes\n\r", h_used); + SAFE_PRINT(Boot, L" Free: %d bytes\n\r", h_free); + SAFE_PRINT(Boot, L" Blocks: %d\n\r", h_blocks); + SAFE_PRINT(Boot, L"\n\r"); + + SAFE_PRINT(Boot, L"Paging:\n\r"); + SAFE_PRINT(Boot, L" CR3: 0x%lx\n\r", read_cr3()); + SAFE_PRINT(Boot, L" Mode: 4-level (PML4)\n\r"); + SAFE_PRINT(Boot, L"\n\r"); +} diff --git a/memory.h b/memory.h new file mode 100644 index 0000000..1453014 --- /dev/null +++ b/memory.h @@ -0,0 +1,75 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include +#include "boot_info.h" + +/* Page size constants */ +#define PAGE_SIZE 4096 +#define PAGE_SHIFT 12 +#define PAGE_MASK (~(UINT64)(PAGE_SIZE - 1)) + +/* Page table entry flags */ +#define PTE_PRESENT (1ULL << 0) +#define PTE_WRITABLE (1ULL << 1) +#define PTE_USER (1ULL << 2) +#define PTE_WRITETHROUGH (1ULL << 3) +#define PTE_NOCACHE (1ULL << 4) +#define PTE_ACCESSED (1ULL << 5) +#define PTE_DIRTY (1ULL << 6) +#define PTE_HUGE (1ULL << 7) +#define PTE_GLOBAL (1ULL << 8) +#define PTE_NX (1ULL << 63) + +#define PTE_ADDR_MASK 0x000FFFFFFFFFF000ULL + +/* PMM pool: 16 MB managed by the kernel */ +#define PMM_POOL_SIZE (16ULL * 1024 * 1024) +#define PMM_POOL_PAGES (PMM_POOL_SIZE / PAGE_SIZE) + +/* Heap constants */ +#define HEAP_INITIAL_PAGES 64 /* 256 KB initial heap */ +#define HEAP_BLOCK_MAGIC 0xDEADBEEFU +#define HEAP_BLOCK_FREE 0 +#define HEAP_BLOCK_USED 1 +#define HEAP_ALIGN 16 + +/* -------- Physical Memory Manager -------- */ + +void pmm_init(BootInfo *Boot); +UINT64 pmm_alloc_page(void); +void pmm_free_page(UINT64 phys_addr); +UINT64 pmm_alloc_pages(UINTN count); +void pmm_free_pages(UINT64 phys_addr, UINTN count); +UINTN pmm_get_free_pages(void); +UINTN pmm_get_total_pages(void); + +/* -------- Paging -------- */ + +void paging_init(BootInfo *Boot); +BOOLEAN paging_map_page(UINT64 virt, UINT64 phys, UINT64 flags); +void paging_unmap_page(UINT64 virt); +UINT64 paging_get_phys(UINT64 virt); + +/* -------- Heap Allocator -------- */ + +typedef struct HeapBlock { + UINT32 magic; + UINT32 state; + UINTN size; /* usable bytes (excludes header) */ + struct HeapBlock *next; + struct HeapBlock *prev; +} HeapBlock; + +void heap_init(BootInfo *Boot); +void *kmalloc(UINTN size); +void kfree(void *ptr); +void heap_get_stats(UINTN *total, UINTN *used, UINTN *free_mem, + UINTN *num_blocks); + +/* -------- Top-level helpers -------- */ + +void memory_init(BootInfo *Boot); +void memory_print_stats(BootInfo *Boot); + +#endif