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>
176 lines
7.2 KiB
C++
176 lines
7.2 KiB
C++
/*
|
||
* Universalisos ARM MMU core — CP15 primitives + init/activate scaffolding.
|
||
*
|
||
* D-1.2 scaffolding: the CP15 primitives (uos_arm_set_asid / _set_ttbr0 /
|
||
* _inval_btac / tlb_inval_*) and the per-VM user-pgdir pool that uos_adspace.cpp
|
||
* references, plus link-stubs for the (later) uos_arm_init_mmu / _activate_mmu /
|
||
* fault decoders. Full bodies land in D-1.3 (activate_mmu + handover) and D-1.6
|
||
* (abort decoders).
|
||
*
|
||
* Author: PortugalFuturista Hypervisor Development Team (adapted from SYSGO PikeOS)
|
||
*/
|
||
|
||
#include "uos_adspace.h"
|
||
#include "uos_mmu_walk.h"
|
||
#include "uos_psp.h"
|
||
|
||
/* The single global PSP descriptor pointer (kglobal.psp). D-1.3's uos_psp_init
|
||
* installs it; for now it is NULL so the dead code never dereferences it. */
|
||
uos_psp_descriptor_t *uos_psp = nullptr;
|
||
|
||
/* Per-VM user page-directory pool. Allocated by uos_arm_init_mmu (D-1.3); NULL
|
||
* until then — uos_arch_adspace_register_user is dead code in D-1.2. */
|
||
uint32_t *uos_user_pgdirs = nullptr;
|
||
|
||
/* -------------------------------------------------------------------------
|
||
* CP15 primitives (PikeOS p4arm_* in armmacro.S / armcpu.h)
|
||
* ------------------------------------------------------------------------- */
|
||
void uos_arm_dsb(void) {
|
||
__asm__ volatile("dsb" ::: "memory");
|
||
}
|
||
|
||
void uos_arm_set_asid(uint32_t asid) {
|
||
/* CONTEXTIDR, PROCID+ASID: cp15 c13,c0,1 */
|
||
__asm__ volatile("mcr p15, 0, %0, c13, c0, 1" : : "r"(asid));
|
||
}
|
||
|
||
void uos_arm_set_ttbr0(uint32_t ttbr0) {
|
||
/* TTBR0: cp15 c2,c0,0 */
|
||
__asm__ volatile("mcr p15, 0, %0, c2, c0, 0" : : "r"(ttbr0));
|
||
}
|
||
|
||
void uos_arm_inval_btac(void) {
|
||
/* Invalidate entire branch target address cache (Errata 754322 hygiene). */
|
||
__asm__ volatile("mcr p15, 0, %0, c7, c5, 6" : : "r"(0)); /* flush BTAC */
|
||
__asm__ volatile("mcr p15, 0, %0, c7, c5, 0" : : "r"(0)); /* invalidate I-cache */
|
||
}
|
||
|
||
void uos_arm_tlb_inval_asid(uint32_t asid) {
|
||
/* TLBIASID: cp15 c8,c7,2 — invalidate all TLB entries matching the ASID. */
|
||
__asm__ volatile("mcr p15, 0, %0, c8, c7, 2" : : "r"(asid));
|
||
}
|
||
|
||
void uos_arm_tlb_inval_page(uint32_t mva) {
|
||
/* TLBIMVA: cp15 c8,c7,1 — invalidate by MVA (ASID-tagged). */
|
||
__asm__ volatile("mcr p15, 0, %0, c8, c7, 1" : : "r"(mva));
|
||
}
|
||
|
||
void uos_arm_tlb_inval_all(void) {
|
||
__asm__ volatile("mcr p15, 0, %0, c8, c7, 0" : : "r"(0)); /* TLBIALL */
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
* D-1.3 / D-1.6 bodies (stubs for now so the unit links; replaced later)
|
||
* ------------------------------------------------------------------------- */
|
||
/* The kernel flat-map pgdir (defined in mm.cpp). */
|
||
extern "C" uint32_t *uos_get_kernel_pgdir(void);
|
||
extern "C" void *memcpy(void *dest, const void *src, unsigned long n);
|
||
|
||
/* Per-VM page-directory pool: 16 guests × 4096 entries × 4 bytes = 256 KiB.
|
||
* Each entry is cloned from the kernel flat map so kernel/device sections
|
||
* (AP=privileged-only) are present in every guest pgdir. Guest-specific user
|
||
* pages are layered on top with AP=user-RW. */
|
||
#define UOS_NUM_VM_PGDIRS 16u
|
||
static uint32_t __attribute__((aligned(16384)))
|
||
g_uos_vm_pgdir_pool[UOS_NUM_VM_PGDIRS][UOS_PD_ENTRIES];
|
||
|
||
extern "C" int uos_arm_init_mmu(void) {
|
||
uint32_t *kern = uos_get_kernel_pgdir();
|
||
|
||
/* Clone the kernel flat map into every per-VM pgdir. */
|
||
for (uint32_t i = 0; i < UOS_NUM_VM_PGDIRS; i++) {
|
||
memcpy(g_uos_vm_pgdir_pool[i], kern,
|
||
UOS_PD_ENTRIES * sizeof(uint32_t));
|
||
}
|
||
|
||
uos_user_pgdirs = (uint32_t *)g_uos_vm_pgdir_pool;
|
||
return 0;
|
||
}
|
||
|
||
int uos_arm_activate_mmu(void) {
|
||
/* TODO D-1.3: write TTBR0/TTBR1, set TTBCR=1, DACR=0x55555555, TLBIALL,
|
||
* enable SCTLR.M. */
|
||
return 0;
|
||
}
|
||
|
||
/* PSP init stub (real body in D-1.3, populated from UART/timer/board_halt). */
|
||
void uos_psp_init(void) {
|
||
/* TODO D-1.3. */
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
* D-1.5 isolation demo: prove per-VM address spaces work.
|
||
*
|
||
* Creates two tasks (guest 0, guest 1), maps the SAME virtual address
|
||
* (0x1000) to DIFFERENT physical pages in each guest's pgdir, switches
|
||
* between them, and verifies the writes don't collide — i.e. the per-VM
|
||
* TTBR0 + ASID gives real address-space isolation.
|
||
* ------------------------------------------------------------------------- */
|
||
#include "uart.h"
|
||
|
||
extern "C" void uos_adspace_isolation_demo(void) {
|
||
uart_puts("\n=== Per-VM Address-Space Isolation Demo ===\n");
|
||
|
||
/* Two task contexts (guest 0 and 1). */
|
||
static uos_taskinfo_t g0, g1;
|
||
uos_arch_adspace_init(&g0); g0.taskno = 1;
|
||
uos_arch_adspace_init(&g1); g1.taskno = 2;
|
||
uos_arch_adspace_register_user(&g0);
|
||
uos_arch_adspace_register_user(&g1);
|
||
uart_puts("D-1: adspace registered (g0 pgdir 0x"); uart_print_hex((uint32_t)(uintptr_t)g0.adspace.pgdir);
|
||
uart_puts(", g1 pgdir 0x"); uart_print_hex((uint32_t)(uintptr_t)g1.adspace.pgdir); uart_puts(")\n");
|
||
|
||
/* Map VA 0x1000 -> PA 0x50000000 in guest 0, PA 0x50100000 in guest 1. */
|
||
uos_access_t rw = UOS_M_READ | UOS_M_WRITE;
|
||
int rc0 = uos_arch_mem_create(&g0, 0x1000u, 4096u, 0x50000000u, rw);
|
||
int rc1 = uos_arch_mem_create(&g1, 0x1000u, 4096u, 0x50100000u, rw);
|
||
uart_puts("D-1: mem_create rc="); uart_print_dec((uint32_t)rc0);
|
||
uart_puts(","); uart_print_dec((uint32_t)rc1); uart_puts("\n");
|
||
|
||
/* Switch to guest 0, write pattern A at VA 0x1000. */
|
||
uart_puts("D-1: set_active g0...\n");
|
||
uos_arch_adspace_set_active(&g0, nullptr);
|
||
uart_puts("D-1: g0 active, writing 0xDEADBEEF...\n");
|
||
*((volatile uint32_t *)0x1000u) = 0xDEADBEEF;
|
||
uart_puts("D-1: g0 write OK\n");
|
||
|
||
/* Switch to guest 1, write pattern B at VA 0x1000. */
|
||
uart_puts("D-1: set_active g1...\n");
|
||
uos_arch_adspace_set_active(&g1, &g0);
|
||
uart_puts("D-1: g1 active, writing 0xCAFEBABE...\n");
|
||
*((volatile uint32_t *)0x1000u) = 0xCAFEBABE;
|
||
uart_puts("D-1: g1 write OK\n");
|
||
|
||
/* Switch back to guest 0, verify pattern A survived (isolation). */
|
||
uart_puts("D-1: set_active g0 again...\n");
|
||
uos_arch_adspace_set_active(&g0, &g1);
|
||
uart_puts("D-1: g0 re-active, reading...\n");
|
||
uint32_t val0 = *((volatile uint32_t *)0x1000u);
|
||
|
||
/* Read guest 1's page via its identity PA to confirm pattern B. */
|
||
uint32_t val1_phys = *((volatile uint32_t *)0x50100000u);
|
||
|
||
/* V-D1.3: test that an unmapped guest access faults and recovers (not halts). */
|
||
static uos_taskinfo_t idle = { { nullptr }, 0 };
|
||
uart_puts("D-1: testing guest fault recovery...\n");
|
||
uos_arch_adspace_set_active(&g0, &idle);
|
||
uart_puts("D-1: g0 active, reading unmapped VA 0x2000000...\n");
|
||
volatile uint32_t unmapped = *((volatile uint32_t *)0x2000000u);
|
||
(void)unmapped; /* if we get here, the handler recovered */
|
||
uart_puts("D-1: survived guest fault (handler recovered)\n");
|
||
|
||
/* Restore kernel address space (idle task, pgdir=NULL). */
|
||
uos_arch_adspace_set_active(&idle, &g0);
|
||
|
||
uart_puts("D-1: guest0 VA 0x1000 = 0x"); uart_print_hex(val0);
|
||
uart_puts(" (expect 0xDEADBEEF)\n");
|
||
uart_puts("D-1: guest1 PA 0x50100000 = 0x"); uart_print_hex(val1_phys);
|
||
uart_puts(" (expect 0xCAFEBABE)\n");
|
||
|
||
if (val0 == 0xDEADBEEF && val1_phys == 0xCAFEBABE) {
|
||
uart_puts("D-1: per-VM isolation PASSED\n");
|
||
} else {
|
||
uart_puts("D-1: per-VM isolation FAILED\n");
|
||
}
|
||
uart_puts("=== End Isolation Demo ===\n\n");
|
||
}
|