universalisos/kernel/mm.cpp
Fábio Coutada 9540b0528c feat(universalisos): PikeOS-style Phase B/C device drivers + Phase D microkernel
Phase B (Core Device Support) — all drivers verified in QEMU:
- Network: virtio-net cleanup, RTL8139, E1000, clause-22 MDIO PHY management,
  CAN bus, industrial protocols (Modbus/Profibus/EtherCAT), controller probe+dispatch
- Block storage: RAM disk backend (write->read->verify PASSED), virtio-blk transport,
  backend dispatch, real MBR+GPT partition parsers, SD/eMMC command framework
- GPIO: PL061 (verified), I2C: DesignWare (verified), SPI: PL022 (verified)

Phase C (Advanced Features):
- PCI: FULL PikeOS ARMv7 replica — transport-agnostic uos_pci_ops, config-address
  encoding, BAR sizing, capability walk, enumeration+bridge recursion, MSI/MSI-X
- USB: PikeOS-style layered stack — usb.h contract, usb_core.cpp (enumeration
  state machine), usb_ehci.cpp (EHCI transport)
- Display: FULL 1:1 PikeOS fbcon replica + copied font_8x16

Build foundation fixes:
- Freestanding aeabi_runtime.cpp (__aeabi_uidiv/__aeabi_uldivmod)
- PikeOS-style flat 4GB MMU section map + proper enable (unblocked device MMIO)
- guest.h MAX_GUEST_IMAGE_SIZE 256MB->16MB (BSS was 259MB)
- C/C++ linkage fixes, duplicate-virtio_net_init, MMIO access-size handling

Phase D (PikeOS ARMv7 Microkernel Port):
- D-1: Per-VM address spaces — cloned pgdirs, ASID-tagged TLB, 4K page walker,
  isolation PASSED (two guests, same VA->different PAs), guest fault recovery
- D-2: IRQ dispatch backbone — 1024-slot dispatch table, real GICv2 hardware
  (GICD_CTLR/GICC_CTLR/GICC_PMR/GICC_IAR/GICC_EOIR), arm_irq_handler wired
- D-3: Time subsystem — CNTVCT ns-since-boot, CNTP periodic ticker via D-2
- D-4: KDEV framework — linker-section driver registration, uos_kdev_init_all,
  name lookup
- D-5: VFP/NEON — lazy enable (undef trap->CPACR+FPEXC.EN), FPEXC=0x40000000
- D-6: SMP — per-CPU state, MPIDR, IPI/SGI framework (reschedule+TLB flush)

All uos_ naming (PikeOS p4_ convention adapted). Compiles -Werror freestanding C++17.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 09:10:53 +01:00

473 lines
No EOL
15 KiB
C++

/*
* Universalisos Memory Management Implementation - Stage 3
* PikeOS 5.0 Feature Parity - Memory Virtualization
*
* Stage 3: Memory Management + VM Context Switching
* Author: PortugalFuturista Hypervisor Development Team
* Version: 1.0.0
*/
#include "mm.h"
#include "arch/arm/uart.h"
#include <stdint.h>
// Global memory management state
static mm_state_t g_mm_state = {
.kernel_page_table = {
.ttb0 = nullptr,
.ttb1 = nullptr,
.ttb0_size = 0,
.ttb1_size = 0,
.using_ttbr0 = false,
.current_domain = 0
},
.vm_page_tables = {},
.active_vm_count = 0,
.domains = {},
.active_domain_count = 0,
.total_memory = 0,
.free_memory = 0,
.hypervisor_memory = 0,
.mmu_enabled = false,
.caches_enabled = false
};
// Simple page table allocator (Stage 3 - will be improved later)
static uint32_t page_table_memory[4096] __attribute__((aligned(16384))); // 16KB aligned
/* Expose the flat kernel pgdir so D-1's per-VM cloned pgdirs can copy it. */
extern "C" uint32_t *uos_get_kernel_pgdir(void) { return page_table_memory; }
/**
* Initialize Memory Management System
*/
extern "C" void mm_init(void) {
uart_puts("MM: Initializing Memory Management System\n");
// Initialize state
g_mm_state.total_memory = 512 * 1024 * 1024; // 512MB for QEMU virt
g_mm_state.free_memory = g_mm_state.total_memory - (2 * 1024 * 1024); // Reserve 2MB for hypervisor
g_mm_state.hypervisor_memory = 2 * 1024 * 1024;
g_mm_state.mmu_enabled = false;
g_mm_state.caches_enabled = false;
// Initialize VM page tables
for (int i = 0; i < 16; i++) {
g_mm_state.vm_page_tables[i].ttb0 = nullptr;
g_mm_state.vm_page_tables[i].ttb1 = nullptr;
g_mm_state.vm_page_tables[i].ttb0_size = 0;
g_mm_state.vm_page_tables[i].ttb1_size = 0;
}
g_mm_state.active_vm_count = 0;
uart_puts("MM: Memory Management System initialized\n");
uart_puts("MM: Total memory: ");
uart_print_dec(g_mm_state.total_memory / (1024 * 1024));
uart_puts(" MB\n");
uart_puts("MM: Free memory: ");
uart_print_dec(g_mm_state.free_memory / (1024 * 1024));
uart_puts(" MB\n");
}
/**
* Initialize ARMv7 MMU
*/
extern "C" void mmu_init(void) {
uart_puts("MMU: Initializing ARMv7 MMU\n");
// Initialize kernel page table with simple identity mapping
g_mm_state.kernel_page_table.ttb0 = page_table_memory;
g_mm_state.kernel_page_table.ttb0_size = 16384; // 16KB
g_mm_state.kernel_page_table.ttb1 = nullptr;
g_mm_state.kernel_page_table.using_ttbr0 = true;
g_mm_state.kernel_page_table.current_domain = 0; // Domain 0
// Clear page table
for (int i = 0; i < 4096; i++) {
page_table_memory[i] = 0;
}
// PikeOS-style flat 1:1 section map (non-LPAE ARMv7), covering the full 4 GB
// address space so every physical region is reachable after the MMU is on.
// Adapted from PikeOS boot_map.c (psp_arm_boot_map_ram / _io). Constants use
// the Universalisos uos_ convention instead of PikeOS's p4_/PD_.
//
// Section descriptor layout (armmmu-v6.h):
// [31:20] base [19:12] TEX [11:10] AP [9] impl [8:5] domain/impl/AP2
// [4] XN [3] C [2] B [1] section-type(=1) [0] 0
// => section type = 0b10 => bit1 set.
#define UOS_PD_SECT (1u << 1) /* section descriptor */
#define UOS_PD_B (1u << 2) /* bufferable */
#define UOS_PD_C (1u << 3) /* cacheable */
#define UOS_PD_XN (1u << 4) /* execute never */
#define UOS_PD_AP0 (1u << 10) /* AP[1:0] = 01 => kernel RW, user none */
#define UOS_PD_TEX0 (1u << 12) /* TEX bit 0 */
/* RAM: cacheable normal write-back (TEX=001,C=1,B=1), executable.
* I/O / device: Device memory (TEX=000,C=0,B=1), executable.
*
* Device memory (not strongly-ordered) is the correct attribute for MMIO
* registers including PCIe ECAM: it permits write buffering while keeping
* device access semantics, and avoids the strict completion-ack demands of
* strongly-ordered memory under which QEMU's gpex ECAM reads can deadlock.
* The whole map stays executable so the exception vectors at 0x0 are
* reachable (matches PikeOS's boot identity map). */
const uint32_t ram_section = UOS_PD_SECT | UOS_PD_AP0 | UOS_PD_TEX0 | UOS_PD_C | UOS_PD_B;
const uint32_t io_section = UOS_PD_SECT | UOS_PD_AP0 | UOS_PD_B;
/* QEMU virt RAM window: 0x40000000 .. 0x60000000 (512 MiB, sections 1024..1535) */
const uint32_t ram_first_section = 0x40000000u >> 20; /* 1024 */
const uint32_t ram_last_section = 0x60000000u >> 20; /* 1536 (exclusive) */
for (uint32_t i = 0; i < 4096u; i++) {
uint32_t phys = i << 20; /* 1 MB section base */
if (i >= ram_first_section && i < ram_last_section) {
page_table_memory[i] = phys | ram_section;
} else {
page_table_memory[i] = phys | io_section;
}
}
uart_puts("MMU: flat 4GB section map built (RAM cacheable, I/O strongly-ordered)\n");
/* Turn the MMU on with the flat map we just built (PikeOS-style). With the
* MMU actually enabled, device regions are properly attributed (strongly
* ordered) and reachable, and the hypervisor can later add per-VM mappings.
* This is the fix for the MMIO-access hangs: previously mmu_enable() was
* never called, leaving the page table built but inert. */
extern void mmu_enable(void);
mmu_enable();
/* D-1: allocate the per-VM page-directory pool (cloned from this flat map). */
extern int uos_arm_init_mmu(void);
uos_arm_init_mmu();
}
/**
* Enable ARMv7 MMU (PikeOS-style, adapted).
*
* Mirrors PREBOOT_psp_arm_boot_map_activate() + the startup M-bit flip in
* PikeOS's boot_map.c: program DACR/TTBCR/TTBR0, flush TLB, then set SCTLR.M.
* The page table is a flat identity map (VA == PA), so the instruction stream
* keeps running unchanged across the MMU-on transition.
*
* Caches are left OFF on first enable so the page walker always reads the table
* we just wrote straight from RAM (no D-cache coherency window). This is the
* conservative, PikeOS-boot-equivalent path.
*/
extern "C" void mmu_enable(void) {
uart_puts("MMU: enabling (PikeOS-style flat map)\n");
/* TTB_FLAGS = outer-cacheable write-allocate | inner-region bit0
* (armmmu-v6.h: TTB_OC_WA | TTB_IRGN0). Harmless with D-cache off. */
const uint32_t UOS_TTB_FLAGS = (1u << 3) | (1u << 6);
/* Domain access control: 0x55555555 => every domain = client (01),
* so the AP bits in each descriptor are checked. */
__asm__ volatile("mcr p15, 0, %0, c3, c0, 0" : : "r"(0x55555555u));
/* TTBCR = 0: no TTBR0/TTBR1 split, TTBR0 covers the whole space. */
__asm__ volatile("mcr p15, 0, %0, c2, c0, 2" : : "r"(0u));
/* TTBR0 = page table base | TTB_FLAGS. */
uint32_t ttbr0 = (uint32_t)page_table_memory | UOS_TTB_FLAGS;
__asm__ volatile("mcr p15, 0, %0, c2, c0, 0" : : "r"(ttbr0));
/* Push the table writes and drop any stale TLB entries before flipping M. */
__asm__ volatile("dsb");
__asm__ volatile("mcr p15, 0, %0, c8, c7, 0" : : "r"(0)); /* TLBIALL */
__asm__ volatile("isb");
/* Flip the M bit. Keep A=0 (EABI assumption). Leave C/I as-is. */
uint32_t sctlr;
__asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(sctlr));
sctlr |= (1u << 0); /* M: MMU enable */
sctlr &= ~(1u << 1); /* A: alignment checking off (EABI codegen) */
__asm__ volatile("mcr p15, 0, %0, c1, c0, 0" : : "r"(sctlr));
__asm__ volatile("dsb");
__asm__ volatile("isb");
g_mm_state.mmu_enabled = true;
uart_puts("MMU: enabled, identity map active\n");
}
/**
* Disable ARMv7 MMU
*/
extern "C" void mmu_disable(void) {
uart_puts("MMU: Disabling MMU\n");
uint32_t sctlr;
__asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(sctlr));
sctlr &= ~(1 << 0); // M bit - MMU disable
sctlr &= ~(1 << 2); // C bit - Data cache disable
sctlr &= ~(1 << 12); // I bit - Instruction cache disable
__asm__ volatile("mcr p15, 0, %0, c1, c0, 0" : : "r"(sctlr));
g_mm_state.mmu_enabled = false;
g_mm_state.caches_enabled = false;
uart_puts("MMU: MMU and caches disabled\n");
}
/**
* Create page table for VM
*/
extern "C" page_table_t* mm_create_page_table(uint32_t vm_id) {
if (vm_id >= 16) {
uart_puts("MM: Invalid VM ID for page table creation\n");
return nullptr;
}
uart_puts("MM: Creating page table for VM ");
uart_print_dec(vm_id);
uart_puts("\n");
// Allocate page table (simplified for Stage 3)
static uint32_t vm_page_tables[16][4096] __attribute__((aligned(16384)));
page_table_t* pt = &g_mm_state.vm_page_tables[vm_id];
pt->ttb0 = vm_page_tables[vm_id];
pt->ttb0_size = 16384;
pt->ttb1 = nullptr;
pt->using_ttbr0 = true;
pt->current_domain = vm_id + 1; // Use VM ID + 1 as domain
// Clear page table
for (int i = 0; i < 4096; i++) {
vm_page_tables[vm_id][i] = 0;
}
g_mm_state.active_vm_count++;
uart_puts("MM: Page table created for VM ");
uart_print_dec(vm_id);
uart_puts(" (Domain ");
uart_print_dec(pt->current_domain);
uart_puts(")\n");
return pt;
}
/**
* Destroy page table
*/
extern "C" void mm_destroy_page_table(page_table_t* pt) {
if (!pt) return;
uart_puts("MM: Destroying page table\n");
// Clear page table entries
if (pt->ttb0) {
for (int i = 0; i < 4096; i++) {
((uint32_t*)pt->ttb0)[i] = 0;
}
}
pt->ttb0 = nullptr;
pt->ttb0_size = 0;
pt->using_ttbr0 = false;
g_mm_state.active_vm_count--;
uart_puts("MM: Page table destroyed\n");
}
/**
* Map physical to virtual address
*/
extern "C" bool mm_map_page(page_table_t* pt, uint32_t virt_addr, uint32_t phys_addr,
uint32_t permissions, uint32_t domain) {
(void)permissions; // Unused in Stage 3
if (!pt || !pt->ttb0) {
return false;
}
// Simple section mapping (1MB granularity for Stage 3)
uint32_t section_index = (virt_addr >> 20) & 0xFFF;
uint32_t section_phys = phys_addr & 0xFFF00000;
uint32_t descriptor = section_phys | 0x400 | 0x10 | 0x02; // Section, domain 0, manager
descriptor |= (domain & 0xF) << 5; // Set domain
((uint32_t*)pt->ttb0)[section_index] = descriptor;
return true;
}
/**
* Unmap virtual address
*/
extern "C" void mm_unmap_page(page_table_t* pt, uint32_t virt_addr) {
if (!pt || !pt->ttb0) {
return;
}
uint32_t section_index = (virt_addr >> 20) & 0xFFF;
((uint32_t*)pt->ttb0)[section_index] = 0;
}
/**
* Create memory domain
*/
extern "C" memory_domain_t* mm_create_domain(uint32_t domain_id, const char* name) {
if (domain_id >= 16) {
uart_puts("MM: Invalid domain ID\n");
return nullptr;
}
uart_puts("MM: Creating memory domain '");
uart_puts(name);
uart_puts("' (ID ");
uart_print_dec(domain_id);
uart_puts(")\n");
memory_domain_t* domain = &g_mm_state.domains[domain_id];
domain->domain_id = domain_id;
domain->domain_name = name;
domain->regions = nullptr;
domain->region_count = 0;
domain->access_permissions = DOMAIN_CLIENT;
domain->enable_protection = true;
domain->enable_logging = false;
uart_puts("MM: Memory domain created\n");
return domain;
}
/**
* Destroy memory domain
*/
extern "C" void mm_destroy_domain(memory_domain_t* domain) {
if (!domain) return;
uart_puts("MM: Destroying memory domain '");
uart_puts(domain->domain_name);
uart_puts("'\n");
domain->domain_id = 0;
domain->domain_name = nullptr;
domain->regions = nullptr;
domain->region_count = 0;
uart_puts("MM: Memory domain destroyed\n");
}
/**
* Add memory region to domain
*/
extern "C" bool mm_add_region_to_domain(memory_domain_t* domain, memory_region_t* region) {
if (!domain || !region) {
return false;
}
uart_puts("MM: Adding region to domain '");
uart_puts(domain->domain_name);
uart_puts("'\n");
// For Stage 3, just print the information
// In full implementation, would add to domain's region array
uart_puts("MM: Base: 0x");
uart_print_hex(region->base_address);
uart_puts("\n");
uart_puts("MM: Size: ");
uart_print_dec(region->size / (1024 * 1024));
uart_puts(" MB\n");
return true;
}
/**
* Handle data abort (memory fault)
*/
extern "C" void mm_handle_data_abort(uint32_t fault_address, uint32_t fault_status) {
uart_puts("\n!!! MM DATA ABORT !!!\n");
uart_puts("Fault Address: 0x");
uart_print_hex(fault_address);
uart_puts("\n");
uart_puts("Fault Status: 0x");
uart_print_hex(fault_status);
uart_puts("\n");
// Decode fault status
uart_puts("Fault Type: ");
if (fault_status & 0x08) uart_puts("Debug event\n");
else if (fault_status & 0x04) uart_puts("Translation fault\n");
else if (fault_status & 0x02) uart_puts("Access flag fault\n");
else if (fault_status & 0x01) uart_puts("Domain fault\n");
else uart_puts("Unknown\n");
// For Stage 3, just halt
uart_puts("System halted for memory safety analysis.\n");
while(1) {
__asm__("wfi");
}
}
/**
* Handle prefetch abort (instruction fetch fault)
*/
extern "C" void mm_handle_prefetch_abort(uint32_t fault_address, uint32_t fault_status) {
uart_puts("\n!!! MM PREFETCH ABORT !!!\n");
uart_puts("Fault Address: 0x");
uart_print_hex(fault_address);
uart_puts("\n");
uart_puts("Fault Status: 0x");
uart_print_hex(fault_status);
uart_puts("\n");
// For Stage 3, just halt
uart_puts("System halted for memory safety analysis.\n");
while(1) {
__asm__("wfi");
}
}
/**
* Flush TLB entries
*/
extern "C" void mm_flush_tlb(void) {
__asm__ volatile("mcr p15, 0, r0, c8, c7, 0"); // TLBIALL
__asm__ volatile("dsb");
__asm__ volatile("isb");
}
/**
* Print memory statistics
*/
extern "C" void mm_print_statistics(void) {
uart_puts("\n=== Memory Management Statistics ===\n");
uart_puts("Total Memory: ");
uart_print_dec(g_mm_state.total_memory / (1024 * 1024));
uart_puts(" MB\n");
uart_puts("Free Memory: ");
uart_print_dec(g_mm_state.free_memory / (1024 * 1024));
uart_puts(" MB\n");
uart_puts("Hypervisor Memory: ");
uart_print_dec(g_mm_state.hypervisor_memory / (1024 * 1024));
uart_puts(" MB\n");
uart_puts("Active VMs: ");
uart_print_dec(g_mm_state.active_vm_count);
uart_puts("\n");
uart_puts("MMU Enabled: ");
uart_puts(g_mm_state.mmu_enabled ? "Yes\n" : "No\n");
uart_puts("Caches Enabled: ");
uart_puts(g_mm_state.caches_enabled ? "Yes\n" : "No\n");
uart_puts("======================================\n\n");
}