378 lines
11 KiB
C
378 lines
11 KiB
C
/*
|
||
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;
|
||
}
|