Files
LithosAnanake/src/starkernel/memory/vmm.c
T

369 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) 20232025 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) 20232025 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.
*/
/**
* vmm.c - 4-level paging Virtual Memory Manager (x86_64)
*
* Minimal bring-up: identity maps the first 16 MiB, supports single-page
* map/unmap/query, and switches to the new page table root.
*/
#include <stddef.h>
#include <stdint.h>
#include "vmm.h"
#include "pmm.h"
#include "console.h"
#include "uefi.h"
#define PTE_PRESENT (1ull << 0)
#define PTE_WRITABLE (1ull << 1)
#define PTE_USER (1ull << 2)
#define PTE_PWT (1ull << 3)
#define PTE_PCD (1ull << 4)
#define PTE_ACCESSED (1ull << 5)
#define PTE_DIRTY (1ull << 6)
#define PTE_PS (1ull << 7)
#define PTE_GLOBAL (1ull << 8)
#define PTE_NX (1ull << 63)
#define ADDR_MASK 0x000FFFFFFFFFF000ull
static uint64_t vmm_root_pml4_phys = 0;
static const uint64_t lapic_phys_base = 0xFEE00000ull;
static const uint64_t hpet_phys_base = 0xFED00000ull;
static int is_ram_type(uint32_t type) {
return type == EfiConventionalMemory ||
type == EfiLoaderCode ||
type == EfiLoaderData ||
type == EfiBootServicesCode ||
type == EfiBootServicesData ||
type == EfiRuntimeServicesCode ||
type == EfiRuntimeServicesData ||
type == EfiACPIReclaimMemory ||
type == EfiACPIMemoryNVS;
}
static inline uint64_t *paddr_to_virt(uint64_t paddr) {
return (uint64_t *)(uintptr_t)paddr;
}
static uint64_t alloc_page_zeroed(void) {
uint64_t paddr = pmm_alloc_page();
if (paddr == 0) {
return 0;
}
uint64_t *ptr = paddr_to_virt(paddr);
for (size_t i = 0; i < (VMM_PAGE_SIZE / sizeof(uint64_t)); ++i) {
ptr[i] = 0;
}
return paddr;
}
static inline uint16_t pml4_index(uint64_t vaddr) { return (uint16_t)((vaddr >> 39) & 0x1FF); }
static inline uint16_t pdpt_index(uint64_t vaddr) { return (uint16_t)((vaddr >> 30) & 0x1FF); }
static inline uint16_t pd_index(uint64_t vaddr) { return (uint16_t)((vaddr >> 21) & 0x1FF); }
static inline uint16_t pt_index(uint64_t vaddr) { return (uint16_t)((vaddr >> 12) & 0x1FF); }
static uint64_t *get_or_alloc_table(uint64_t *parent, uint16_t idx) {
uint64_t entry = parent[idx];
if (entry & PTE_PRESENT) {
return paddr_to_virt(entry & ADDR_MASK);
}
uint64_t child_phys = alloc_page_zeroed();
if (child_phys == 0) {
return NULL;
}
parent[idx] = child_phys | PTE_PRESENT | PTE_WRITABLE;
return paddr_to_virt(child_phys);
}
static uint64_t *get_table(uint64_t *parent, uint16_t idx) {
uint64_t entry = parent[idx];
if (entry & PTE_PRESENT) {
return paddr_to_virt(entry & ADDR_MASK);
}
return NULL;
}
static uint64_t make_pte(uint64_t paddr, uint64_t flags) {
uint64_t pte = paddr & ADDR_MASK;
if (flags & VMM_FLAG_PRESENT) pte |= PTE_PRESENT;
if (flags & VMM_FLAG_WRITABLE) pte |= PTE_WRITABLE;
if (flags & VMM_FLAG_USER) pte |= PTE_USER;
if (flags & VMM_FLAG_CACHE_DISABLE) pte |= PTE_PCD;
if (!(flags & VMM_FLAG_NX)) pte &= ~PTE_NX;
else pte |= PTE_NX;
return pte;
}
static inline void invlpg(uint64_t vaddr) {
#if defined(__x86_64__) || defined(__i386__)
__asm__ volatile ("invlpg (%0)" :: "r"(vaddr) : "memory");
#else
(void)vaddr;
#endif
}
static inline void load_cr3(uint64_t pml4_phys) {
#if defined(__x86_64__)
__asm__ volatile("mov %0, %%cr3" :: "r"(pml4_phys) : "memory");
#else
(void)pml4_phys;
#endif
}
static uint64_t *resolve_pt(uint64_t vaddr, uint16_t *idx_out) {
if (vmm_root_pml4_phys == 0) {
return NULL;
}
uint64_t *pml4 = paddr_to_virt(vmm_root_pml4_phys);
uint64_t *pdpt = get_table(pml4, pml4_index(vaddr));
if (!pdpt) return NULL;
uint64_t *pd = get_table(pdpt, pdpt_index(vaddr));
if (!pd) return NULL;
uint64_t *pt = get_table(pd, pd_index(vaddr));
if (!pt) return NULL;
if (idx_out) {
*idx_out = pt_index(vaddr);
}
return pt;
}
int vmm_map_page(uint64_t vaddr, uint64_t paddr, uint64_t flags) {
if (vmm_root_pml4_phys == 0) {
return -1;
}
uint64_t *pml4 = paddr_to_virt(vmm_root_pml4_phys);
uint64_t *pdpt = get_or_alloc_table(pml4, pml4_index(vaddr));
if (!pdpt) return -1;
uint64_t *pd = get_or_alloc_table(pdpt, pdpt_index(vaddr));
if (!pd) return -1;
uint64_t *pt = get_or_alloc_table(pd, pd_index(vaddr));
if (!pt) return -1;
uint16_t idx = pt_index(vaddr);
if (pt[idx] & PTE_PRESENT) {
uint64_t existing_pa = pt[idx] & ADDR_MASK;
uint64_t new_pte = make_pte(paddr, flags | VMM_FLAG_PRESENT);
/* Idempotent mapping: same PA + compatible flags => success */
if (existing_pa == (paddr & ADDR_MASK)) {
/* Require WRITABLE compatibility (no silent downgrade/upgrade) */
int was_writable = (pt[idx] & PTE_WRITABLE) != 0;
int want_writable = (new_pte & PTE_WRITABLE) != 0;
if (was_writable == want_writable) {
return 0;
}
}
console_println("VMM map conflict");
return -1;
}
pt[idx] = make_pte(paddr, flags | VMM_FLAG_PRESENT);
invlpg(vaddr);
return 0;
}
int vmm_unmap_page(uint64_t vaddr) {
if (vmm_root_pml4_phys == 0) {
return -1;
}
uint64_t *pml4 = paddr_to_virt(vmm_root_pml4_phys);
uint64_t *pdpt = get_table(pml4, pml4_index(vaddr));
if (!pdpt) return -1;
uint64_t *pd = get_table(pdpt, pdpt_index(vaddr));
if (!pd) return -1;
uint64_t *pt = get_table(pd, pd_index(vaddr));
if (!pt) return -1;
uint16_t idx = pt_index(vaddr);
if (!(pt[idx] & PTE_PRESENT)) {
return -1;
}
pt[idx] = 0;
invlpg(vaddr);
return 0;
}
uint64_t vmm_get_paddr(uint64_t vaddr) {
if (vmm_root_pml4_phys == 0) {
return 0;
}
uint64_t *pml4 = paddr_to_virt(vmm_root_pml4_phys);
uint64_t *pdpt = get_table(pml4, pml4_index(vaddr));
if (!pdpt) return 0;
uint64_t *pd = get_table(pdpt, pdpt_index(vaddr));
if (!pd) return 0;
uint64_t *pt = get_table(pd, pd_index(vaddr));
if (!pt) return 0;
uint16_t idx = pt_index(vaddr);
if (!(pt[idx] & PTE_PRESENT)) {
return 0;
}
return (pt[idx] & ADDR_MASK) | (vaddr & 0xFFF);
}
int vmm_map_range(uint64_t vaddr, uint64_t paddr, uint64_t size, uint64_t flags) {
uint64_t pages = (size + VMM_PAGE_SIZE - 1) / VMM_PAGE_SIZE;
for (uint64_t i = 0; i < pages; ++i) {
if (vmm_map_page(vaddr + i * VMM_PAGE_SIZE, paddr + i * VMM_PAGE_SIZE, flags) != 0) {
return -1;
}
}
return 0;
}
int vmm_query_page(uint64_t vaddr, vmm_page_info_t *info) {
if (info) {
info->present = 0;
info->writable = 0;
info->executable = 0;
}
uint16_t idx = 0;
uint64_t *pt = resolve_pt(vaddr, &idx);
if (!pt) {
return 0;
}
uint64_t entry = pt[idx];
if (!(entry & PTE_PRESENT)) {
return 0;
}
if (info) {
info->present = 1;
info->writable = (entry & PTE_WRITABLE) ? 1 : 0;
info->executable = (entry & PTE_NX) ? 0 : 1;
}
return 1;
}
int vmm_init(BootInfo *boot_info) {
if (!pmm_is_initialized()) {
console_println("VMM init failed: PMM not ready");
return -1;
}
if (!boot_info || !boot_info->memory_map || boot_info->memory_map_descriptor_size == 0) {
console_println("VMM init failed: missing BootInfo");
return -1;
}
vmm_root_pml4_phys = alloc_page_zeroed();
if (vmm_root_pml4_phys == 0) {
console_println("VMM init failed: no memory for PML4");
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;
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 = desc->PhysicalStart;
uint64_t size = desc->NumberOfPages * VMM_PAGE_SIZE;
if (vmm_map_range(start, start, size, VMM_FLAG_WRITABLE) != 0) {
console_println("VMM init failed: mapping RAM region");
return -1;
}
}
/* Map LAPIC and HPET identity — cache-disabled (PCD) as required for MMIO */
if (vmm_map_range(lapic_phys_base, lapic_phys_base, VMM_PAGE_SIZE,
VMM_FLAG_WRITABLE | VMM_FLAG_CACHE_DISABLE) != 0) {
console_println("VMM init failed: map LAPIC MMIO");
return -1;
}
if (vmm_map_range(hpet_phys_base, hpet_phys_base, VMM_PAGE_SIZE,
VMM_FLAG_WRITABLE | VMM_FLAG_CACHE_DISABLE) != 0) {
console_println("VMM init failed: map HPET MMIO");
return -1;
}
/* Map GOP framebuffer identity — MMIO not covered by the RAM walk above.
* QEMU's emulated GOP linear framebuffer sits in a reserved memory range
* (EfiMemoryMappedIO) that is excluded from the RAM walk. Map it WB here
* so the VT100 renderer can write to it after load_cr3(). CACHE_DISABLE
* is omitted deliberately: QEMU's VGA emulation is coherent and UC- mapping
* causes #GP on some PAT/MTRR combinations. */
if (boot_info->framebuffer.base != NULL && boot_info->framebuffer.size > 0) {
uint64_t fb_phys = (uint64_t)(uintptr_t)boot_info->framebuffer.base;
uint64_t fb_size = (uint64_t)boot_info->framebuffer.size;
if (vmm_map_range(fb_phys, fb_phys, fb_size, VMM_FLAG_WRITABLE) != 0) {
console_println("VMM init failed: map GOP framebuffer");
return -1;
}
}
load_cr3(vmm_root_pml4_phys);
console_println("VMM initialized (mapped RAM, CR3 switched)");
return 0;
}