Files
Sylva/kernel/memory/pmm.cpp
T
2026-05-29 19:25:56 +08:00

261 lines
7.9 KiB
C++

#include <memory/pmm.h>
#include <serial.h>
extern EFI_SYSTEM_TABLE *ST;
pmm_t g_pmm;
static inline void bitmap_set(UINTN idx) {
g_pmm.bitmap[idx / 8] |= (1 << (idx % 8));
}
static inline void bitmap_clear(UINTN idx) {
g_pmm.bitmap[idx / 8] &= ~(1 << (idx % 8));
}
static inline BOOLEAN bitmap_test(UINTN idx) {
return (g_pmm.bitmap[idx / 8] >> (idx % 8)) & 1;
}
// Clean stale entries from free list head
static void clean_free_list() {
while (g_pmm.free_list_head != NULL &&
bitmap_test((UINTN)g_pmm.free_list_head / PAGE_SIZE)) {
g_pmm.free_list_head = *(void**)g_pmm.free_list_head;
}
}
EFI_STATUS pmm_init() {
UINTN map_size = 0;
UINTN map_key;
UINTN desc_size;
UINT32 desc_version;
EFI_STATUS status = uefi_call_wrapper(
ST->BootServices->GetMemoryMap, 5,
&map_size, NULL, &map_key, &desc_size, &desc_version
);
map_size += desc_size * 64;
EFI_MEMORY_DESCRIPTOR* mem_map = NULL;
status = uefi_call_wrapper(
ST->BootServices->AllocatePool, 3,
EfiLoaderData, map_size, (void**)&mem_map
);
if (EFI_ERROR(status)) return status;
status = uefi_call_wrapper(
ST->BootServices->GetMemoryMap, 5,
&map_size, mem_map, &map_key, &desc_size, &desc_version
);
if (EFI_ERROR(status)) {
uefi_call_wrapper(ST->BootServices->FreePool, 1, mem_map);
return status;
}
UINTN entry_count = map_size / desc_size;
// First pass: count total pages and find max physical address
UINT64 max_addr = 0;
UINT64 total_free = 0;
for (UINTN i = 0; i < entry_count; i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size);
UINT64 end = desc->PhysicalStart + desc->NumberOfPages * PAGE_SIZE;
if (end > max_addr) max_addr = end;
if (desc->Type == EfiConventionalMemory) {
total_free += desc->NumberOfPages * PAGE_SIZE;
}
}
g_pmm.base_addr = 0;
g_pmm.max_addr = max_addr;
// How many pages does the bitmap cover?
UINTN total_pages = (UINTN)(max_addr / PAGE_SIZE);
g_pmm.total_pages = total_pages;
// Bitmap size in bytes, rounded up to page boundary
g_pmm.bitmap_size = ((total_pages + 7) / 8);
UINTN bitmap_pages = (g_pmm.bitmap_size + PAGE_SIZE - 1) / PAGE_SIZE;
g_pmm.bitmap_size = bitmap_pages * PAGE_SIZE; // round to full pages
// Place bitmap at the end of the highest free conventional memory region
UINT64 bitmap_addr = 0;
for (UINTN i = 0; i < entry_count; i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size);
if (desc->Type == EfiConventionalMemory) {
UINT64 region_bytes = desc->NumberOfPages * PAGE_SIZE;
if (region_bytes >= g_pmm.bitmap_size) {
UINT64 candidate = desc->PhysicalStart + region_bytes - g_pmm.bitmap_size;
if (candidate > bitmap_addr) {
bitmap_addr = candidate;
}
}
}
}
if (bitmap_addr == 0) {
serial_write("PMM: ERROR - no space for bitmap!\n");
uefi_call_wrapper(ST->BootServices->FreePool, 1, mem_map);
return EFI_OUT_OF_RESOURCES;
}
g_pmm.bitmap = (UINT8*)(UINTN)bitmap_addr;
// Init bitmap: mark ALL pages as used (0xFF)
for (UINTN i = 0; i < g_pmm.bitmap_size; i++) {
g_pmm.bitmap[i] = 0xFF;
}
// Mark free pages (EfiConventionalMemory) as free in bitmap
g_pmm.free_pages = 0;
UINT64 bm_start_page = bitmap_addr / PAGE_SIZE;
UINT64 bm_end_page = (bitmap_addr + g_pmm.bitmap_size + PAGE_SIZE - 1) / PAGE_SIZE;
for (UINTN i = 0; i < entry_count; i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size);
if (desc->Type != EfiConventionalMemory) continue;
UINT64 start_page = desc->PhysicalStart / PAGE_SIZE;
UINT64 end_page = start_page + desc->NumberOfPages;
for (UINT64 p = start_page; p < end_page; p++) {
// Skip bitmap pages
if (p >= bm_start_page && p < bm_end_page) continue;
bitmap_clear((UINTN)p);
g_pmm.free_pages++;
}
}
// Mark bitmap pages as used
for (UINT64 p = bm_start_page; p < bm_end_page; p++) {
bitmap_set((UINTN)p);
}
// Build free list by linking free pages
g_pmm.free_list_head = NULL;
void* prev = NULL;
for (UINTN i = 0; i < entry_count; i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size);
if (desc->Type != EfiConventionalMemory) continue;
UINT64 start_page = desc->PhysicalStart / PAGE_SIZE;
UINT64 end_page = start_page + desc->NumberOfPages;
for (UINT64 p = start_page; p < end_page; p++) {
if (p >= bm_start_page && p < bm_end_page) continue;
void* page = (void*)(UINTN)(p * PAGE_SIZE);
if (prev) {
*(void**)prev = page;
} else {
g_pmm.free_list_head = page;
}
prev = page;
}
}
if (prev) *(void**)prev = NULL;
uefi_call_wrapper(ST->BootServices->FreePool, 1, mem_map);
serial_write("PMM: init OK, total ");
serial_write_hex(total_pages);
serial_write(" pages (");
serial_write_hex(total_free / (1024*1024));
serial_write(" MB free), bitmap ");
serial_write_hex(bitmap_pages);
serial_write(" pages @ ");
serial_write_hex(bitmap_addr);
serial_write("\n");
return EFI_SUCCESS;
}
void* pmm_alloc_pages(UINTN n) {
if (n == 0) return NULL;
clean_free_list();
if (n == 1) {
if (g_pmm.free_list_head == NULL) {
serial_write("PMM: OOM (no free pages)\n");
return NULL;
}
void* page = g_pmm.free_list_head;
g_pmm.free_list_head = *(void**)page;
*(void**)page = NULL; // clear the next pointer
UINTN idx = (UINTN)page / PAGE_SIZE;
bitmap_set(idx);
g_pmm.free_pages--;
serial_write("PMM: alloc 1 page @ ");
serial_write_hex((UINTN)page);
serial_write("\n");
return page;
}
// n > 1: scan bitmap for n consecutive free pages
UINTN consecutive = 0;
UINTN start_idx = 0;
for (UINTN i = 0; i < g_pmm.total_pages; i++) {
if (!bitmap_test(i)) {
if (consecutive == 0) start_idx = i;
consecutive++;
if (consecutive == n) {
// Found n consecutive free pages
void* base = (void*)(UINTN)(start_idx * PAGE_SIZE);
for (UINTN j = 0; j < n; j++) {
bitmap_set(start_idx + j);
}
g_pmm.free_pages -= n;
serial_write("PMM: alloc ");
serial_write_hex(n);
serial_write(" pages @ ");
serial_write_hex((UINTN)base);
serial_write("\n");
return base;
}
} else {
consecutive = 0;
}
}
serial_write("PMM: OOM (no contiguous free pages)\n");
return NULL;
}
void pmm_free_pages(void* addr, UINTN n) {
UINTN start_idx = (UINTN)addr / PAGE_SIZE;
// Add freed pages to free list
for (UINTN i = 0; i < n; i++) {
UINTN idx = start_idx + i;
bitmap_clear(idx);
g_pmm.free_pages++;
void* page = (void*)(UINTN)(idx * PAGE_SIZE);
// Push to front of free list
*(void**)page = g_pmm.free_list_head;
g_pmm.free_list_head = page;
}
serial_write("PMM: free ");
serial_write_hex(n);
serial_write(" pages @ ");
serial_write_hex((UINTN)addr);
serial_write("\n");
}
UINTN pmm_get_free_count() {
return g_pmm.free_pages;
}
BOOLEAN pmm_is_page_free(void* addr) {
UINTN idx = (UINTN)addr / PAGE_SIZE;
if (idx >= g_pmm.total_pages) return FALSE;
return !bitmap_test(idx);
}