Files
LithosAnanke/src/starkernel/memory/pmm.c
T

378 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* pmm.c - Physical Memory Manager (bitmap allocator)
*
* Builds a 1-bit-per-page bitmap from the UEFI memory map and provides
* primitive page allocation for early kernel bring-up. The allocator favors
* simplicity and debuggability; performance tuning can happen once paging and
* interrupts are in place.
*/
#include "pmm.h"
#include "console.h"
#define PMM_MAX_BITMAP_BYTES (2u * 1024u * 1024u) /* Tracks up to ~64 GiB */
#define PMM_BITMAP_BITS (PMM_MAX_BITMAP_BYTES * 8ull)
/* Do not hand out pages below this floor (1 MiB) */
static const uint64_t pmm_alloc_floor_page = 0x100000u / PMM_PAGE_SIZE;
static uint8_t pmm_bitmap[PMM_MAX_BITMAP_BYTES];
static uint64_t pmm_total_pages = 0;
static uint64_t pmm_free_pages = 0;
static int pmm_initialized = 0;
static uint64_t pmm_tracked_pages = 0;
extern char __kernel_start[];
extern char __kernel_end[];
static void pmm_memset(void *dst, uint8_t value, size_t size) {
uint8_t *ptr = (uint8_t *)dst;
for (size_t i = 0; i < size; ++i) {
ptr[i] = value;
}
}
static inline uint64_t pmm_bytes_to_pages(uint64_t bytes) {
uint64_t pages = bytes / PMM_PAGE_SIZE;
if (bytes % PMM_PAGE_SIZE) {
pages += 1;
}
return pages;
}
static inline void bitmap_set(uint64_t bit) {
pmm_bitmap[bit / 8u] |= (uint8_t)(1u << (bit % 8u));
}
static inline void bitmap_clear(uint64_t bit) {
pmm_bitmap[bit / 8u] &= (uint8_t)~(1u << (bit % 8u));
}
static inline int bitmap_test(uint64_t bit) {
return (pmm_bitmap[bit / 8u] >> (bit % 8u)) & 1u;
}
static void bitmap_mark_range(uint64_t start_page, uint64_t page_count, int used) {
for (uint64_t i = 0; i < page_count; ++i) {
uint64_t page = start_page + i;
if (page >= pmm_tracked_pages) {
break;
}
if (used) {
if (!bitmap_test(page)) {
bitmap_set(page);
if (pmm_free_pages > 0) {
pmm_free_pages--;
}
}
} else {
if (bitmap_test(page)) {
bitmap_clear(page);
pmm_free_pages++;
}
}
}
}
static uint64_t find_contiguous_free(uint64_t pages_needed) {
uint64_t run_start = 0;
uint64_t run_length = 0;
for (uint64_t page = pmm_alloc_floor_page; page < pmm_tracked_pages; ++page) {
if (!bitmap_test(page)) {
if (run_length == 0) {
run_start = page;
}
run_length++;
if (run_length == pages_needed) {
return run_start;
}
} else {
run_length = 0;
}
}
return UINT64_MAX;
}
static int is_usable_memory(uint32_t type) {
return type == EfiConventionalMemory;
}
static int is_ram_type(uint32_t type) {
return is_usable_memory(type) ||
type == EfiRuntimeServicesCode ||
type == EfiRuntimeServicesData ||
type == EfiACPIReclaimMemory ||
type == EfiACPIMemoryNVS;
}
static void recompute_free_pages(void) {
uint64_t free_count = 0;
for (uint64_t page = 0; page < pmm_tracked_pages; ++page) {
if (!bitmap_test(page)) {
free_count++;
}
}
pmm_free_pages = free_count;
}
int pmm_init(BootInfo *boot_info)
{
if (!boot_info ||
!boot_info->memory_map ||
boot_info->memory_map_descriptor_size == 0)
{
return -1;
}
EFI_MEMORY_DESCRIPTOR *map = boot_info->memory_map;
UINTN map_size = boot_info->memory_map_size;
UINTN desc_size = boot_info->memory_map_descriptor_size;
UINTN entry_count = map_size / desc_size;
uint64_t max_ram_end = 0;
/* ------------------------------------------------------------
* Pass 1: determine highest physical address of RAM-like regions
* ------------------------------------------------------------ */
for (UINTN i = 0; i < entry_count; ++i) {
EFI_MEMORY_DESCRIPTOR *desc =
(EFI_MEMORY_DESCRIPTOR *)((uint8_t *)map + i * desc_size);
if (!is_ram_type(desc->Type))
continue;
uint64_t region_end =
desc->PhysicalStart +
desc->NumberOfPages * (uint64_t)PMM_PAGE_SIZE;
if (region_end > max_ram_end)
max_ram_end = region_end;
}
if (max_ram_end == 0)
return -1;
pmm_tracked_pages = pmm_bytes_to_pages(max_ram_end);
if (pmm_tracked_pages > PMM_BITMAP_BITS)
pmm_tracked_pages = PMM_BITMAP_BITS;
if (pmm_tracked_pages == 0)
return -1;
uint64_t bitmap_bytes = (pmm_tracked_pages + 7) / 8;
pmm_memset(pmm_bitmap, 0xFF, (size_t)bitmap_bytes);
pmm_total_pages = 0;
pmm_free_pages = 0;
/* ------------------------------------------------------------
* Pass 2: count RAM pages (tracked universe)
* ------------------------------------------------------------ */
for (UINTN i = 0; i < entry_count; ++i) {
EFI_MEMORY_DESCRIPTOR *desc =
(EFI_MEMORY_DESCRIPTOR *)((uint8_t *)map + i * desc_size);
if (!is_ram_type(desc->Type))
continue;
uint64_t start_page = desc->PhysicalStart / PMM_PAGE_SIZE;
uint64_t end_page = start_page + desc->NumberOfPages;
if (start_page >= pmm_tracked_pages)
continue;
if (end_page > pmm_tracked_pages)
end_page = pmm_tracked_pages;
if (end_page > start_page)
pmm_total_pages += (end_page - start_page);
}
/* ------------------------------------------------------------
* Pass 3: free usable memory (respect allocation floor)
* ------------------------------------------------------------ */
for (UINTN i = 0; i < entry_count; ++i) {
EFI_MEMORY_DESCRIPTOR *desc =
(EFI_MEMORY_DESCRIPTOR *)((uint8_t *)map + i * desc_size);
if (!is_usable_memory(desc->Type))
continue;
uint64_t start_page = desc->PhysicalStart / PMM_PAGE_SIZE;
uint64_t end_page = start_page + desc->NumberOfPages;
if (start_page >= pmm_tracked_pages)
continue;
if (end_page > pmm_tracked_pages)
end_page = pmm_tracked_pages;
if (end_page <= pmm_alloc_floor_page)
continue;
if (start_page < pmm_alloc_floor_page)
start_page = pmm_alloc_floor_page;
if (end_page > start_page) {
uint64_t pages = end_page - start_page;
bitmap_mark_range(start_page, pages, 0);
}
}
/* ------------------------------------------------------------
* Reserve low memory (defensive)
* ------------------------------------------------------------ */
if (pmm_alloc_floor_page > 0) {
bitmap_mark_range(0, pmm_alloc_floor_page, 1);
}
/* ------------------------------------------------------------
* Reserve kernel image (page-aligned, physical)
* ------------------------------------------------------------ */
uint64_t kstart = (uint64_t)__kernel_start;
uint64_t kend = (uint64_t)__kernel_end;
uint64_t kstart_page = kstart / PMM_PAGE_SIZE;
uint64_t kend_page =
(kend + PMM_PAGE_SIZE - 1) / PMM_PAGE_SIZE;
if (kend_page > kstart_page) {
uint64_t kpages = kend_page - kstart_page;
bitmap_mark_range(kstart_page, kpages, 1);
}
/* ------------------------------------------------------------
* Keep page 0 reserved (NULL guard)
* ------------------------------------------------------------ */
bitmap_mark_range(0, 1, 1);
/* ------------------------------------------------------------
* Reserve the UEFI memory map buffer itself (BootInfo memory_map)
* ------------------------------------------------------------ */
if (boot_info->memory_map && boot_info->memory_map_size > 0) {
uint64_t mm_phys = (uint64_t)(uintptr_t)boot_info->memory_map;
uint64_t mm_pages = pmm_bytes_to_pages(boot_info->memory_map_size);
uint64_t mm_start_page = mm_phys / PMM_PAGE_SIZE;
bitmap_mark_range(mm_start_page, mm_pages, 1);
}
recompute_free_pages();
pmm_initialized = 1;
return 0;
}
int pmm_is_initialized(void) {
return pmm_initialized;
}
uint64_t pmm_alloc_page(void) {
if (!pmm_initialized) {
return 0;
}
uint64_t start = find_contiguous_free(1);
if (start == UINT64_MAX) {
return 0;
}
bitmap_mark_range(start, 1, 1);
return start * (uint64_t)PMM_PAGE_SIZE;
}
uint64_t pmm_alloc_contiguous(uint64_t num_pages) {
if (!pmm_initialized || num_pages == 0) {
return 0;
}
uint64_t start = find_contiguous_free(num_pages);
if (start == UINT64_MAX) {
return 0;
}
bitmap_mark_range(start, num_pages, 1);
return start * (uint64_t)PMM_PAGE_SIZE;
}
void pmm_free_page(uint64_t paddr) {
if (!pmm_initialized || (paddr % PMM_PAGE_SIZE) != 0) {
return;
}
pmm_free_contiguous(paddr, 1);
}
void pmm_free_contiguous(uint64_t paddr, uint64_t num_pages) {
if (!pmm_initialized || (paddr % PMM_PAGE_SIZE) != 0 || num_pages == 0) {
return;
}
uint64_t start_page = paddr / PMM_PAGE_SIZE;
if (start_page >= pmm_tracked_pages) {
return;
}
if (start_page + num_pages > pmm_tracked_pages) {
num_pages = pmm_tracked_pages - start_page;
}
bitmap_mark_range(start_page, num_pages, 0);
}
pmm_stats_t pmm_get_stats(void) {
pmm_stats_t stats = {0};
stats.total_pages = pmm_total_pages;
stats.free_pages = pmm_free_pages;
stats.used_pages = (pmm_total_pages > pmm_free_pages) ? (pmm_total_pages - pmm_free_pages) : 0;
stats.total_bytes = stats.total_pages * (uint64_t)PMM_PAGE_SIZE;
stats.used_bytes = stats.used_pages * (uint64_t)PMM_PAGE_SIZE;
stats.free_bytes = stats.free_pages * (uint64_t)PMM_PAGE_SIZE;
return stats;
}