From 5daa18a19257b0f92f3a0c980105e1479c773236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Coutada?= Date: Thu, 9 Jul 2026 21:03:33 +0100 Subject: [PATCH] refactor: restructure kernel architecture by reorganizing source files and implementing RISC-V support --- kernel/arch/riscv/boot.S | 177 ----------- .../arm => src/arch/armv7}/aeabi_runtime.cpp | 0 kernel/{arch/arm => src/arch/armv7}/boot.S | 29 +- .../src/arch/armv7/config/cfg_boot_armv7.cpp | 144 +++++++++ kernel/src/arch/armv7/config/cfg_boot_armv7.h | 37 +++ .../arm => src/arch/armv7}/context_switch.cpp | 0 .../arch/armv7}/context_switch_asm.S | 0 .../arm => src/arch/armv7}/exceptions.cpp | 28 +- .../arch/armv7/inc}/context_switch.h | 0 .../arm => src/arch/armv7/inc}/exceptions.h | 0 .../{arch/arm => src/arch/armv7/inc}/uart.h | 0 .../arm => src/arch/armv7/inc}/uos_adspace.h | 0 .../arm => src/arch/armv7/inc}/uos_mmu_walk.h | 0 kernel/{arch/arm => src/arch/armv7}/linker.ld | 0 kernel/src/arch/armv7/sched/ctx_switch.S | 56 ++++ kernel/src/arch/armv7/sched/preempt_switch.S | 139 +++++++++ kernel/{arch/arm => src/arch/armv7}/string.S | 0 kernel/{arch/arm => src/arch/armv7}/uart.cpp | 0 .../arm => src/arch/armv7}/uos_adspace.cpp | 0 .../{arch/arm => src/arch/armv7}/uos_cmm.cpp | 0 .../arm => src/arch/armv7}/uos_mmu_walk.cpp | 0 kernel/src/arch/riscv/boot.S | 167 ++++++++++ kernel/src/arch/riscv/context_switch.cpp | 27 ++ kernel/src/arch/riscv/context_switch_asm.S | 168 ++++++++++ kernel/src/arch/riscv/cpu.cpp | 56 ++++ kernel/src/arch/riscv/exceptions.cpp | 225 +++++++++++++ kernel/src/arch/riscv/guest_boot.cpp | 98 ++++++ kernel/src/arch/riscv/hm.cpp | 107 +++++++ kernel/src/arch/riscv/inc/context_switch.h | 30 ++ kernel/src/arch/riscv/inc/cpu.h | 36 +++ kernel/src/arch/riscv/inc/csr.h | 184 +++++++++++ kernel/src/arch/riscv/inc/exceptions.h | 85 +++++ kernel/src/arch/riscv/inc/guest_boot.h | 51 +++ kernel/src/arch/riscv/inc/hm.h | 74 +++++ kernel/src/arch/riscv/inc/mem_pikeos.h | 76 +++++ kernel/src/arch/riscv/inc/page_table.h | 86 +++++ kernel/src/arch/riscv/inc/plic.h | 33 ++ kernel/src/arch/riscv/inc/sbi.h | 47 +++ kernel/src/arch/riscv/inc/sched_pikeos.h | 139 +++++++++ kernel/src/arch/riscv/inc/timer.h | 23 ++ kernel/src/arch/riscv/inc/uart.h | 16 + kernel/src/arch/riscv/inc/vm.h | 64 ++++ kernel/src/arch/riscv/kernel_riscv.cpp | 113 +++++++ kernel/src/arch/riscv/linker.ld | 54 ++++ kernel/src/arch/riscv/linker_polarfire.ld | 54 ++++ kernel/src/arch/riscv/mem_pikeos.cpp | 139 +++++++++ kernel/src/arch/riscv/page_table.cpp | 103 ++++++ kernel/src/arch/riscv/plic.cpp | 100 ++++++ kernel/src/arch/riscv/sbi.cpp | 32 ++ kernel/src/arch/riscv/sched_pikeos.cpp | 295 ++++++++++++++++++ kernel/src/arch/riscv/timer.cpp | 40 +++ kernel/src/arch/riscv/trap.S | 135 ++++++++ kernel/src/arch/riscv/uart.cpp | 79 +++++ kernel/src/arch/riscv/vm.cpp | 132 ++++++++ kernel/{ => src/core}/kernel.cpp | 0 kernel/src/{ => core}/uos_api.cpp | 0 kernel/src/{ => core}/uos_drivers.cpp | 0 kernel/src/{ => core}/uos_syscalls.cpp | 0 kernel/src/{ => core}/usp.cpp | 0 kernel/src/{ => core}/usp_arm.cpp | 0 kernel/src/{ => core}/usp_board.cpp | 0 kernel/{ => src/platform}/drivers/block.cpp | 0 kernel/{ => src/platform}/drivers/block.h | 0 kernel/{ => src/platform}/drivers/driver.cpp | 0 kernel/{ => src/platform}/drivers/driver.h | 0 .../{ => src/platform}/drivers/font_8x16.cpp | 0 kernel/{ => src/platform}/drivers/gpio.cpp | 0 kernel/{ => src/platform}/drivers/gpio.h | 0 kernel/{ => src/platform}/drivers/i2c.cpp | 0 kernel/{ => src/platform}/drivers/i2c.h | 0 kernel/{ => src/platform}/drivers/network.cpp | 0 kernel/{ => src/platform}/drivers/network.h | 0 kernel/{ => src/platform}/drivers/pci.cpp | 0 kernel/{ => src/platform}/drivers/pci.h | 0 kernel/{ => src/platform}/drivers/spi.cpp | 0 kernel/{ => src/platform}/drivers/spi.h | 0 kernel/{ => src/platform}/drivers/timer.cpp | 0 kernel/{ => src/platform}/drivers/timer.h | 0 kernel/{ => src/platform}/drivers/uart.cpp | 0 kernel/{ => src/platform}/drivers/uart.h | 0 .../{ => src/platform}/drivers/uos_fbcon.cpp | 0 kernel/{ => src/platform}/drivers/uos_fbcon.h | 0 kernel/{ => src/platform}/drivers/usb.h | 0 .../{ => src/platform}/drivers/usb_core.cpp | 0 .../{ => src/platform}/drivers/usb_ehci.cpp | 0 85 files changed, 3475 insertions(+), 203 deletions(-) delete mode 100644 kernel/arch/riscv/boot.S rename kernel/{arch/arm => src/arch/armv7}/aeabi_runtime.cpp (100%) rename kernel/{arch/arm => src/arch/armv7}/boot.S (89%) create mode 100644 kernel/src/arch/armv7/config/cfg_boot_armv7.cpp create mode 100644 kernel/src/arch/armv7/config/cfg_boot_armv7.h rename kernel/{arch/arm => src/arch/armv7}/context_switch.cpp (100%) rename kernel/{arch/arm => src/arch/armv7}/context_switch_asm.S (100%) rename kernel/{arch/arm => src/arch/armv7}/exceptions.cpp (94%) rename kernel/{arch/arm => src/arch/armv7/inc}/context_switch.h (100%) rename kernel/{arch/arm => src/arch/armv7/inc}/exceptions.h (100%) rename kernel/{arch/arm => src/arch/armv7/inc}/uart.h (100%) rename kernel/{arch/arm => src/arch/armv7/inc}/uos_adspace.h (100%) rename kernel/{arch/arm => src/arch/armv7/inc}/uos_mmu_walk.h (100%) rename kernel/{arch/arm => src/arch/armv7}/linker.ld (100%) create mode 100644 kernel/src/arch/armv7/sched/ctx_switch.S create mode 100644 kernel/src/arch/armv7/sched/preempt_switch.S rename kernel/{arch/arm => src/arch/armv7}/string.S (100%) rename kernel/{arch/arm => src/arch/armv7}/uart.cpp (100%) rename kernel/{arch/arm => src/arch/armv7}/uos_adspace.cpp (100%) rename kernel/{arch/arm => src/arch/armv7}/uos_cmm.cpp (100%) rename kernel/{arch/arm => src/arch/armv7}/uos_mmu_walk.cpp (100%) create mode 100644 kernel/src/arch/riscv/boot.S create mode 100644 kernel/src/arch/riscv/context_switch.cpp create mode 100644 kernel/src/arch/riscv/context_switch_asm.S create mode 100644 kernel/src/arch/riscv/cpu.cpp create mode 100644 kernel/src/arch/riscv/exceptions.cpp create mode 100644 kernel/src/arch/riscv/guest_boot.cpp create mode 100644 kernel/src/arch/riscv/hm.cpp create mode 100644 kernel/src/arch/riscv/inc/context_switch.h create mode 100644 kernel/src/arch/riscv/inc/cpu.h create mode 100644 kernel/src/arch/riscv/inc/csr.h create mode 100644 kernel/src/arch/riscv/inc/exceptions.h create mode 100644 kernel/src/arch/riscv/inc/guest_boot.h create mode 100644 kernel/src/arch/riscv/inc/hm.h create mode 100644 kernel/src/arch/riscv/inc/mem_pikeos.h create mode 100644 kernel/src/arch/riscv/inc/page_table.h create mode 100644 kernel/src/arch/riscv/inc/plic.h create mode 100644 kernel/src/arch/riscv/inc/sbi.h create mode 100644 kernel/src/arch/riscv/inc/sched_pikeos.h create mode 100644 kernel/src/arch/riscv/inc/timer.h create mode 100644 kernel/src/arch/riscv/inc/uart.h create mode 100644 kernel/src/arch/riscv/inc/vm.h create mode 100644 kernel/src/arch/riscv/kernel_riscv.cpp create mode 100644 kernel/src/arch/riscv/linker.ld create mode 100644 kernel/src/arch/riscv/linker_polarfire.ld create mode 100644 kernel/src/arch/riscv/mem_pikeos.cpp create mode 100644 kernel/src/arch/riscv/page_table.cpp create mode 100644 kernel/src/arch/riscv/plic.cpp create mode 100644 kernel/src/arch/riscv/sbi.cpp create mode 100644 kernel/src/arch/riscv/sched_pikeos.cpp create mode 100644 kernel/src/arch/riscv/timer.cpp create mode 100644 kernel/src/arch/riscv/trap.S create mode 100644 kernel/src/arch/riscv/uart.cpp create mode 100644 kernel/src/arch/riscv/vm.cpp rename kernel/{ => src/core}/kernel.cpp (100%) rename kernel/src/{ => core}/uos_api.cpp (100%) rename kernel/src/{ => core}/uos_drivers.cpp (100%) rename kernel/src/{ => core}/uos_syscalls.cpp (100%) rename kernel/src/{ => core}/usp.cpp (100%) rename kernel/src/{ => core}/usp_arm.cpp (100%) rename kernel/src/{ => core}/usp_board.cpp (100%) rename kernel/{ => src/platform}/drivers/block.cpp (100%) rename kernel/{ => src/platform}/drivers/block.h (100%) rename kernel/{ => src/platform}/drivers/driver.cpp (100%) rename kernel/{ => src/platform}/drivers/driver.h (100%) rename kernel/{ => src/platform}/drivers/font_8x16.cpp (100%) rename kernel/{ => src/platform}/drivers/gpio.cpp (100%) rename kernel/{ => src/platform}/drivers/gpio.h (100%) rename kernel/{ => src/platform}/drivers/i2c.cpp (100%) rename kernel/{ => src/platform}/drivers/i2c.h (100%) rename kernel/{ => src/platform}/drivers/network.cpp (100%) rename kernel/{ => src/platform}/drivers/network.h (100%) rename kernel/{ => src/platform}/drivers/pci.cpp (100%) rename kernel/{ => src/platform}/drivers/pci.h (100%) rename kernel/{ => src/platform}/drivers/spi.cpp (100%) rename kernel/{ => src/platform}/drivers/spi.h (100%) rename kernel/{ => src/platform}/drivers/timer.cpp (100%) rename kernel/{ => src/platform}/drivers/timer.h (100%) rename kernel/{ => src/platform}/drivers/uart.cpp (100%) rename kernel/{ => src/platform}/drivers/uart.h (100%) rename kernel/{ => src/platform}/drivers/uos_fbcon.cpp (100%) rename kernel/{ => src/platform}/drivers/uos_fbcon.h (100%) rename kernel/{ => src/platform}/drivers/usb.h (100%) rename kernel/{ => src/platform}/drivers/usb_core.cpp (100%) rename kernel/{ => src/platform}/drivers/usb_ehci.cpp (100%) diff --git a/kernel/arch/riscv/boot.S b/kernel/arch/riscv/boot.S deleted file mode 100644 index 412249be8..000000000 --- a/kernel/arch/riscv/boot.S +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Universalisos RISC-V Boot Code - * PikeOS 5.0 Feature Parity - RISC-V 64-bit Architecture Support - * - * This code handles: - * - RISC-V boot sequence for 64-bit mode - * - Exception vector table setup - * - Trap handling for RISC-V - * - Context saving and restoration - * - Platform initialization (QEMU virt machine) - */ - -.section .text.boot -.global _start - -_start: - /* - * RISC-V boot entry point - * QEMU loads the kernel at 0x80200000 and jumps here - */ - - /* Disable interrupts until kernel is ready */ - csrw mie, zero - - /* Set up stack pointer */ - lui sp, %hi(_stack_top) - addi sp, sp, %lo(_stack_top) - - /* Check hart ID - we only run on hart 0 */ - csrr a0, mhartid - bnez a0, halt_other_harts - -halt_other_harts: - /* Other harts wait here */ - wfi - j halt_other_harts - - /* Clear BSS section */ -main_hart: - lui a0, %hi(_bss_start) - addi a0, a0, %lo(_bss_start) - lui a1, %hi(_bss_end) - addi a1, a1, %lo(_bss_end) - -bss_loop: - bge a0, a1, bss_done - sw zero, 0(a0) - addi a0, a0, 4 - j bss_loop - -bss_done: - /* Set up trap vector (exception handler table) */ - la t0, trap_vector - csrw mtvec, t0 - - /* Configure trap vector to use direct mode */ - /* mtvec[1:0] = 0 for direct mode */ - - /* Enable machine-mode external interrupts */ - csrr t0, mie - ori t0, t0, 0x800 /* Enable MEIE (machine external interrupt enable) */ - csrw mie, t0 - - /* Set up machine stack pointer (MSP) */ - lui sp, %hi(_stack_top) - addi sp, sp, %lo(_stack_top) - - /* Jump to C kernel */ - call kernel_main - - /* Halt if kernel_main returns */ -halt: - wfi - j halt - -/* - * RISC-V Trap Vector Table - * All exceptions and interrupts go through mtvec - * In direct mode, all traps jump to this address - */ -.align 4 -.global trap_vector -trap_vector: - /* - * Save caller-saved registers - * RISC-V ABI: t0-t6, a0-a7 are caller-saved - */ - addi sp, sp, -128 - - /* Save general purpose registers */ - sd x1, 0(sp) /* ra (return address) */ - sd x5, 8(sp) /* t0 */ - sd x6, 16(sp) /* t1 */ - sd x7, 24(sp) /* t2 */ - sd x10, 32(sp) /* a0 */ - sd x11, 40(sp) /* a1 */ - sd x12, 48(sp) /* a2 */ - sd x13, 56(sp) /* a3 */ - sd x14, 64(sp) /* a4 */ - sd x15, 72(sp) /* a5 */ - sd x16, 80(sp) /* a6 */ - sd x17, 88(sp) /* a7 */ - sd x28, 96(sp) /* t3 */ - sd x29, 104(sp) /* t4 */ - sd x30, 112(sp) /* t5 */ - sd x31, 120(sp) /* t6 */ - - /* Read mcause to determine exception type */ - csrr a0, mcause - csrr a1, mtval /* Trap value (fault address or instruction) */ - - /* Read mepc (exception program counter) */ - csrr a2, mepc - - /* Read mstatus (machine status register) */ - csrr a3, mstatus - - /* Call C trap handler */ - call riscv_trap_handler - - /* Restore general purpose registers */ - ld x1, 0(sp) - ld x5, 8(sp) - ld x6, 16(sp) - ld x7, 24(sp) - ld x10, 32(sp) - ld x11, 40(sp) - ld x12, 48(sp) - ld x13, 56(sp) - ld x14, 64(sp) - ld x15, 72(sp) - ld x16, 80(sp) - ld x17, 88(sp) - ld x28, 96(sp) - ld x29, 104(sp) - ld x30, 112(sp) - ld x31, 120(sp) - - addi sp, sp, 128 - - /* Return from trap */ - mret - -/* - * Define stack regions - */ -.section .bss -.align 16 - -/* Main kernel stack */ -.global kernel_stack -kernel_stack: - .skip 16384 /* 16KB kernel stack */ -.global _stack_top -_stack_top: - -/* Exception stack */ -.global exception_stack -exception_stack: - .skip 8192 /* 8KB exception stack */ -.global exception_stack_top -exception_stack_top: - -/* User stack for kernel operations */ -.global user_stack -user_stack: - .skip 8192 /* 8KB user stack */ -.global user_stack_top -user_stack_top: - -/* - * External symbols for BSS clearing - */ -.global _bss_start -_bss_start: -.global _bss_end -_bss_end: \ No newline at end of file diff --git a/kernel/arch/arm/aeabi_runtime.cpp b/kernel/src/arch/armv7/aeabi_runtime.cpp similarity index 100% rename from kernel/arch/arm/aeabi_runtime.cpp rename to kernel/src/arch/armv7/aeabi_runtime.cpp diff --git a/kernel/arch/arm/boot.S b/kernel/src/arch/armv7/boot.S similarity index 89% rename from kernel/arch/arm/boot.S rename to kernel/src/arch/armv7/boot.S index cdaff36aa..4f817edbc 100644 --- a/kernel/arch/arm/boot.S +++ b/kernel/src/arch/armv7/boot.S @@ -56,6 +56,13 @@ reset_handler: /* Disable interrupts until kernel is ready */ cpsid if + /* Point VBAR at our vector table (start of .text). Without this the reset + * default VBAR=0 makes every exception vector to PA 0x0 (QEMU bootrom) and + * hang — SVC/data-abort/IRQ never reach the handlers below. */ + ldr r0, =exception_vectors + mcr p15, 0, r0, c12, c0, 0 + isb + /* Check processor mode and set up stacks for each mode */ /* System mode stack */ msr cpsr_c, #0xDF /* System mode */ @@ -166,8 +173,10 @@ svc_handler: add r1, sp, #0 /* Pass pointer to saved registers as second arg */ bl arm_svc_handler - /* Save return value in r5 */ - mov r5, r0 + /* Stash the return value in frame slot 17 (the saved-PC slot, which ldm + * r0-r14 does NOT restore). r5/r6 can't hold it: ldm restores them from the + * saved frame, which would clobber the result before mov r0,r5. */ + str r0, [sp, #17 * 4] /* Restore processor state */ ldr r6, [sp, #15 * 4] /* Restore CPSR */ @@ -175,11 +184,9 @@ svc_handler: ldmia sp, {r0-r14} /* Restore registers */ ldr lr, [sp, #16 * 4] /* Restore LR */ + ldr r0, [sp, #17 * 4] /* Return value (override the restored r0) */ add sp, sp, #18 * 4 - /* Return result in r0 */ - mov r0, r5 - /* Return from exception */ movs pc, lr @@ -290,6 +297,18 @@ irq_handler: /* Call C handler */ bl arm_irq_handler + /* Phase 2.1b: if the timer ISR requested a task switch, do the preemptive + * context switch (saves the preempted task, exception-returns into next). */ + ldr r5, =g_need_reschedule + ldr r5, [r5] + cmp r5, #0 + beq .Lirq_return + mov r6, #0 + ldr r7, =g_need_reschedule + str r6, [r7] @ clear flag + b uos_irq_reschedule @ never returns +.Lirq_return: + /* Restore processor state */ ldr r5, [sp, #15 * 4] msr spsr, r5 diff --git a/kernel/src/arch/armv7/config/cfg_boot_armv7.cpp b/kernel/src/arch/armv7/config/cfg_boot_armv7.cpp new file mode 100644 index 000000000..92731ddaa --- /dev/null +++ b/kernel/src/arch/armv7/config/cfg_boot_armv7.cpp @@ -0,0 +1,144 @@ +/* + * UniversalisOS partition materializer + guest runner — ARMv7/SVC backend. + * + * For each parsed partition: reset an adspace slot, set taskno=identifier + * (the ASID), register the user pgdir, allocate physical memory (respart) for + * each MemoryRequirement, and map it at a user VA; then proves isolation. + * Afterwards, mints capabilities and runs a trivial cap-checked guest in each + * partition's adspace. + * + * Reuses the exact uos_adspace sequence proven by uos_adspace_isolation_demo(). + */ +#include "cfg_boot_armv7.h" +#include "config/sysmodel.h" +#include "universalisos/respart.h" +#include "arch/arm/uos_adspace.h" +#include "arch/arm/uos_mmu_walk.h" +#include "uos_armmmu_v6.h" +#include "arch/arm/uart.h" +#include "universalisos/baremetal.h" +#include "cap/cap_compiler.h" +#include "abi/guest_abi.h" +#include "sched/sched_core.h" +#include "ipc/uos_ipc.h" + +/* Per-VM user pgdir pool re-init (clones kernel flat map into all 16 slots). */ +extern "C" int uos_arm_init_mmu(void); +/* The kernel flat-map pgdir (defined in mm.cpp). */ +extern "C" uint32_t* uos_get_kernel_pgdir(void); + +/* Partition memory pool: QEMU virt RAM, clear of the kernel image and heap. + * The isolation demo already proved 0x50000000/0x50100000 writable. */ +#define RESPART_BASE 0x50000000u +#define RESPART_SIZE 0x08000000u /* 128 MiB */ + +/* Per-partition runtime task contexts (ASID-isolated adspaces). */ +static uos_taskinfo_t g_part_task[UOS_CFG_MAX_PARTITIONS]; + +/* User VA where each partition's first MemoryRequirement is mapped. */ +#define PART_VA_BASE 0x1000u + +static uos_access_t to_uos_access(uint8_t a) { + uos_access_t x = 0; + if (a & UOS_CFG_ACC_RD) x |= UOS_M_READ; + if (a & UOS_CFG_ACC_WR) x |= UOS_M_WRITE; + if (a & UOS_CFG_ACC_EXEC) x |= UOS_M_EXEC; + return x; +} + +extern "C" int uos_cfg_materialize(void) { + using namespace universalisos::cfg; + const system_model* m = get_system(); + if (!m) return -1; + + /* Reset the pgdir pool to clean kernel clones (discards any prior demo use). */ + uos_arm_init_mmu(); + universalisos::respart_init(RESPART_BASE, RESPART_SIZE); + + uart_puts("\n=== UOS Materialize ===\n"); + for (uint32_t i = 0; i < m->n_partitions; i++) { + const partition* p = &m->partitions[i]; + if (p->identifier == 0 || p->identifier >= 16) { + uart_puts("UOS: skip "); uart_puts(p->name); uart_puts(" (id out of range)\n"); + continue; + } + uos_taskinfo_t* t = &g_part_task[i]; + universalisos::baremetal::memset(t, 0, sizeof(*t)); + uos_arch_adspace_init(t); + t->taskno = p->identifier; + uos_arch_adspace_register_user(t); + + uint32_t va = PART_VA_BASE; + for (uint32_t k = 0; k < p->n_memreqs; k++) { + const mem_region* mr = &p->memreqs[k]; + uint32_t phys = (mr->phys == UOS_CFG_AUTO) + ? universalisos::respart_alloc((uint32_t)mr->size, 0x1000u) + : (uint32_t)mr->phys; + if (phys == 0) { uart_puts("UOS: respart exhausted\n"); return -2; } + int rc = uos_arch_mem_create(t, va, (uint32_t)mr->size, phys, to_uos_access(mr->access)); + uart_puts("UOS: materialize "); uart_puts(p->name); + uart_puts(" asid="); uart_print_dec(p->identifier); + uart_puts(" va=0x"); uart_print_hex(va); + uart_puts(" pa=0x"); uart_print_hex(phys); + uart_puts(" rc="); uart_print_dec((uint32_t)rc); uart_puts("\n"); + va += (uint32_t)mr->size; + } + } + + /* Inter-partition isolation proof: same VA, distinct PA per partition. */ + if (m->n_partitions >= 2) { + uos_taskinfo_t idle; + universalisos::baremetal::memset(&idle, 0, sizeof(idle)); + uos_arch_adspace_set_active(&g_part_task[0], &idle); + *((volatile uint32_t*)PART_VA_BASE) = 0xDEADBEEFu; + uos_arch_adspace_set_active(&g_part_task[1], &g_part_task[0]); + *((volatile uint32_t*)PART_VA_BASE) = 0xCAFEBABEu; + uos_arch_adspace_set_active(&g_part_task[0], &g_part_task[1]); + uint32_t v0 = *((volatile uint32_t*)PART_VA_BASE); + uos_arch_adspace_set_active(&idle, &g_part_task[0]); + uart_puts("UOS: isolation guest0@va=0x"); uart_print_hex(PART_VA_BASE); + uart_puts(" = 0x"); uart_print_hex(v0); + uart_puts(v0 == 0xDEADBEEFu ? " PASSED\n" : " FAILED\n"); + } + + uart_puts("UOS: respart used 0x"); uart_print_hex(universalisos::respart_used()); + uart_puts(" bytes\n"); + uart_puts("=== End UOS Materialize ===\n\n"); + return 0; +} + +extern "C" uint32_t* uos_cfg_get_part_pgdir(uint32_t index) { + if (index >= UOS_CFG_MAX_PARTITIONS) return nullptr; + return g_part_task[index].adspace.pgdir; +} + +extern "C" int uos_cfg_run_guests(void) { + using namespace universalisos::cfg; + const system_model* m = get_system(); + if (!m) return -1; + + /* Mint the static capability set per partition, then run the cyclic + * scheduler: each partition is a task with its own stack + ASID adspace, + * switched via uos_ctx_switch, running its cap-checked guest until yield. */ + cap_compile_from_config(); + uos_sched_init(); + uos_sched_start(); + + /* Phase 2.2: resolve the P4 ConnectionTable into IPC sampling slots and run + * a cross-partition counter exchange (write/read/validity) while we are + * still in the cooperative boot phase (before the never-returning preempt + * demo takes over). */ + uint32_t n_ipc = uos_ipc_init(); + uart_puts("UOS: IPC resolved "); uart_print_dec(n_ipc); uart_puts(" sampling channels\n"); + if (n_ipc > 0) uos_ipc_demo(); + + uos_sched_preempt_demo(); /* Phase 2.1b-2: never returns (timer drives all switches) */ + + /* A true isolation *fault* isn't demonstrable for SVC-mode cooperative + * guests: the per-VM pgdir is a full 4 GB clone (every VA maps) and the + * guest runs privileged, so no access aborts. Real fault isolation arrives + * with user-mode partition tasks (Phase 2) and stage-2 MMU (Phase 4). Space + * isolation is already proven positively in uos_cfg_materialize() — the same + * VA resolves to a distinct PA per partition. */ + return 0; +} diff --git a/kernel/src/arch/armv7/config/cfg_boot_armv7.h b/kernel/src/arch/armv7/config/cfg_boot_armv7.h new file mode 100644 index 000000000..b08ff368b --- /dev/null +++ b/kernel/src/arch/armv7/config/cfg_boot_armv7.h @@ -0,0 +1,37 @@ +/* + * UniversalisOS partition materializer + guest runner — ARMv7/SVC backend. + * + * Turns the parsed system model into real, ASID-isolated address spaces by + * driving the proven uos_adspace engine (the same sequence as + * uos_adspace_isolation_demo), then runs a trivial cap-checked guest in each. + * This is the ARMv7/SVC backend; the AArch64/EL2 backend + * (arch/aarch64/config/cfg_boot_el2.*) will share the same signatures but + * program VTTBR_EL2 stage-2 instead. + */ +#ifndef CFG_BOOT_ARMV7_H +#define CFG_BOOT_ARMV7_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Materialise every parsed partition: assign an ASID-isolated address space, + * allocate physical memory for each MemoryRequirement(phys=ALLOC), and map it. + * Verifies inter-partition isolation. Returns 0 on success. */ +int uos_cfg_materialize(void); + +/* Mint per-partition capabilities, then cooperatively run the trivial guest in + * each partition's isolated adspace (cap-checked SVCs), and probe an isolation + * fault. Returns 0 on success. */ +int uos_cfg_run_guests(void); + +/* Return the materialised page-directory for parsed partition `index`, or NULL. */ +uint32_t* uos_cfg_get_part_pgdir(uint32_t index); + +#ifdef __cplusplus +} +#endif + +#endif /* CFG_BOOT_ARMV7_H */ diff --git a/kernel/arch/arm/context_switch.cpp b/kernel/src/arch/armv7/context_switch.cpp similarity index 100% rename from kernel/arch/arm/context_switch.cpp rename to kernel/src/arch/armv7/context_switch.cpp diff --git a/kernel/arch/arm/context_switch_asm.S b/kernel/src/arch/armv7/context_switch_asm.S similarity index 100% rename from kernel/arch/arm/context_switch_asm.S rename to kernel/src/arch/armv7/context_switch_asm.S diff --git a/kernel/arch/arm/exceptions.cpp b/kernel/src/arch/armv7/exceptions.cpp similarity index 94% rename from kernel/arch/arm/exceptions.cpp rename to kernel/src/arch/armv7/exceptions.cpp index ad6b48e16..1944a222e 100644 --- a/kernel/arch/arm/exceptions.cpp +++ b/kernel/src/arch/armv7/exceptions.cpp @@ -68,31 +68,13 @@ extern "C" void arm_undefined_instruction_handler(uint32_t instruction, uint32_t extern "C" uint32_t arm_svc_handler(uint32_t svc_number, uint32_t* args) { exception_stats.svc_count++; - uart_puts("\n>>> UOS SYSTEM CALL <<<\n"); - uart_puts("SVC Number: 0x"); - uart_print_hex(svc_number); - uart_puts("\n"); - - // Validate arguments pointer if (!args) { - uart_puts("Invalid arguments pointer\n"); + uart_puts("SVC: null args\n"); return UOS_SC_ERROR; } - // Dispatch to comprehensive system call implementation - uint32_t result = uos_syscall_dispatch(svc_number, args); - - // Check for errors - if (result == (uint32_t)UOS_SC_ERROR) { - uart_puts("System call returned error\n"); - exception_stats.svc_count++; // Count as error - } else { - uart_puts("System call completed: 0x"); - uart_print_hex(result); - uart_puts("\n"); - } - - return result; + /* Dispatch (capability-checked for the 0x90-0x9F guest ABI inside). */ + return uos_syscall_dispatch(svc_number, args); } /** @@ -194,7 +176,11 @@ extern "C" void arm_data_abort_handler(uint32_t fault_address, uint32_t fault_st * IRQ Handler * Normal interrupt handling - platform-specific interrupt controller */ +/* Debug: count entries to arm_irq_handler (read by the preempt self-test). */ +volatile uint32_t g_uos_irq_enters = 0; + extern "C" void arm_irq_handler(void) { + g_uos_irq_enters++; exception_stats.irq_count++; /* D-2: dispatch through the PikeOS-style interrupt table (uos_int_dispatch). diff --git a/kernel/arch/arm/context_switch.h b/kernel/src/arch/armv7/inc/context_switch.h similarity index 100% rename from kernel/arch/arm/context_switch.h rename to kernel/src/arch/armv7/inc/context_switch.h diff --git a/kernel/arch/arm/exceptions.h b/kernel/src/arch/armv7/inc/exceptions.h similarity index 100% rename from kernel/arch/arm/exceptions.h rename to kernel/src/arch/armv7/inc/exceptions.h diff --git a/kernel/arch/arm/uart.h b/kernel/src/arch/armv7/inc/uart.h similarity index 100% rename from kernel/arch/arm/uart.h rename to kernel/src/arch/armv7/inc/uart.h diff --git a/kernel/arch/arm/uos_adspace.h b/kernel/src/arch/armv7/inc/uos_adspace.h similarity index 100% rename from kernel/arch/arm/uos_adspace.h rename to kernel/src/arch/armv7/inc/uos_adspace.h diff --git a/kernel/arch/arm/uos_mmu_walk.h b/kernel/src/arch/armv7/inc/uos_mmu_walk.h similarity index 100% rename from kernel/arch/arm/uos_mmu_walk.h rename to kernel/src/arch/armv7/inc/uos_mmu_walk.h diff --git a/kernel/arch/arm/linker.ld b/kernel/src/arch/armv7/linker.ld similarity index 100% rename from kernel/arch/arm/linker.ld rename to kernel/src/arch/armv7/linker.ld diff --git a/kernel/src/arch/armv7/sched/ctx_switch.S b/kernel/src/arch/armv7/sched/ctx_switch.S new file mode 100644 index 000000000..daf4883ce --- /dev/null +++ b/kernel/src/arch/armv7/sched/ctx_switch.S @@ -0,0 +1,56 @@ +/* + * UniversalisOS ARMv7 context-switch primitives (Phase 2). + * + * uos_ctx_switch: a standard coroutine switch. Since the kernel and all + * partition tasks run in SVC mode, switching is just saving the callee-saved + * registers (r4-r11) plus sp/lr of the outgoing context and loading those of + * the incoming one, then "returning" (bx lr) into it. r0-r3/r12 are + * caller-saved — the compiler preserves them at call sites, and on a + * switch round-trip they are saved/restored with the rest of the frame. + * + * uos_sched_start: run uos_sched_loop() on a private scheduler stack so that a + * task which yields from inside the SVC handler does not clobber its own + * handler frames (which live on the task's SVC stack). + */ + .syntax unified + .arch armv7-a + +/* + * void uos_ctx_switch(uos_tcb_t* from [r0], uos_tcb_t* to [r1]) + * TCB layout: [r4,r5,r6,r7,r8,r9,r10,r11,sp,lr] (10 words). + */ + .global uos_ctx_switch + .type uos_ctx_switch, %function +uos_ctx_switch: + stmia r0!, {r4-r11} @ save r4-r11 into from->[0..7] + str sp, [r0], #4 @ save sp into from->[8] + str lr, [r0], #4 @ save lr into from->[9] + ldmia r1!, {r4-r11} @ load r4-r11 from to->[0..7] + ldr sp, [r1], #4 @ load sp from to->[8] + ldr lr, [r1], #4 @ load lr from to->[9] + bx lr @ "return" into the incoming context + .size uos_ctx_switch, .-uos_ctx_switch + +/* + * void uos_sched_start(void) + * Saves the caller (SVC) sp/lr into globals, switches to the scheduler stack, + * calls uos_sched_loop(), then restores the SVC sp/lr and returns. r1 is + * scratch (re-loaded each use); r4-r11 are clobbered by the loop's switches, + * so the saved sp/lr are kept in memory, not registers. + */ + .global uos_sched_start + .type uos_sched_start, %function +uos_sched_start: + ldr r1, =g_saved_svc_lr + str lr, [r1] + ldr r1, =g_saved_svc_sp + str sp, [r1] + ldr r1, =g_sched_stack_top + ldr sp, [r1] @ switch to private scheduler stack + bl uos_sched_loop @ loop manages its own frame on this stack + ldr r1, =g_saved_svc_sp + ldr sp, [r1] @ restore SVC stack + ldr r1, =g_saved_svc_lr + ldr lr, [r1] + bx lr + .size uos_sched_start, .-uos_sched_start diff --git a/kernel/src/arch/armv7/sched/preempt_switch.S b/kernel/src/arch/armv7/sched/preempt_switch.S new file mode 100644 index 000000000..b6dc627fd --- /dev/null +++ b/kernel/src/arch/armv7/sched/preempt_switch.S @@ -0,0 +1,139 @@ +/* + * UniversalisOS ARMv7 preemptive context switch (Phase 2.1b-2). + * + * uos_preempt_run(pctx): start a task — load its full context and exception- + * return into it. Called from SVC mode; never returns. [pctx in r0] + * + * uos_irq_reschedule: reached by `b` from boot.S irq_handler when the timer ISR + * set g_need_reschedule. Enter in IRQ mode with sp_irq pointing at the boot.S + * IRQ frame: + * [0..12]=r0-r12, [13]=lr_irq(A.pc+4), [14]=gap, [15]=spsr(A.cpsr), + * [16]=lr_irq, [17]=PC(A.pc). + * Saves the preempted task's full context to *g_preempt_cur_tcb (r0-r12 from + * the frame; banked sp_svc/lr_svc via a brief SVC-mode switch; pc/cpsr from + * the frame) and exception-returns into *g_preempt_next_tcb. + * + * uos_pctx_t layout (must match sched_core.h): r[13] @0..48, sp@52, lr@56, + * pc@60, cpsr@64. + * + * The central subtlety in both routines: `ldm {r0-r12}` clobbers r0-r12, so the + * pctx base pointer and the exception-return pc/cpsr must be carried in banked + * registers (sp/lr/spsr of the relevant mode), NOT in r0-r12. + */ + .syntax unified + .arch armv7-a + +#define PR0 0 +#define PSP 52 +#define PLR 56 +#define PPC 60 +#define PCPSR 64 + +/* + * void uos_preempt_run(uos_pctx_t* pctx) [pctx in r0] + * Strategy: set sp_svc, lr_irq, spsr_irq from r5/r6/r7 BEFORE the ldm clobbers + * r0-r12; then load r0-r12 via sp_irq (banked, scratch here). lr_irq (pc+4) and + * spsr_irq (cpsr) survive the ldm untouched. + */ + .global uos_preempt_run + .type uos_preempt_run, %function +uos_preempt_run: + ldr r5, [r0, #PPC] @ r5 = pc + ldr r6, [r0, #PCPSR] @ r6 = cpsr + ldr r7, [r0, #PSP] @ r7 = sp + add r5, r5, #4 @ r5 = pc + 4 (the lr_irq return value) + + @ SVC stack for the task (sp_svc is banked; survives all mode switches). + msr cpsr_c, #0xD3 @ SVC mode, IRQ/FIQ off for setup + mov sp, r7 @ sp_svc = task sp + + @ Exception-return setup in IRQ mode while r5/r6 still hold pc+4/cpsr. + msr cpsr_c, #0xD2 @ IRQ mode + mov lr, r5 @ lr_irq = pc + 4 + msr spsr, r6 @ spsr_irq = task cpsr + + @ Load r0-r12 from the frame. Use sp_irq as the ldm base: it is banked and + @ scratch (we no longer need the boot.S IRQ frame here). pctx->r[0] is moved + @ into sp_irq while still in a mode where r0 is intact. + add r0, r0, #PR0 @ r0 = &pctx->r[0] + mov sp, r0 @ sp_irq = &pctx->r[0] (sp is sp_irq in IRQ mode) + ldmia sp!, {r0-r12} @ r0-r12 = task regs (sp_irq now &pctx->r[13]) + + @ Reset sp_irq to the canonical IRQ stack so the first timer IRQ frame lands + @ in the right place (boot.S does sub sp,sp,#18*4 + stmia on entry). + ldr sp, =_irq_stack_top + + subs pc, lr, #4 @ exception-return into the task (SVC mode) + .size uos_preempt_run, .-uos_preempt_run + +/* + * void uos_irq_reschedule(void) - reached by `b` from boot.S; never returns. + * Enter: IRQ mode, sp_irq = frame base, frame holds preempted task's state. + */ + .global uos_irq_reschedule + .type uos_irq_reschedule, %function +uos_irq_reschedule: + @ Read the preempted task's banked sp_svc / lr_svc via a brief SVC switch. + @ r4 holds the frame pointer; r4 is in {r0-r12} and gets clobbered by the + @ final ldm, so we must do all frame reads BEFORE that ldm. + mov r4, sp @ r4 = frame ptr (sp_irq) + msr cpsr_c, #0xD3 @ SVC mode, IRQ/FIQ off + mov r5, sp @ r5 = sp_svc (preempted task's sp) + mov r6, lr @ r6 = lr_svc (preempted task's lr) + msr cpsr_c, #0xD2 @ back to IRQ mode (sp_irq = frame, in r4) + + @ Save the preempted task to *g_preempt_cur_tcb (all via r0-r3 only). + ldr r0, =g_preempt_cur_tcb + ldr r0, [r0] @ r0 = &cur_pctx + str r5, [r0, #PSP] @ cur.sp = sp_svc + str r6, [r0, #PLR] @ cur.lr = lr_svc + ldr r1, [r4, #(17*4)] @ frame[17] = preempted PC + str r1, [r0, #PPC] + ldr r1, [r4, #(15*4)] @ frame[15] = preempted cpsr (spsr) + str r1, [r0, #PCPSR] + @ Copy frame[0..12] (r0-r12) -> cur_pctx.r[0..12]. + add r3, r0, #PR0 @ r3 = dst (&cur_pctx.r[0]) + mov r2, r4 @ r2 = src (frame) + mov r1, #13 +1: ldr r0, [r2], #4 + str r0, [r3], #4 + subs r1, r1, #1 + bne 1b + + @ Load *g_preempt_next_tcb's pc/cpsr/sp/lr into surviving/banked regs. + ldr r0, =g_preempt_next_tcb + ldr r0, [r0] @ r0 = &next_pctx + ldr r5, [r0, #PPC] @ r5 = next.pc + ldr r6, [r0, #PCPSR] @ r6 = next.cpsr + ldr r7, [r0, #PSP] @ r7 = next.sp + add r5, r5, #4 @ r5 = next.pc + 4 + + @ SVC stack + lr for the next task. We must set BOTH sp_svc and lr_svc + @ (lr_svc is banked; the task resumes and immediately uses it on call/ret). + msr cpsr_c, #0xD3 @ SVC mode + mov sp, r7 @ sp_svc = next.sp + ldr r7, [r0, #PLR] @ r7 = next.lr (reload; r0 still = &next_pctx) + mov lr, r7 @ lr_svc = next.lr + + @ Exception-return setup in IRQ mode while r5/r6 hold pc+4/cpsr. + msr cpsr_c, #0xD2 @ IRQ mode (sp_irq = frame base, in r4) + mov lr, r5 @ lr_irq = next.pc + 4 + msr spsr, r6 @ spsr_irq = next.cpsr + + @ Free the boot.S IRQ frame now (r4 still holds its base; the final ldm + @ below would clobber r4). sp_irq is scratch from here on. + add sp, r4, #(18*4) @ sp_irq just past the freed frame + + @ Load r0-r12 from next_pctx->r[0] via sp_irq (scratch base). + ldr r0, =g_preempt_next_tcb + ldr r0, [r0] @ r0 = &next_pctx + add r0, r0, #PR0 @ r0 = &next_pctx->r[0] + mov sp, r0 @ sp_irq = &next_pctx->r[0] + ldmia sp!, {r0-r12} @ r0-r12 = next regs + + @ Reset sp_irq to the canonical IRQ stack so the NEXT IRQ's frame lands in + @ the right place (boot.S does sub sp,sp,#18*4 + stmia on entry). + ldr sp, =_irq_stack_top + + subs pc, lr, #4 @ exception-return into next task + .size uos_irq_reschedule, .-uos_irq_reschedule diff --git a/kernel/arch/arm/string.S b/kernel/src/arch/armv7/string.S similarity index 100% rename from kernel/arch/arm/string.S rename to kernel/src/arch/armv7/string.S diff --git a/kernel/arch/arm/uart.cpp b/kernel/src/arch/armv7/uart.cpp similarity index 100% rename from kernel/arch/arm/uart.cpp rename to kernel/src/arch/armv7/uart.cpp diff --git a/kernel/arch/arm/uos_adspace.cpp b/kernel/src/arch/armv7/uos_adspace.cpp similarity index 100% rename from kernel/arch/arm/uos_adspace.cpp rename to kernel/src/arch/armv7/uos_adspace.cpp diff --git a/kernel/arch/arm/uos_cmm.cpp b/kernel/src/arch/armv7/uos_cmm.cpp similarity index 100% rename from kernel/arch/arm/uos_cmm.cpp rename to kernel/src/arch/armv7/uos_cmm.cpp diff --git a/kernel/arch/arm/uos_mmu_walk.cpp b/kernel/src/arch/armv7/uos_mmu_walk.cpp similarity index 100% rename from kernel/arch/arm/uos_mmu_walk.cpp rename to kernel/src/arch/armv7/uos_mmu_walk.cpp diff --git a/kernel/src/arch/riscv/boot.S b/kernel/src/arch/riscv/boot.S new file mode 100644 index 000000000..fa57a5a93 --- /dev/null +++ b/kernel/src/arch/riscv/boot.S @@ -0,0 +1,167 @@ +/* + * UniversalisOS RISC-V 64-bit Hypervisor Boot + * Targets QEMU virt with OpenSBI firmware. + * + * OpenSBI loads the kernel at 0x80200000 in S-mode/HS-mode and passes: + * a0 = hart id + * a1 = device tree physical address + * + * We are already in HS-mode if the H-extension is present. The boot + * code sets up an identity-mapped Sv39 page table, installs the HS-mode + * trap vector, initialises hypervisor CSRs, and calls kernel_main(). + * + * Reference: Bao src/arch/riscv/boot.S, seL4 src/arch/riscv/head.S + */ + +/* PTE flags for boot page tables */ +#define PTE_V (1 << 0) +#define PTE_R (1 << 1) +#define PTE_W (1 << 2) +#define PTE_X (1 << 3) +#define PTE_A (1 << 6) +#define PTE_D (1 << 7) + +#define SATP_MODE_SV39 8 +#define SATP_MODE_SHIFT 60 + +#define HSTATUS_VSXL_64 (2ULL << 32) + +.section .text.boot +.global _start + +_start: + /* Disable all interrupts */ + csrw sie, zero + csrw sip, zero + + /* Save arguments passed by OpenSBI */ + mv s0, a0 /* s0 = hart id */ + mv s1, a1 /* s1 = dtb pa */ + +#ifdef PLATFORM_POLARFIRE + /* On PolarFire SoC: + * Hart 0: E51 monitor core (non-MMU, parked) + * Harts 1-4: U54 application cores + * Hart 1 acts as boot coordinator. Harts 0 and 2-4 park/wait. */ + li t0, 1 + bne a0, t0, .Lpark +#else + /* On QEMU/Default: Hart 0 acts as boot coordinator */ + bnez a0, .Lpark +#endif + + /* Set stack pointer per Hart. + * Stack size is 32KB (32768 bytes) per context. */ + la sp, _stack_top + li t0, 32768 + mul t1, s0, t0 + sub sp, sp, t1 + + /* Clear BSS */ + la a0, _bss_start + la a1, _bss_end +.Lbss_loop: + bgeu a0, a1, .Lbss_done + sd zero, 0(a0) + addi a0, a0, 8 + j .Lbss_loop +.Lbss_done: + + /* Build bootstrap page tables */ + call riscv_boot_pt_init + + /* Set HS-mode trap vector */ + la t0, riscv_trap_vector + csrw stvec, t0 + + /* Initialise hypervisor CSRs. We are already in S/HS-mode. */ + li t0, HSTATUS_VSXL_64 + csrw hstatus, t0 + + /* Delegate virtual exceptions/interrupts to VS-mode */ + li t0, 0xF2FF /* all sync exceptions + v interrupts */ + csrw hedeleg, t0 + li t0, (1 << 2) | (1 << 6) | (1 << 10) + csrw hideleg, t0 + + /* Virtual interrupt pending clear */ + csrw hvip, zero + + /* Enable external, timer and software interrupts in S-mode */ + li t0, (1 << 9) | (1 << 5) | (1 << 1) + csrw sie, t0 + + /* Jump to C kernel entry */ + mv a0, s0 + mv a1, s1 + call kernel_main + +.Lhalt: + wfi + j .Lhalt + +.Lpark: + /* Secondary harts park and wait for SBI HSM start */ + wfi + j .Lpark + +/* + * Bootstrap page-table setup. + * Builds a 1-level (giant-page) Sv39 identity map covering low physical + * memory. Page tables are placed in .bss (_boot_l1_pt). + */ +.global riscv_boot_pt_init +riscv_boot_pt_init: + la t0, _boot_l1_pt + li t1, 4096 + add t1, t0, t1 +.Lptzero: + bgeu t0, t1, .Lptzero_done + sd zero, 0(t0) + addi t0, t0, 8 + j .Lptzero +.Lptzero_done: + + la t0, _boot_l1_pt + + /* Entry 0: 0x00000000-0x3FFFFFFF (device/MMIO) */ + li t1, (PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D) + sd t1, 0(t0) + + /* Entry 1: 0x40000000-0x7FFFFFFF */ + li t1, (0x40000000 >> 12) + slli t1, t1, 10 + li t2, (PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D) + or t1, t1, t2 + sd t1, 8(t0) + + /* Entry 2: 0x80000000-0xBFFFFFFF (RAM) */ + li t1, (0x80000000 >> 12) + slli t1, t1, 10 + li t2, (PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D) + or t1, t1, t2 + sd t1, 16(t0) + + /* Load SATP */ + la t0, _boot_l1_pt + srli t0, t0, 12 + li t1, (SATP_MODE_SV39 << SATP_MODE_SHIFT) + or t0, t0, t1 + csrw satp, t0 + + sfence.vma zero, zero + ret + +.section .bss +.align 4096 +.global _boot_l1_pt +_boot_l1_pt: + .skip 4096 + +/* Main hypervisor stack */ +.align 16 +.global _stack_bottom +_stack_bottom: + .skip 32768 +.global _stack_top +_stack_top: diff --git a/kernel/src/arch/riscv/context_switch.cpp b/kernel/src/arch/riscv/context_switch.cpp new file mode 100644 index 000000000..165cdd27e --- /dev/null +++ b/kernel/src/arch/riscv/context_switch.cpp @@ -0,0 +1,27 @@ +/* + * UniversalisOS RISC-V Context Switch (high-level) + */ + +#include "context_switch.h" +#include "universalisos/baremetal.h" + +extern "C" { + +void riscv_context_init(riscv_context_t* ctx, void* entry_point, void* stack_top) { + if (!ctx) return; + universalisos::baremetal::memset(ctx, 0, sizeof(*ctx)); + ctx->gpr[1] = (uint64_t)entry_point; /* ra */ + ctx->gpr[2] = (uint64_t)stack_top; /* sp */ + ctx->pc = (uint64_t)entry_point; + /* SPP=1 so sret enters S-mode, SPIE=1 so interrupts are enabled */ + ctx->status = (1ULL << 8) | (1ULL << 5); +} + +/* The real switch is in context_switch.S; this symbol satisfies links + * when the assembly version is not selected. */ +__attribute__((weak)) void riscv_context_switch(riscv_context_t* next, riscv_context_t* prev) { + (void)next; + (void)prev; +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/context_switch_asm.S b/kernel/src/arch/riscv/context_switch_asm.S new file mode 100644 index 000000000..bdb1fb16a --- /dev/null +++ b/kernel/src/arch/riscv/context_switch_asm.S @@ -0,0 +1,168 @@ +/* + * UniversalisOS RISC-V Context Switch + * + * riscv_vcpu_entry(regs): + * - Loads guest register state from the trap frame + * - Sets hstatus.SPV so sret enters VS-mode + * - Executes sret into the guest + * + * riscv_context_switch(next, prev): + * - Saves current hypervisor task context to prev + * - Restores next hypervisor task context + * - Uses sret to resume next task in HS-mode + * + * Reference: Bao src/arch/riscv/exceptions.S, seL4 src/arch/riscv/traps.S + */ + +.equ HSTATUS_SPV, (1 << 7) + +.section .text +.global riscv_vcpu_entry +.global riscv_context_switch + +/* + * vCPU entry: enter a guest in VS-mode. + * a0 = pointer to trap frame (vcpu->regs) + */ +riscv_vcpu_entry: + csrw sscratch, a0 + + ld x1, 0(a0) + ld x2, 8(a0) + ld x3, 16(a0) + ld x4, 24(a0) + ld x5, 32(a0) + ld x6, 40(a0) + ld x7, 48(a0) + ld x8, 56(a0) + ld x9, 64(a0) + ld x10, 72(a0) + ld x11, 80(a0) + ld x12, 88(a0) + ld x13, 96(a0) + ld x14, 104(a0) + ld x15, 112(a0) + ld x16, 120(a0) + ld x17, 128(a0) + ld x18, 136(a0) + ld x19, 144(a0) + ld x20, 152(a0) + ld x21, 160(a0) + ld x22, 168(a0) + ld x23, 176(a0) + ld x24, 184(a0) + ld x25, 192(a0) + ld x26, 200(a0) + ld x27, 208(a0) + ld x28, 216(a0) + ld x29, 224(a0) + ld x30, 232(a0) + ld x31, 240(a0) + + ld t0, 248(a0) + csrw sepc, t0 + ld t0, 256(a0) + csrw sstatus, t0 + + csrr t0, hstatus + li t1, HSTATUS_SPV + or t0, t0, t1 + csrw hstatus, t0 + + sret + +/* + * Cooperative context switch between hypervisor tasks. + * a0 = next context (riscv_context_t*) + * a1 = prev context (riscv_context_t*) + */ +riscv_context_switch: + /* Save current GPRs to prev */ + sd x1, 8(a1) + sd x2, 16(a1) + sd x3, 24(a1) + sd x4, 32(a1) + sd x5, 40(a1) + sd x6, 48(a1) + sd x7, 56(a1) + sd x8, 64(a1) + sd x9, 72(a1) + sd x10, 80(a1) + sd x11, 88(a1) + sd x12, 96(a1) + sd x13, 104(a1) + sd x14, 112(a1) + sd x15, 120(a1) + sd x16, 128(a1) + sd x17, 136(a1) + sd x18, 144(a1) + sd x19, 152(a1) + sd x20, 160(a1) + sd x21, 168(a1) + sd x22, 176(a1) + sd x23, 184(a1) + sd x24, 192(a1) + sd x25, 200(a1) + sd x26, 208(a1) + sd x27, 216(a1) + sd x28, 224(a1) + sd x29, 232(a1) + sd x30, 240(a1) + sd x31, 248(a1) + + /* x0 is always zero; store 0 for completeness */ + sd x0, 0(a1) + + /* Save return address as the new PC for prev */ + sd ra, 256(a1) + + /* Save sstatus */ + csrr t0, sstatus + sd t0, 264(a1) + + /* Clear hstatus.SPV: we stay in HS-mode */ + csrr t0, hstatus + li t1, HSTATUS_SPV + not t1, t1 + and t0, t0, t1 + csrw hstatus, t0 + + /* Restore next GPRs */ + ld x1, 8(a0) + ld x2, 16(a0) + ld x3, 24(a0) + ld x4, 32(a0) + ld x5, 40(a0) + ld x6, 48(a0) + ld x7, 56(a0) + ld x8, 64(a0) + ld x9, 72(a0) + ld x10, 80(a0) + ld x11, 88(a0) + ld x12, 96(a0) + ld x13, 104(a0) + ld x14, 112(a0) + ld x15, 120(a0) + ld x16, 128(a0) + ld x17, 136(a0) + ld x18, 144(a0) + ld x19, 152(a0) + ld x20, 160(a0) + ld x21, 168(a0) + ld x22, 176(a0) + ld x23, 184(a0) + ld x24, 192(a0) + ld x25, 200(a0) + ld x26, 208(a0) + ld x27, 216(a0) + ld x28, 224(a0) + ld x29, 232(a0) + ld x30, 240(a0) + ld x31, 248(a0) + + /* Restore sstatus and sepc, then sret into next task */ + ld t0, 264(a0) + csrw sstatus, t0 + ld t0, 256(a0) + csrw sepc, t0 + sret diff --git a/kernel/src/arch/riscv/cpu.cpp b/kernel/src/arch/riscv/cpu.cpp new file mode 100644 index 000000000..e1f1145e6 --- /dev/null +++ b/kernel/src/arch/riscv/cpu.cpp @@ -0,0 +1,56 @@ +/* + * UniversalisOS RISC-V CPU Initialisation + */ + +#include "cpu.h" +#include "csr.h" +#include "uart.h" +#include "universalisos/baremetal.h" + +riscv_cpu_info_t riscv_cpu_info[RISCV_HART_MAX]; + +extern "C" { + +void riscv_cpu_init(uint32_t hart_id) { + if (hart_id >= RISCV_HART_MAX) return; + + riscv_cpu_info_t* cpu = &riscv_cpu_info[hart_id]; + universalisos::baremetal::memset(cpu, 0, sizeof(*cpu)); + cpu->hart_id = hart_id; + cpu->online = true; + + /* We are running as an S-mode/HS-mode payload loaded by OpenSBI. + * MISA is not readable from S-mode; ISA features are discovered from + * the device tree or assumed from the platform. The H-extension is + * mandatory for this hypervisor. */ + cpu->h_extension = true; + + /* Probe Svpbmt and Sstc via henvcfg if accessible. Reading henvcfg + * itself is allowed in HS-mode. */ + uint64_t henvcfg = csr_read(CSR_HENVCFG); + cpu->sstc = (henvcfg & (1ULL << 63)) != 0; /* STCE */ + cpu->svpbmt = (henvcfg & (1ULL << 62)) != 0; /* PBMTE */ +} + +bool riscv_cpu_has_h_extension(void) { + return riscv_cpu_info[0].h_extension; +} + +void riscv_cpu_print_info(void) { + universalisos::uart::puts("=== RISC-V CPU Info ===\r\n"); + for (int i = 0; i < RISCV_HART_MAX; ++i) { + if (!riscv_cpu_info[i].online) continue; + universalisos::uart::puts("Hart "); + universalisos::uart::print_dec(riscv_cpu_info[i].hart_id); + universalisos::uart::puts(" H-ext="); + universalisos::uart::puts(riscv_cpu_info[i].h_extension ? "yes" : "no"); + universalisos::uart::puts(" Sstc="); + universalisos::uart::puts(riscv_cpu_info[i].sstc ? "yes" : "no"); + universalisos::uart::puts(" Svpbmt="); + universalisos::uart::puts(riscv_cpu_info[i].svpbmt ? "yes" : "no"); + universalisos::uart::puts("\r\n"); + } + universalisos::uart::puts("=======================\r\n"); +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/exceptions.cpp b/kernel/src/arch/riscv/exceptions.cpp new file mode 100644 index 000000000..be363a806 --- /dev/null +++ b/kernel/src/arch/riscv/exceptions.cpp @@ -0,0 +1,225 @@ +/* + * UniversalisOS RISC-V HS-mode Trap Handler + * + * Dispatches synchronous exceptions and interrupts that occur while the + * hypervisor is running in HS-mode, or that have been delegated from a + * guest in VS-mode. + */ + +#include "exceptions.h" +#include "uart.h" +#include "csr.h" +#include "page_table.h" +#include "vm.h" +#include "timer.h" +#include "plic.h" +#include "sbi.h" +#include "sched_pikeos.h" +#include "hm.h" +#include +#include "universalisos/baremetal.h" + +static riscv_exception_stats_t stats = {}; + +extern "C" { + +void riscv_exceptions_init() { + stats = {}; +} + +void riscv_get_exception_stats(riscv_exception_stats_t* out_stats) { + if (out_stats) { + universalisos::baremetal::memcpy(out_stats, &stats, sizeof(riscv_exception_stats_t)); + } +} + +void riscv_print_exception_stats() { + universalisos::uart::puts("=== RISC-V Exception Statistics ===\r\n"); + universalisos::uart::puts("Illegal Instructions: "); + universalisos::uart::print_dec(stats.illegal_instruction_count); + universalisos::uart::puts("\r\nEnvironment Calls: "); + universalisos::uart::print_dec(stats.environment_call_count); + universalisos::uart::puts("\r\nPage Faults (I/L/S): "); + universalisos::uart::print_dec(stats.instruction_page_fault_count); + universalisos::uart::puts("/"); + universalisos::uart::print_dec(stats.load_page_fault_count); + universalisos::uart::puts("/"); + universalisos::uart::print_dec(stats.store_page_fault_count); + universalisos::uart::puts("\r\nGuest PF / Virt Inst: "); + universalisos::uart::print_dec(stats.guest_page_fault_count); + universalisos::uart::puts("/"); + universalisos::uart::print_dec(stats.virtual_instruction_count); + universalisos::uart::puts("\r\nInterrupts (timer/ext): "); + universalisos::uart::print_dec(stats.timer_interrupt_count); + universalisos::uart::puts("/"); + universalisos::uart::print_dec(stats.external_interrupt_count); + universalisos::uart::puts("\r\n===================================\r\n"); +} + +extern "C" __attribute__((weak)) uint32_t uos_syscall_dispatch(uint32_t svc_number, uint32_t* args) { + (void)svc_number; (void)args; + return 0; +} + +static void handle_guest_ecall(riscv_trap_frame_t* frame) { + /* Guest SBI / hypercall proxy. Bao uses SBI extension 0x08000ba0 + * for bao-specific hypercalls; standard SBI calls are forwarded. */ + uint64_t arg0 = frame->a0; + uint64_t arg1 = frame->a1; + uint64_t arg2 = frame->a2; + uint64_t arg3 = frame->a3; + uint64_t arg4 = frame->a4; + uint64_t arg5 = frame->a5; + uint64_t fid = frame->a6; + uint64_t eid = frame->a7; + + if (eid == 0x08000ba0) { + /* Bao-style hypercall: fid selects operation */ + switch (fid) { + case 0: /* yield */ + frame->a0 = 0; + frame->sepc += 4; + return; + default: + frame->a0 = (uint64_t)-1; + frame->sepc += 4; + return; + } + } + + /* Standard SBI: forward to M-mode firmware */ + struct sbiret ret = sbi_ecall(eid, fid, arg0, arg1, arg2, arg3, arg4, arg5); + frame->a0 = ret.error; + frame->a1 = ret.value; + frame->sepc += 4; +} + +static void handle_hypervisor_ecall(riscv_trap_frame_t* frame) { + /* UOS system call from a task running in HS-mode (not guest). + * Syscall number in a7, arguments in a0-a5. */ + uint32_t args[8] = { + (uint32_t)frame->a0, (uint32_t)frame->a1, (uint32_t)frame->a2, + (uint32_t)frame->a3, (uint32_t)frame->a4, (uint32_t)frame->a5, + (uint32_t)frame->a6, 0 + }; + uint32_t result = uos_syscall_dispatch((uint32_t)frame->a7, args); + frame->a0 = result; + frame->sepc += 4; +} + +void riscv_trap_handler_c(uint64_t scause, uint64_t stval, uint64_t sepc, + uint64_t sstatus, riscv_trap_frame_t* frame) { + (void)sepc; + (void)sstatus; + + const uint64_t INTERRUPT_BIT = (1ULL << 63); + bool is_interrupt = (scause & INTERRUPT_BIT) != 0; + uint64_t code = scause & ~INTERRUPT_BIT; + + if (is_interrupt) { + stats.interrupt_count++; + switch (code) { + case IRQ_S_TIMER: + stats.timer_interrupt_count++; + riscv_timer_clear(); + riscv_sched_tick(riscv_timer_get_time()); + break; + case IRQ_S_EXT: + stats.external_interrupt_count++; + riscv_plic_handle_interrupt(); + break; + case IRQ_S_SOFT: + /* IPI / software interrupt */ + csr_clear(CSR_SIP, (1ULL << IRQ_S_SOFT)); + break; + default: + break; + } + return; + } + + /* Determine if the trap came from a guest (VS-mode) */ + bool from_guest = (frame->hstatus & HSTATUS_SPV) != 0; + + switch (code) { + case CAUSE_ILLEGAL_INSTRUCTION: + stats.illegal_instruction_count++; + riscv_hm_event(RISCV_HM_EVENT_ILLEGAL_INSTRUCTION, + RISCV_HM_LEVEL_PARTITION_STOP, + 0, 0, frame->sepc, 0, "illegal instruction"); + break; + + case CAUSE_USER_ECALL: + case CAUSE_SUPERVISOR_ECALL: + stats.environment_call_count++; + if (from_guest) { + handle_guest_ecall(frame); + } else { + handle_hypervisor_ecall(frame); + } + break; + + case CAUSE_FETCH_PAGE_FAULT: + stats.instruction_page_fault_count++; + if (from_guest) { + stats.guest_page_fault_count++; + riscv_vm_handle_guest_page_fault(frame, stval, true); + } else { + riscv_hm_event(RISCV_HM_EVENT_PAGE_FAULT, + RISCV_HM_LEVEL_PARTITION_STOP, + 0, 0, frame->sepc, stval, "fetch page fault"); + } + break; + case CAUSE_LOAD_PAGE_FAULT: + stats.load_page_fault_count++; + if (from_guest) { + stats.guest_page_fault_count++; + riscv_vm_handle_guest_page_fault(frame, stval, false); + } else { + riscv_hm_event(RISCV_HM_EVENT_PAGE_FAULT, + RISCV_HM_LEVEL_PARTITION_STOP, + 0, 0, frame->sepc, stval, "load page fault"); + } + break; + case CAUSE_STORE_PAGE_FAULT: + stats.store_page_fault_count++; + if (from_guest) { + stats.guest_page_fault_count++; + riscv_vm_handle_guest_page_fault(frame, stval, false); + } else { + riscv_hm_event(RISCV_HM_EVENT_PAGE_FAULT, + RISCV_HM_LEVEL_PARTITION_STOP, + 0, 0, frame->sepc, stval, "store page fault"); + } + break; + + case CAUSE_FETCH_GUEST_PAGE_FAULT: + case CAUSE_LOAD_GUEST_PAGE_FAULT: + case CAUSE_STORE_GUEST_PAGE_FAULT: + stats.guest_page_fault_count++; + riscv_vm_handle_guest_page_fault(frame, stval, code == CAUSE_FETCH_GUEST_PAGE_FAULT); + break; + + case CAUSE_VIRTUAL_INSTRUCTION: + stats.virtual_instruction_count++; + riscv_vm_handle_virtual_instruction(frame); + break; + + case CAUSE_BREAKPOINT: + stats.breakpoint_count++; + frame->sepc += 2; /* c.ebreak = 2 bytes, ebreak = 4 bytes */ + break; + + default: + universalisos::uart::puts("TRAP: sync exception code "); + universalisos::uart::print_dec((uint32_t)code); + universalisos::uart::puts(" at PC: 0x"); + universalisos::uart::print_hex((uint32_t)frame->sepc); + universalisos::uart::puts(" val: 0x"); + universalisos::uart::print_hex((uint32_t)stval); + universalisos::uart::puts("\r\n"); + break; + } +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/guest_boot.cpp b/kernel/src/arch/riscv/guest_boot.cpp new file mode 100644 index 000000000..d338f6b0a --- /dev/null +++ b/kernel/src/arch/riscv/guest_boot.cpp @@ -0,0 +1,98 @@ +/* + * UniversalisOS RISC-V Guest OS Boot Loader Implementation + */ + +#include "guest_boot.h" +#include "vm.h" +#include "page_table.h" +#include "universalisos/baremetal.h" + +extern "C" { + +static riscv_vm_t* vm_by_id(uint32_t vm_id) +{ + if (vm_id >= RISCV_VM_MAX) return nullptr; + riscv_vm_t* vm = &riscv_vms[vm_id]; + if (vm->state == RISCV_VM_STOPPED && vm->gstage_root == 0) + return nullptr; + return vm; +} + +int riscv_guest_copy_to_guest(uint32_t vm_id, uint64_t gpa, + const void* src, uint64_t size) +{ + riscv_vm_t* vm = vm_by_id(vm_id); + if (!vm || !src || size == 0) return -1; + if (gpa < vm->mem_base || (gpa + size) > (vm->mem_base + vm->mem_size)) + return -1; + + /* In a two-stage system the guest physical memory is mapped 1:1 in the + * G-stage page tables, so GPA == host physical address for the RAM region. + * A real implementation must use the G-stage walk. */ + universalisos::baremetal::memcpy((void*)gpa, src, (size_t)size); + return 0; +} + +int riscv_guest_map_dtb(uint32_t vm_id, uint64_t gpa, const void* dtb, + uint64_t size) +{ + riscv_vm_t* vm = vm_by_id(vm_id); + if (!vm || !dtb || size == 0) return -1; + if (gpa < vm->mem_base || (gpa + size) > (vm->mem_base + vm->mem_size)) + return -1; + + /* Map DTB region in G-stage as read-only device-ish memory */ + for (uint64_t off = 0; off < size; off += PAGE_SIZE) { + if (riscv_pt_map_page(vm->gstage_root, gpa + off, gpa + off, + PTE_V | PTE_R | PTE_A | PTE_PBMT_IO, true) != 0) + return -1; + } + + universalisos::baremetal::memcpy((void*)gpa, dtb, (size_t)size); + return 0; +} + +int riscv_guest_load_image(uint32_t vm_id, const riscv_guest_image_t* image) +{ + riscv_vm_t* vm = vm_by_id(vm_id); + if (!vm || !image) return -1; + + switch (image->type) { + case RISCV_GUEST_RAW: + case RISCV_GUEST_IMAGE: + /* Copy image from hypervisor physical address to guest PA */ + if (riscv_guest_copy_to_guest(vm_id, image->load_phys, + (const void*)image->load_phys, + image->size) != 0) + return -1; + break; + + case RISCV_GUEST_ELF: + /* ELF parsing is not implemented in this foundation layer. + * The caller must parse ELF and provide load_phys/entry_phys. */ + return -1; + } + + /* Ensure the entry region is executable in G-stage */ + for (uint64_t off = 0; off < image->size; off += PAGE_SIZE) { + vaddr_t va = image->load_phys + off; + paddr_t pa = image->load_phys + off; + riscv_pt_map_page(vm->gstage_root, va, pa, + PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D, true); + } + + vm->entry_point = image->entry_phys; + vm->dtb_phys = image->dtb_phys; + return 0; +} + +int riscv_guest_boot_vcpu(uint32_t vcpu_id, uint64_t entry, uint64_t dtb) +{ + if (vcpu_id >= RISCV_VCPU_MAX) return -1; + riscv_vcpu_t* vcpu = &riscv_vcpus[vcpu_id]; + if (!vcpu->active) return -1; + + return riscv_vcpu_reset(vcpu_id, entry, dtb); +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/hm.cpp b/kernel/src/arch/riscv/hm.cpp new file mode 100644 index 000000000..47073989d --- /dev/null +++ b/kernel/src/arch/riscv/hm.cpp @@ -0,0 +1,107 @@ +/* + * UniversalisOS RISC-V Health Monitoring Implementation + */ + +#include "hm.h" +#include "timer.h" +#include "uart.h" + +#define RISCV_HM_LOG_MAX 32 + +static riscv_hm_record_t hm_log[RISCV_HM_LOG_MAX]; +static uint32_t hm_log_head = 0; +static uint32_t hm_log_count = 0; +static riscv_hm_handler_t hm_handler = riscv_hm_default_handler; + +extern "C" { + +void riscv_hm_init(void) +{ + hm_handler = riscv_hm_default_handler; + hm_log_head = 0; + hm_log_count = 0; +} + +void riscv_hm_set_handler(riscv_hm_handler_t handler) +{ + hm_handler = handler ? handler : riscv_hm_default_handler; +} + +void riscv_hm_event(riscv_hm_event_t event, riscv_hm_level_t level, + uint32_t partition_id, uint32_t task_id, + uint64_t pc, uint64_t info, const char* msg) +{ + riscv_hm_record_t rec; + rec.event = event; + rec.level = level; + rec.partition_id = partition_id; + rec.task_id = task_id; + rec.timestamp_us = riscv_timer_get_time(); + rec.pc = pc; + rec.info = info; + rec.msg = msg; + + /* Store in circular log */ + uint32_t idx = (hm_log_head + hm_log_count) % RISCV_HM_LOG_MAX; + hm_log[idx] = rec; + if (hm_log_count < RISCV_HM_LOG_MAX) + hm_log_count++; + else + hm_log_head = (hm_log_head + 1) % RISCV_HM_LOG_MAX; + + if (hm_handler) + hm_handler(&rec); + + riscv_hm_partition_action(partition_id, level); +} + +void riscv_hm_partition_action(uint32_t partition_id, riscv_hm_level_t action) +{ + (void)partition_id; + switch (action) { + case RISCV_HM_LEVEL_NOACTION: + case RISCV_HM_LEVEL_LOG: + case RISCV_HM_LEVEL_IGNORE: + return; + case RISCV_HM_LEVEL_PARTITION_STOP: + /* TODO: stop partition */ + return; + case RISCV_HM_LEVEL_PARTITION_RESTART: + /* TODO: restart partition */ + return; + case RISCV_HM_LEVEL_RESET: + case RISCV_HM_LEVEL_SHUTDOWN: + case RISCV_HM_LEVEL_SYSTEM_STOP: + riscv_hm_panic("HM critical action triggered"); + return; + } +} + +void riscv_hm_default_handler(const riscv_hm_record_t* rec) +{ + universalisos::uart::puts("[HM] event="); + universalisos::uart::print_dec((unsigned int)rec->event); + universalisos::uart::puts(" level="); + universalisos::uart::print_dec((unsigned int)rec->level); + universalisos::uart::puts(" part="); + universalisos::uart::print_dec(rec->partition_id); + universalisos::uart::puts(" task="); + universalisos::uart::print_dec(rec->task_id); + if (rec->msg) { + universalisos::uart::puts(" msg="); + universalisos::uart::puts(rec->msg); + } + universalisos::uart::puts("\r\n"); +} + +void riscv_hm_panic(const char* msg) +{ + universalisos::uart::puts("\r\n*** HM PANIC: "); + if (msg) universalisos::uart::puts(msg); + universalisos::uart::puts(" ***\r\n"); + while (1) { + __asm__ volatile("wfi"); + } +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/inc/context_switch.h b/kernel/src/arch/riscv/inc/context_switch.h new file mode 100644 index 000000000..bf5d001d6 --- /dev/null +++ b/kernel/src/arch/riscv/inc/context_switch.h @@ -0,0 +1,30 @@ +#ifndef UNIVERSALISOS_RISCV_CONTEXT_SWITCH_H +#define UNIVERSALISOS_RISCV_CONTEXT_SWITCH_H + +#include +#include "exceptions.h" +#include "vm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint64_t gpr[32]; /* x0 - x31 */ + uint64_t pc; /* sepc */ + uint64_t status; /* sstatus */ + uint64_t cause; /* scause */ + uint64_t badaddr; /* stval */ +} riscv_context_t; + +void riscv_context_init(riscv_context_t* ctx, void* entry_point, void* stack_top); +void riscv_context_switch(riscv_context_t* next, riscv_context_t* prev); + +/* vCPU assembly entry/exit. 'regs' must point to vcpu->regs. */ +void riscv_vcpu_entry(riscv_trap_frame_t* regs); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_CONTEXT_SWITCH_H */ diff --git a/kernel/src/arch/riscv/inc/cpu.h b/kernel/src/arch/riscv/inc/cpu.h new file mode 100644 index 000000000..7a98d0717 --- /dev/null +++ b/kernel/src/arch/riscv/inc/cpu.h @@ -0,0 +1,36 @@ +/* + * UniversalisOS RISC-V CPU / H-extension Initialisation + */ + +#ifndef UNIVERSALISOS_RISCV_CPU_H +#define UNIVERSALISOS_RISCV_CPU_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define RISCV_HART_MAX 8 + +typedef struct { + uint32_t hart_id; + bool online; + uint64_t isa; + bool h_extension; + bool sstc; + bool svpbmt; +} riscv_cpu_info_t; + +extern riscv_cpu_info_t riscv_cpu_info[RISCV_HART_MAX]; + +void riscv_cpu_init(uint32_t hart_id); +bool riscv_cpu_has_h_extension(void); +void riscv_cpu_print_info(void); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_CPU_H */ diff --git a/kernel/src/arch/riscv/inc/csr.h b/kernel/src/arch/riscv/inc/csr.h new file mode 100644 index 000000000..0e65fb20e --- /dev/null +++ b/kernel/src/arch/riscv/inc/csr.h @@ -0,0 +1,184 @@ +/* + * UniversalisOS RISC-V CSR Definitions + * Based on RISC-V Privileged Spec 1.12 + H-extension + * + * Reference: Bao src/arch/riscv/inc/arch/csrs.h, seL4 include/arch/riscv/arch/machine.h + */ + +#ifndef UNIVERSALISOS_RISCV_CSR_H +#define UNIVERSALISOS_RISCV_CSR_H + +#ifndef __ASSEMBLER__ +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Machine-level CSRs */ +#define CSR_MSTATUS 0x300 +#define CSR_MISA 0x301 +#define CSR_MEDELEG 0x302 +#define CSR_MIDELEG 0x303 +#define CSR_MIE 0x304 +#define CSR_MTVEC 0x305 +#define CSR_MSCRATCH 0x340 +#define CSR_MEPC 0x341 +#define CSR_MCAUSE 0x342 +#define CSR_MTVAL 0x343 +#define CSR_MIP 0x344 + +/* Supervisor-level CSRs */ +#define CSR_SSTATUS 0x100 +#define CSR_SIE 0x104 +#define CSR_STVEC 0x105 +#define CSR_SSCRATCH 0x140 +#define CSR_SEPC 0x141 +#define CSR_SCAUSE 0x142 +#define CSR_STVAL 0x143 +#define CSR_SIP 0x144 +#define CSR_SATP 0x180 + +/* Hypervisor CSRs */ +#define CSR_HSTATUS 0x600 +#define CSR_HEDELEG 0x602 +#define CSR_HIDELEG 0x603 +#define CSR_HIE 0x604 +#define CSR_HCOUNTEREN 0x606 +#define CSR_HGEIE 0x607 +#define CSR_HTVAL 0x643 +#define CSR_HIP 0x644 +#define CSR_HVIP 0x645 +#define CSR_HTINST 0x64A +#define CSR_HGATP 0x680 +#define CSR_HENVCFG 0x60A +#define CSR_HENVCFGH 0x61A +#define CSR_HSTATEEN0 0x60C +#define CSR_HSTATEEN0H 0x61C + +/* Virtual supervisor CSRs */ +#define CSR_VSSTATUS 0x200 +#define CSR_VSIE 0x204 +#define CSR_VSTVEC 0x205 +#define CSR_VSSCRATCH 0x240 +#define CSR_VSEPC 0x241 +#define CSR_VSCAUSE 0x242 +#define CSR_VSTVAL 0x243 +#define CSR_VSIP 0x244 +#define CSR_VSATP 0x280 + +/* MSTATUS bits */ +#define MSTATUS_MIE (1ULL << 3) +#define MSTATUS_MPIE (1ULL << 7) +#define MSTATUS_MPP (3ULL << 11) +#define MSTATUS_MPP_M (3ULL << 11) +#define MSTATUS_MPP_S (1ULL << 11) +#define MSTATUS_MPP_U (0ULL << 11) +#define MSTATUS_MPRV (1ULL << 17) + +/* SSTATUS bits */ +#define SSTATUS_SIE (1ULL << 1) +#define SSTATUS_SPIE (1ULL << 5) +#define SSTATUS_SPP (1ULL << 8) +#define SSTATUS_FS (3ULL << 13) +#define SSTATUS_XS (3ULL << 15) +#define SSTATUS_SUM (1ULL << 18) +#define SSTATUS_MXR (1ULL << 19) +#define SSTATUS_SD (1ULL << 63) + +/* HSTATUS bits */ +#define HSTATUS_VSBE (1ULL << 5) +#define HSTATUS_GVA (1ULL << 6) +#define HSTATUS_SPV (1ULL << 7) +#define HSTATUS_SPVP (1ULL << 8) +#define HSTATUS_HU (1ULL << 9) +#define HSTATUS_VGEIN (0x3FUL << 12) +#define HSTATUS_VTVM (1ULL << 20) +#define HSTATUS_VTW (1ULL << 21) +#define HSTATUS_VTSR (1ULL << 22) +#define HSTATUS_VSXL (3ULL << 32) +#define HSTATUS_VSXL_64 (2ULL << 32) + +/* MIP / MIE bits */ +#define IRQ_M_SOFT 3 +#define IRQ_M_TIMER 7 +#define IRQ_M_EXT 11 +#define IRQ_S_SOFT 1 +#define IRQ_S_TIMER 5 +#define IRQ_S_EXT 9 +#define IRQ_VS_SOFT 2 +#define IRQ_VS_TIMER 6 +#define IRQ_VS_EXT 10 + +#define MIP_MSIP (1ULL << IRQ_M_SOFT) +#define MIP_MTIP (1ULL << IRQ_M_TIMER) +#define MIP_MEIP (1ULL << IRQ_M_EXT) +#define MIP_SSIP (1ULL << IRQ_S_SOFT) +#define MIP_STIP (1ULL << IRQ_S_TIMER) +#define MIP_SEIP (1ULL << IRQ_S_EXT) +#define MIP_VSSIP (1ULL << IRQ_VS_SOFT) +#define MIP_VSTIP (1ULL << IRQ_VS_TIMER) +#define MIP_VSEIP (1ULL << IRQ_VS_EXT) + +/* MCAUSE / SCAUSE exception codes */ +#define CAUSE_MISALIGNED_FETCH 0 +#define CAUSE_FETCH_ACCESS 1 +#define CAUSE_ILLEGAL_INSTRUCTION 2 +#define CAUSE_BREAKPOINT 3 +#define CAUSE_MISALIGNED_LOAD 4 +#define CAUSE_LOAD_ACCESS 5 +#define CAUSE_MISALIGNED_STORE 6 +#define CAUSE_STORE_ACCESS 7 +#define CAUSE_USER_ECALL 8 +#define CAUSE_SUPERVISOR_ECALL 9 +#define CAUSE_MACHINE_ECALL 11 +#define CAUSE_FETCH_PAGE_FAULT 12 +#define CAUSE_LOAD_PAGE_FAULT 13 +#define CAUSE_STORE_PAGE_FAULT 15 +#define CAUSE_FETCH_GUEST_PAGE_FAULT 20 +#define CAUSE_LOAD_GUEST_PAGE_FAULT 21 +#define CAUSE_VIRTUAL_INSTRUCTION 22 +#define CAUSE_STORE_GUEST_PAGE_FAULT 23 + +/* SATP / HGATP modes */ +#define SATP_MODE_BARE 0ULL +#define SATP_MODE_SV39 8ULL +#define SATP_MODE_SV48 9ULL +#define HGATP_MODE_BARE 0ULL +#define HGATP_MODE_SV39X4 8ULL +#define HGATP_MODE_SV48X4 9ULL + +#define SATP_MODE_SHIFT 60 +#define HGATP_MODE_SHIFT 60 +#define HGATP_VMID_SHIFT 44 + +#ifndef __ASSEMBLER__ +static inline uint64_t csr_read(uint16_t csr) +{ + uint64_t val; + __asm__ volatile ("csrr %0, %1" : "=r"(val) : "i"(csr)); + return val; +} + +static inline void csr_write(uint16_t csr, uint64_t val) +{ + __asm__ volatile ("csrw %0, %1" :: "i"(csr), "r"(val)); +} + +static inline void csr_set(uint16_t csr, uint64_t bits) +{ + __asm__ volatile ("csrs %0, %1" :: "i"(csr), "r"(bits)); +} + +static inline void csr_clear(uint16_t csr, uint64_t bits) +{ + __asm__ volatile ("csrc %0, %1" :: "i"(csr), "r"(bits)); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_CSR_H */ diff --git a/kernel/src/arch/riscv/inc/exceptions.h b/kernel/src/arch/riscv/inc/exceptions.h new file mode 100644 index 000000000..37a110eb1 --- /dev/null +++ b/kernel/src/arch/riscv/inc/exceptions.h @@ -0,0 +1,85 @@ +#ifndef UNIVERSALISOS_RISCV_EXCEPTIONS_H +#define UNIVERSALISOS_RISCV_EXCEPTIONS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Full trap frame layout; must match trap.S offsets */ +typedef struct { + uint64_t ra; /* x1 */ + uint64_t sp; /* x2 */ + uint64_t gp; /* x3 */ + uint64_t tp; /* x4 */ + uint64_t t0; /* x5 */ + uint64_t t1; /* x6 */ + uint64_t t2; /* x7 */ + uint64_t s0; /* x8 */ + uint64_t s1; /* x9 */ + uint64_t a0; /* x10 */ + uint64_t a1; /* x11 */ + uint64_t a2; /* x12 */ + uint64_t a3; /* x13 */ + uint64_t a4; /* x14 */ + uint64_t a5; /* x15 */ + uint64_t a6; /* x16 */ + uint64_t a7; /* x17 */ + uint64_t s2; /* x18 */ + uint64_t s3; /* x19 */ + uint64_t s4; /* x20 */ + uint64_t s5; /* x21 */ + uint64_t s6; /* x22 */ + uint64_t s7; /* x23 */ + uint64_t s8; /* x24 */ + uint64_t s9; /* x25 */ + uint64_t s10; /* x26 */ + uint64_t s11; /* x27 */ + uint64_t t3; /* x28 */ + uint64_t t4; /* x29 */ + uint64_t t5; /* x30 */ + uint64_t t6; /* x31 */ + uint64_t sepc; + uint64_t sstatus; + uint64_t scause; + uint64_t stval; + uint64_t hstatus; + uint64_t scounteren; + uint64_t _pad[3]; +} riscv_trap_frame_t; + +typedef struct { + uint32_t instruction_address_misaligned_count; + uint32_t instruction_access_fault_count; + uint32_t illegal_instruction_count; + uint32_t breakpoint_count; + uint32_t load_address_misaligned_count; + uint32_t load_access_fault_count; + uint32_t store_address_misaligned_count; + uint32_t store_access_fault_count; + uint32_t environment_call_count; + uint32_t instruction_page_fault_count; + uint32_t load_page_fault_count; + uint32_t store_page_fault_count; + uint32_t guest_page_fault_count; + uint32_t virtual_instruction_count; + uint32_t interrupt_count; + uint32_t timer_interrupt_count; + uint32_t external_interrupt_count; +} riscv_exception_stats_t; + +void riscv_exceptions_init(); +void riscv_get_exception_stats(riscv_exception_stats_t* stats); +void riscv_print_exception_stats(); + +/* C trap handler called from trap.S */ +void riscv_trap_handler_c(uint64_t scause, uint64_t stval, uint64_t sepc, + uint64_t sstatus, riscv_trap_frame_t* frame); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_EXCEPTIONS_H */ diff --git a/kernel/src/arch/riscv/inc/guest_boot.h b/kernel/src/arch/riscv/inc/guest_boot.h new file mode 100644 index 000000000..2d78c7e6c --- /dev/null +++ b/kernel/src/arch/riscv/inc/guest_boot.h @@ -0,0 +1,51 @@ +/* + * UniversalisOS RISC-V Guest OS Boot Loader + * + * Supports loading bare-metal / Linux-style guest images into a VM and + * entering VS-mode with a0=hart id, a1=dtb physical address. + * + * Reference: Bao src/core/vm.c, PikeOS share/hwvirt-linux/ guest loading. + */ + +#ifndef UNIVERSALISOS_RISCV_GUEST_BOOT_H +#define UNIVERSALISOS_RISCV_GUEST_BOOT_H + +#include +#include "vm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RISCV_GUEST_RAW = 0, + RISCV_GUEST_ELF, + RISCV_GUEST_IMAGE +} riscv_guest_image_type_t; + +typedef struct { + riscv_guest_image_type_t type; + uint64_t load_phys; /* physical address where image is loaded */ + uint64_t entry_phys; /* guest physical entry point */ + uint64_t size; + uint64_t dtb_phys; /* device tree physical address */ +} riscv_guest_image_t; + +/* Load a guest image from hypervisor physical memory into the VM's + * guest physical address space. */ +int riscv_guest_load_image(uint32_t vm_id, const riscv_guest_image_t* image); + +/* Prepare a vCPU and boot the guest. */ +int riscv_guest_boot_vcpu(uint32_t vcpu_id, uint64_t entry, uint64_t dtb); + +/* Helpers */ +int riscv_guest_copy_to_guest(uint32_t vm_id, uint64_t gpa, + const void* src, uint64_t size); +int riscv_guest_map_dtb(uint32_t vm_id, uint64_t gpa, const void* dtb, + uint64_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_GUEST_BOOT_H */ diff --git a/kernel/src/arch/riscv/inc/hm.h b/kernel/src/arch/riscv/inc/hm.h new file mode 100644 index 000000000..3182fc11f --- /dev/null +++ b/kernel/src/arch/riscv/inc/hm.h @@ -0,0 +1,74 @@ +/* + * UniversalisOS RISC-V Health Monitoring (HM) + * + * PikeOS 5.0 HM foundation: + * - HM event types and error levels + * - Per-partition HM actions + * - Error containment / fault escalation + * + * Reference: PikeOS sources/ukernel-arm_v7hf/src/hm.c, include/kernel/p4hm.h + */ + +#ifndef UNIVERSALISOS_RISCV_HM_H +#define UNIVERSALISOS_RISCV_HM_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RISCV_HM_LEVEL_NOACTION = 0, + RISCV_HM_LEVEL_LOG, + RISCV_HM_LEVEL_IGNORE, + RISCV_HM_LEVEL_RESET, + RISCV_HM_LEVEL_SHUTDOWN, + RISCV_HM_LEVEL_PARTITION_RESTART, + RISCV_HM_LEVEL_PARTITION_STOP, + RISCV_HM_LEVEL_SYSTEM_STOP +} riscv_hm_level_t; + +typedef enum { + RISCV_HM_EVENT_TRAP = 0, + RISCV_HM_EVENT_PAGE_FAULT, + RISCV_HM_EVENT_TIMEOUT, + RISCV_HM_EVENT_DEADLINE_MISS, + RISCV_HM_EVENT_STACK_OVERFLOW, + RISCV_HM_EVENT_ILLEGAL_INSTRUCTION, + RISCV_HM_EVENT_PRIVILEGE_VIOLATION, + RISCV_HM_EVENT_IO_ACCESS_VIOLATION, + RISCV_HM_EVENT_PARTITION_PANIC, + RISCV_HM_EVENT_COUNT +} riscv_hm_event_t; + +typedef struct { + riscv_hm_event_t event; + riscv_hm_level_t level; + uint32_t partition_id; + uint32_t task_id; + uint64_t timestamp_us; + uint64_t pc; + uint64_t info; + const char* msg; +} riscv_hm_record_t; + +typedef void (*riscv_hm_handler_t)(const riscv_hm_record_t* rec); + +void riscv_hm_init(void); +void riscv_hm_set_handler(riscv_hm_handler_t handler); +void riscv_hm_event(riscv_hm_event_t event, riscv_hm_level_t level, + uint32_t partition_id, uint32_t task_id, + uint64_t pc, uint64_t info, const char* msg); +void riscv_hm_partition_action(uint32_t partition_id, riscv_hm_level_t action); + +/* Default handlers */ +void riscv_hm_default_handler(const riscv_hm_record_t* rec); +void riscv_hm_panic(const char* msg); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_HM_H */ diff --git a/kernel/src/arch/riscv/inc/mem_pikeos.h b/kernel/src/arch/riscv/inc/mem_pikeos.h new file mode 100644 index 000000000..f8a57672f --- /dev/null +++ b/kernel/src/arch/riscv/inc/mem_pikeos.h @@ -0,0 +1,76 @@ +/* + * UniversalisOS RISC-V PikeOS-Style Memory Mapping API + * + * Implements PikeOS 5.0 p4map semantics: + * - map / unmap / update virtual memory regions + * - per-partition address spaces with permission flags + * - G-stage (guest physical) and VS-stage (guest virtual) support + * + * Reference: PikeOS sources/ukernel-arm_v7hf/include/kernel/p4map.h, + * src/map.c, arch/arm/src/mmu.c + */ + +#ifndef UNIVERSALISOS_RISCV_MEM_PIKEOS_H +#define UNIVERSALISOS_RISCV_MEM_PIKEOS_H + +#include +#include +#include "page_table.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* PikeOS mapping flags (subset aligned with P4_M_*) */ +#define RISCV_M_UPDATE (1U << 0) +#define RISCV_M_READ (1U << 1) +#define RISCV_M_WRITE (1U << 2) +#define RISCV_M_EXEC (1U << 3) +#define RISCV_M_C_ENABLE (1U << 4) +#define RISCV_M_C_WRITEBACK (1U << 5) +#define RISCV_M_REPLACE (1U << 11) +#define RISCV_M_C_UPDATE (1U << 10) +#define RISCV_M_IO (1U << 12) +#define RISCV_M_UNCACHEABLE (1U << 13) + +#define RISCV_M_PERM_MASK (RISCV_M_READ | RISCV_M_WRITE | RISCV_M_EXEC) + +typedef struct { + uint32_t asid; + paddr_t root_pt; + uint64_t va_start; + uint64_t va_size; +} riscv_aspace_t; + +typedef struct { + vaddr_t virt; + paddr_t phys; + uint64_t size; + uint32_t flags; +} riscv_mem_region_t; + +/* Address-space lifecycle */ +int riscv_aspace_create(uint32_t asid, uint64_t va_start, uint64_t va_size, + riscv_aspace_t* out); +int riscv_aspace_destroy(riscv_aspace_t* as); + +/* Mapping operations */ +int riscv_aspace_map(riscv_aspace_t* as, vaddr_t va, paddr_t pa, + uint64_t size, uint32_t flags); +int riscv_aspace_unmap(riscv_aspace_t* as, vaddr_t va, uint64_t size); +int riscv_aspace_update(riscv_aspace_t* as, vaddr_t va, uint64_t size, + uint32_t flags); +int riscv_aspace_unmap_all(riscv_aspace_t* as); + +/* Translation / lookup */ +paddr_t riscv_aspace_translate(riscv_aspace_t* as, vaddr_t va); +pte_t* riscv_aspace_lookup(riscv_aspace_t* as, vaddr_t va); + +/* TLB */ +void riscv_aspace_flush(riscv_aspace_t* as); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_MEM_PIKEOS_H */ diff --git a/kernel/src/arch/riscv/inc/page_table.h b/kernel/src/arch/riscv/inc/page_table.h new file mode 100644 index 000000000..b00464bc1 --- /dev/null +++ b/kernel/src/arch/riscv/inc/page_table.h @@ -0,0 +1,86 @@ +/* + * UniversalisOS RISC-V Page Table Interface + * Sv39 / Sv39x4 (G-stage) support for RV64. + * + * Reference: Bao src/arch/riscv/inc/arch/page_table.h, src/arch/riscv/page_table.c + */ + +#ifndef UNIVERSALISOS_RISCV_PAGE_TABLE_H +#define UNIVERSALISOS_RISCV_PAGE_TABLE_H + +#ifndef __ASSEMBLER__ +#include +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define PAGE_SHIFT 12 +#define PAGE_SIZE (1UL << PAGE_SHIFT) +#define PAGE_MASK (PAGE_SIZE - 1) +#define PTE_SIZE 8 +#define PTES_PER_PT (PAGE_SIZE / PTE_SIZE) +#define PT_LEVELS 3 + +#define VA_BITS 39 +#define VPN_MASK 0x1FF + +/* PTE flags */ +#define PTE_V (1ULL << 0) +#define PTE_R (1ULL << 1) +#define PTE_W (1ULL << 2) +#define PTE_X (1ULL << 3) +#define PTE_U (1ULL << 4) +#define PTE_G (1ULL << 5) +#define PTE_A (1ULL << 6) +#define PTE_D (1ULL << 7) +#define PTE_PBMT_IO (1ULL << 62) /* Svpbmt: IO ordering */ +#define PTE_PBMT_NC (2ULL << 62) /* Svpbmt: non-cacheable */ + +#define PTE_TABLE_FLAGS (PTE_V) +#define PTE_PAGE_FLAGS (PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D) +#define PTE_DEVICE_FLAGS (PTE_V | PTE_R | PTE_W | PTE_A | PTE_D | PTE_PBMT_IO) + +#ifndef __ASSEMBLER__ +typedef uint64_t pte_t; +typedef uint64_t paddr_t; +typedef uint64_t vaddr_t; + +static inline uint64_t pte_ppn(pte_t pte) { return (pte >> 10) & ((1ULL << 44) - 1); } +static inline paddr_t pte_addr(pte_t pte) { return pte_ppn(pte) << PAGE_SHIFT; } +static inline bool pte_valid(pte_t pte) { return (pte & PTE_V) != 0; } +static inline bool pte_leaf(pte_t pte) { return pte_valid(pte) && (pte & (PTE_R | PTE_W | PTE_X)); } +static inline pte_t pte_make(paddr_t pa, uint64_t flags) +{ + return ((pa >> PAGE_SHIFT) << 10) | flags; +} + +/* Page-table allocator: returns a zeroed page-aligned physical frame. + * Backed by a simple bitmap over a static pool. */ +paddr_t riscv_pt_alloc_frame(void); +void riscv_pt_free_frame(paddr_t frame); + +/* Map a single 4 KiB page. Creates intermediate tables as needed. + * root_pa must be a physical address of a root page table. + * is_gstage selects Sv39x4 semantics (HGATP) for guest mappings. */ +int riscv_pt_map_page(paddr_t root_pa, vaddr_t va, paddr_t pa, uint64_t flags, bool is_gstage); + +/* Unmap a page. */ +int riscv_pt_unmap_page(paddr_t root_pa, vaddr_t va, bool is_gstage); + +/* Translate a virtual address through a page table (walk only). */ +pte_t* riscv_pt_walk(paddr_t root_pa, vaddr_t va, bool is_gstage); + +/* Invalidate TLB for a given address and optional ASID/VMID. */ +void riscv_tlb_inval_va(vaddr_t va, uint64_t asid); +void riscv_tlb_inval_all(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_PAGE_TABLE_H */ diff --git a/kernel/src/arch/riscv/inc/plic.h b/kernel/src/arch/riscv/inc/plic.h new file mode 100644 index 000000000..f6def29e2 --- /dev/null +++ b/kernel/src/arch/riscv/inc/plic.h @@ -0,0 +1,33 @@ +/* + * UniversalisOS RISC-V PLIC Driver + * + * Reference: Bao src/arch/riscv/irqc/plic/plic.c, seL4 include/drivers/irq/riscv_plic0.h + */ + +#ifndef UNIVERSALISOS_RISCV_PLIC_H +#define UNIVERSALISOS_RISCV_PLIC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PLIC_BASE 0x0C000000ULL +#define PLIC_MAX_IRQ 127 +#define PLIC_NUM_CONTEXTS 2 + +void riscv_plic_init(void); +void riscv_plic_enable(unsigned int context, unsigned int irq); +void riscv_plic_disable(unsigned int context, unsigned int irq); +void riscv_plic_set_priority(unsigned int irq, unsigned int priority); +void riscv_plic_set_threshold(unsigned int context, unsigned int threshold); +unsigned int riscv_plic_claim(unsigned int context); +void riscv_plic_complete(unsigned int context, unsigned int irq); +void riscv_plic_handle_interrupt(void); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_PLIC_H */ diff --git a/kernel/src/arch/riscv/inc/sbi.h b/kernel/src/arch/riscv/inc/sbi.h new file mode 100644 index 000000000..05ceacd8f --- /dev/null +++ b/kernel/src/arch/riscv/inc/sbi.h @@ -0,0 +1,47 @@ +/* + * UniversalisOS RISC-V SBI Interface + * + * Reference: Bao src/arch/riscv/inc/arch/sbi.h, seL4 include/arch/riscv/arch/sbi.h + */ + +#ifndef UNIVERSALISOS_RISCV_SBI_H +#define UNIVERSALISOS_RISCV_SBI_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct sbiret { + long error; + long value; +}; + +struct sbiret sbi_ecall(long eid, long fid, long arg0, long arg1, + long arg2, long arg3, long arg4, long arg5); + +static inline struct sbiret sbi_set_timer(uint64_t stime_value) { + return sbi_ecall(0x54494D45, 0, (long)stime_value, 0, 0, 0, 0, 0); +} + +static inline struct sbiret sbi_send_ipi(unsigned long hart_mask, + unsigned long hart_mask_base) { + return sbi_ecall(0x735049, 0, (long)hart_mask, (long)hart_mask_base, 0, 0, 0, 0); +} + +static inline struct sbiret sbi_hsm_hart_start(unsigned long hartid, + unsigned long start_addr, + unsigned long opaque) { + return sbi_ecall(0x48534D, 0, (long)hartid, (long)start_addr, (long)opaque, 0, 0, 0); +} + +static inline struct sbiret sbi_hsm_hart_stop(void) { + return sbi_ecall(0x48534D, 1, 0, 0, 0, 0, 0, 0); +} + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_SBI_H */ diff --git a/kernel/src/arch/riscv/inc/sched_pikeos.h b/kernel/src/arch/riscv/inc/sched_pikeos.h new file mode 100644 index 000000000..7d900689a --- /dev/null +++ b/kernel/src/arch/riscv/inc/sched_pikeos.h @@ -0,0 +1,139 @@ +/* + * UniversalisOS RISC-V PikeOS-Style Scheduler + * + * Implements PikeOS 5.0 scheduling concepts: + * - Time partitioning (major frame / windows) + * - Preemptive priority scheduling within a time partition + * - RMS / DMS / ARINC-653 time partitioning foundation + * + * Reference: PikeOS sources/ukernel-arm_v7hf/src/sched.c, tps.c, + * include/kernel/p4timepart.h + */ + +#ifndef UNIVERSALISOS_RISCV_SCHED_PIKEOS_H +#define UNIVERSALISOS_RISCV_SCHED_PIKEOS_H + +#include +#include +#include "uos/uos_types.h" +#include "context_switch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define RISCV_SCHED_MAX_TIMEPART 16 +#define RISCV_SCHED_MAX_WINDOWS 64 +#define RISCV_SCHED_MAX_TASKS 64 +#define RISCV_SCHED_NUM_PRIO 32 +#define RISCV_SCHED_PRIO_IDLE 0 +#define RISCV_SCHED_PRIO_MAX (RISCV_SCHED_NUM_PRIO - 1) + +typedef enum { + RISCV_TASK_INVALID = 0, + RISCV_TASK_READY, + RISCV_TASK_RUNNING, + RISCV_TASK_BLOCKED, + RISCV_TASK_SLEEPING, + RISCV_TASK_SUSPENDED +} riscv_task_state_t; + +typedef struct riscv_task { + uint32_t task_id; + uint32_t partition_id; + uint32_t timepart_id; + uos_priority_t priority; + uos_priority_t base_priority; + riscv_task_state_t state; + uint64_t deadline_us; + uint64_t period_us; + uint64_t next_release_us; + uint64_t time_slice_us; + uint64_t time_used_us; + void* stack_top; + void (*entry)(void); + riscv_context_t _ctx; + struct riscv_task* next; +} riscv_task_t; + +typedef struct { + uint32_t timepart_id; + uint32_t duration_us; + uint32_t min_duration_us; + uint16_t flags; + uint16_t userdata; + uint32_t window_id; +} riscv_window_t; + +typedef struct { + uint32_t schema_id; + uint32_t num_windows; + uint32_t major_frame_us; + uint32_t current_window; + uint64_t window_start_us; + riscv_window_t windows[RISCV_SCHED_MAX_WINDOWS]; +} riscv_timepart_schema_t; + +typedef struct { + riscv_task_t* head; + riscv_task_t* tail; +} riscv_ready_queue_t; + +typedef struct { + uint32_t cpu_id; + uint32_t current_timepart; + uint32_t current_schema; + uint64_t major_frame_start_us; + riscv_task_t* current_task; + riscv_task_t* idle_task; + riscv_ready_queue_t readyq[RISCV_SCHED_NUM_PRIO]; +} riscv_sched_cpu_t; + +typedef struct { + riscv_task_t tasks[RISCV_SCHED_MAX_TASKS]; + uint32_t num_tasks; + riscv_timepart_schema_t schemas[RISCV_SCHED_MAX_TIMEPART]; + uint32_t num_schemas; + riscv_sched_cpu_t cpus[1]; /* UP first; extend for SMP */ + uint64_t tick_interval_us; + uint64_t monotonic_time_us; + bool initialized; +} riscv_sched_state_t; + +extern riscv_sched_state_t riscv_sched_state; + +/* Lifecycle */ +void riscv_sched_init(uint64_t tick_interval_us); +void riscv_sched_start(void); + +/* Time partition schema / windows */ +int riscv_sched_schema_create(uint32_t schema_id, uint32_t major_frame_us); +int riscv_sched_window_add(uint32_t schema_id, uint32_t timepart_id, + uint32_t duration_us, uint32_t min_duration_us, + uint16_t flags, uint16_t userdata, uint32_t window_id); + +/* Tasks */ +int riscv_sched_task_create(uint32_t partition_id, uint32_t timepart_id, + uos_priority_t priority, uint64_t period_us, + uint64_t deadline_us, uint64_t time_slice_us, + void* stack_top, void (*entry)(void)); +void riscv_sched_task_yield(void); +void riscv_sched_task_block(uint32_t task_id); +void riscv_sched_task_unblock(uint32_t task_id); +void riscv_sched_task_sleep(uint32_t task_id, uint64_t us); + +/* Tick and context switch */ +void riscv_sched_tick(uint64_t now_us); +void riscv_sched_reschedule(void); +void riscv_sched_context_switch(riscv_task_t* prev, riscv_task_t* next); + +/* Helpers */ +riscv_task_t* riscv_sched_pick_next(void); +void riscv_sched_add_ready(riscv_task_t* task); +void riscv_sched_remove_ready(riscv_task_t* task); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_SCHED_PIKEOS_H */ diff --git a/kernel/src/arch/riscv/inc/timer.h b/kernel/src/arch/riscv/inc/timer.h new file mode 100644 index 000000000..8a1b4cd71 --- /dev/null +++ b/kernel/src/arch/riscv/inc/timer.h @@ -0,0 +1,23 @@ +/* + * UniversalisOS RISC-V Timer Driver (SBI-based) + */ + +#ifndef UNIVERSALISOS_RISCV_TIMER_H +#define UNIVERSALISOS_RISCV_TIMER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void riscv_timer_init(void); +void riscv_timer_set(uint64_t ticks); +void riscv_timer_clear(void); +uint64_t riscv_timer_get_time(void); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_TIMER_H */ diff --git a/kernel/src/arch/riscv/inc/uart.h b/kernel/src/arch/riscv/inc/uart.h new file mode 100644 index 000000000..af8b10842 --- /dev/null +++ b/kernel/src/arch/riscv/inc/uart.h @@ -0,0 +1,16 @@ +#ifndef UNIVERSALISOS_RISCV_UART_H +#define UNIVERSALISOS_RISCV_UART_H + +namespace universalisos { +namespace uart { + +void init(); +void putc(char c); +void puts(const char* s); +void print_dec(unsigned int val); +void print_hex(unsigned int val); + +} // namespace uart +} // namespace universalisos + +#endif // UNIVERSALISOS_RISCV_UART_H diff --git a/kernel/src/arch/riscv/inc/vm.h b/kernel/src/arch/riscv/inc/vm.h new file mode 100644 index 000000000..d5a17a5a5 --- /dev/null +++ b/kernel/src/arch/riscv/inc/vm.h @@ -0,0 +1,64 @@ +/* + * UniversalisOS RISC-V VM / vCPU State + */ + +#ifndef UNIVERSALISOS_RISCV_VM_H +#define UNIVERSALISOS_RISCV_VM_H + +#include +#include +#include "exceptions.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define RISCV_VM_MAX 4 +#define RISCV_VCPU_MAX (RISCV_VM_MAX * 4) + +typedef enum { + RISCV_VM_STOPPED = 0, + RISCV_VM_RUNNING, + RISCV_VM_PAUSED, + RISCV_VM_ERROR +} riscv_vm_state_t; + +typedef struct { + uint32_t vm_id; + riscv_vm_state_t state; + uint64_t gstage_root; /* physical root page table for hgatp */ + uint16_t vmid; + uint64_t entry_point; + uint64_t dtb_phys; + uint64_t mem_base; + uint64_t mem_size; +} riscv_vm_t; + +typedef struct { + uint32_t vcpu_id; + uint32_t vm_id; + uint32_t vcpu_no; /* index inside VM */ + uint32_t phys_hart; + bool active; + riscv_trap_frame_t regs; /* saved guest register state */ +} riscv_vcpu_t; + +extern riscv_vm_t riscv_vms[RISCV_VM_MAX]; +extern riscv_vcpu_t riscv_vcpus[RISCV_VCPU_MAX]; +extern riscv_vcpu_t* riscv_current_vcpu; + +int riscv_vm_create(uint32_t vm_id, uint64_t mem_base, uint64_t mem_size); +int riscv_vm_destroy(uint32_t vm_id); +int riscv_vcpu_create(uint32_t vm_id, uint32_t vcpu_no, uint32_t phys_hart); +int riscv_vcpu_reset(uint32_t vcpu_id, uint64_t entry, uint64_t dtb); +void riscv_vcpu_run(uint32_t vcpu_id); + +/* Trap-side helpers */ +void riscv_vm_handle_guest_page_fault(riscv_trap_frame_t* frame, uint64_t gpa, bool inst); +void riscv_vm_handle_virtual_instruction(riscv_trap_frame_t* frame); + +#ifdef __cplusplus +} +#endif + +#endif /* UNIVERSALISOS_RISCV_VM_H */ diff --git a/kernel/src/arch/riscv/kernel_riscv.cpp b/kernel/src/arch/riscv/kernel_riscv.cpp new file mode 100644 index 000000000..b176cfdca --- /dev/null +++ b/kernel/src/arch/riscv/kernel_riscv.cpp @@ -0,0 +1,113 @@ +/* + * UniversalisOS RISC-V Hypervisor Entry Point + * + * Minimal boot path for the RISC-V port. Initialises the hypervisor + * foundation (CPU detection, UART, trap handling, timer, PLIC, page + * tables, scheduler, memory API, HM) and runs self-tests before halting. + */ + +#include "uart.h" +#include "exceptions.h" +#include "context_switch.h" +#include "cpu.h" +#include "page_table.h" +#include "mem_pikeos.h" +#include "timer.h" +#include "plic.h" +#include "vm.h" +#include "guest_boot.h" +#include "sched_pikeos.h" +#include "hm.h" +#include "csr.h" +#include + +static void test_task(void) +{ + universalisos::uart::puts("[SCHED] test task running\r\n"); + riscv_sched_task_yield(); + universalisos::uart::puts("[SCHED] test task returned\r\n"); + while (1) { + riscv_sched_task_yield(); + } +} + +extern "C" void kernel_main(uint32_t hart_id, uint64_t dtb_pa) +{ + (void)dtb_pa; + + universalisos::uart::init(); + universalisos::uart::puts("\r\n"); + universalisos::uart::puts("UniversalisOS RISC-V 64-bit hypervisor booted.\r\n"); + universalisos::uart::puts("=== PikeOS 5.0 parity foundation ===\r\n"); + + riscv_cpu_init(hart_id); + riscv_cpu_print_info(); + + riscv_hm_init(); + riscv_exceptions_init(); + riscv_timer_init(); + riscv_plic_init(); + + universalisos::uart::puts("\r\n=== RISC-V Subsystem Tests ===\r\n"); + + /* Sv39 page-table smoke test */ + paddr_t root = riscv_pt_alloc_frame(); + if (root) { + int rc = riscv_pt_map_page(root, 0x40000000ULL, 0x80000000ULL, + PTE_PAGE_FLAGS, false); + if (rc == 0) { + universalisos::uart::puts("[OK] Sv39 page table map\r\n"); + } else { + universalisos::uart::puts("[FAIL] Sv39 page table map\r\n"); + } + riscv_pt_free_frame(root); + } + + /* PikeOS-style address-space smoke test */ + riscv_aspace_t as; + if (riscv_aspace_create(1, 0x40000000ULL, 0x40000000ULL, &as) == 0) { + if (riscv_aspace_map(&as, 0x40000000ULL, 0x80000000ULL, + 0x10000ULL, RISCV_M_READ | RISCV_M_WRITE | RISCV_M_EXEC) == 0) { + universalisos::uart::puts("[OK] p4map-style aspace map\r\n"); + } + riscv_aspace_destroy(&as); + } + + /* Guest VM creation smoke test */ + if (riscv_vm_create(0, 0x90000000ULL, 0x10000ULL) == 0) { + universalisos::uart::puts("[OK] Guest VM create\r\n"); + int vcpu = riscv_vcpu_create(0, 0, hart_id); + if (vcpu >= 0) { + universalisos::uart::puts("[OK] vCPU create\r\n"); + riscv_vcpu_reset(vcpu, 0x90000000ULL, 0); + } + riscv_vm_destroy(0); + } else { + universalisos::uart::puts("[FAIL] Guest VM create\r\n"); + } + + /* PikeOS-style scheduler smoke test */ + riscv_sched_init(1000); /* 1 ms tick */ + riscv_sched_schema_create(0, 10000); /* 10 ms major frame */ + riscv_sched_window_add(0, 1, 5000, 4500, 0, 0, 0); + riscv_sched_window_add(0, 2, 5000, 4500, 0, 0, 1); + static uint8_t test_stack[4096] __attribute__((aligned(16))); + int tid = riscv_sched_task_create(1, 1, 16, 0, 0, 1000, + (void*)((uintptr_t)test_stack + sizeof(test_stack)), + test_task); + if (tid >= 0) { + universalisos::uart::puts("[OK] PikeOS-style scheduler task create\r\n"); + } + + /* Timer smoke test */ + uint64_t now = riscv_timer_get_time(); + riscv_timer_set(now + 1000000ULL); + universalisos::uart::puts("[OK] SBI timer armed\r\n"); + + riscv_print_exception_stats(); + + universalisos::uart::puts("\r\nHalting.\r\n"); + while (1) { + __asm__ volatile("wfi"); + } +} diff --git a/kernel/src/arch/riscv/linker.ld b/kernel/src/arch/riscv/linker.ld new file mode 100644 index 000000000..172cffecf --- /dev/null +++ b/kernel/src/arch/riscv/linker.ld @@ -0,0 +1,54 @@ +/* + * Universalisos RISC-V 64-bit Linker Script + * Targets QEMU virt platform, loaded by OpenSBI at 0x80200000 + */ + +OUTPUT_ARCH(riscv) +ENTRY(_start) + +SECTIONS +{ + . = 0x80200000; + + .text : { + _text_start = .; + *(.text.boot) + *(.text .text.*) + _text_end = .; + } + + .rodata : { + _rodata_start = .; + *(.rodata .rodata.*) + _rodata_end = .; + } + + .data : { + _data_start = .; + *(.data .data.*) + _data_end = .; + } + + .uos_drv : { + __uos_drv_start = .; + KEEP(*(.uos_drv)) + __uos_drv_end = .; + } + + .bss : { + . = ALIGN(4096); + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + . = ALIGN(4096); + _bss_end = .; + } + + . = ALIGN(4096); + _end = .; + + /* Main hypervisor stack immediately after image */ + _stack_bottom = ALIGN(16); + . = _stack_bottom + 32768; + _stack_top = ALIGN(16); +} diff --git a/kernel/src/arch/riscv/linker_polarfire.ld b/kernel/src/arch/riscv/linker_polarfire.ld new file mode 100644 index 000000000..3f6fce99f --- /dev/null +++ b/kernel/src/arch/riscv/linker_polarfire.ld @@ -0,0 +1,54 @@ +/* + * Universalisos RISC-V 64-bit Linker Script + * Targets QEMU virt platform, loaded by OpenSBI at 0x80200000 + */ + +OUTPUT_ARCH(riscv) +ENTRY(_start) + +SECTIONS +{ + . = 0x80000000; + + .text : { + _text_start = .; + *(.text.boot) + *(.text .text.*) + _text_end = .; + } + + .rodata : { + _rodata_start = .; + *(.rodata .rodata.*) + _rodata_end = .; + } + + .data : { + _data_start = .; + *(.data .data.*) + _data_end = .; + } + + .uos_drv : { + __uos_drv_start = .; + KEEP(*(.uos_drv)) + __uos_drv_end = .; + } + + .bss : { + . = ALIGN(4096); + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + . = ALIGN(4096); + _bss_end = .; + } + + . = ALIGN(4096); + _end = .; + + /* Main hypervisor stack immediately after image */ + _stack_bottom = ALIGN(16); + . = _stack_bottom + 32768; + _stack_top = ALIGN(16); +} diff --git a/kernel/src/arch/riscv/mem_pikeos.cpp b/kernel/src/arch/riscv/mem_pikeos.cpp new file mode 100644 index 000000000..aef0136af --- /dev/null +++ b/kernel/src/arch/riscv/mem_pikeos.cpp @@ -0,0 +1,139 @@ +/* + * UniversalisOS RISC-V PikeOS-Style Memory Mapping Implementation + */ + +#include "mem_pikeos.h" +#include "page_table.h" +#include "universalisos/baremetal.h" + +extern "C" { + +static uint64_t flags_to_pte(uint32_t flags) +{ + uint64_t pte = PTE_A | PTE_D; + if (flags & RISCV_M_READ) pte |= PTE_R; + if (flags & RISCV_M_WRITE) pte |= PTE_W; + if (flags & RISCV_M_EXEC) pte |= PTE_X; + if (flags & RISCV_M_IO) pte |= PTE_PBMT_IO; + if ((flags & RISCV_M_C_ENABLE) == 0) pte |= PTE_PBMT_NC; + return pte; +} + +static uint64_t pte_to_flags(uint64_t pte) +{ + uint64_t flags = 0; + if (pte & PTE_R) flags |= RISCV_M_READ; + if (pte & PTE_W) flags |= RISCV_M_WRITE; + if (pte & PTE_X) flags |= RISCV_M_EXEC; + if (pte & PTE_PBMT_IO) flags |= RISCV_M_IO; + if (pte & PTE_PBMT_NC) flags |= RISCV_M_UNCACHEABLE; + return flags; +} + +int riscv_aspace_create(uint32_t asid, uint64_t va_start, uint64_t va_size, + riscv_aspace_t* out) +{ + if (!out) return -1; + paddr_t root = riscv_pt_alloc_frame(); + if (!root) return -1; + + out->asid = asid; + out->root_pt = root; + out->va_start = va_start; + out->va_size = va_size; + return 0; +} + +int riscv_aspace_destroy(riscv_aspace_t* as) +{ + if (!as) return -1; + /* Freeing the root page table also leaks intermediate tables in this + * simple implementation. A production version must walk and free all + * levels. */ + riscv_pt_free_frame(as->root_pt); + as->root_pt = 0; + return 0; +} + +int riscv_aspace_map(riscv_aspace_t* as, vaddr_t va, paddr_t pa, + uint64_t size, uint32_t flags) +{ + if (!as || !as->root_pt || size == 0) return -1; + if (va < as->va_start || (va + size) > (as->va_start + as->va_size)) + return -1; + + uint64_t pte_flags = flags_to_pte(flags); + if ((pte_flags & (PTE_R | PTE_W | PTE_X)) == 0) + pte_flags |= PTE_R; /* PikeOS: readable if no perms specified */ + + vaddr_t end = va + size; + for (vaddr_t v = va & ~PAGE_MASK, p = pa & ~PAGE_MASK; + v < end; + v += PAGE_SIZE, p += PAGE_SIZE) { + if (riscv_pt_map_page(as->root_pt, v, p, pte_flags, false) != 0) + return -1; + } + riscv_aspace_flush(as); + return 0; +} + +int riscv_aspace_unmap(riscv_aspace_t* as, vaddr_t va, uint64_t size) +{ + if (!as || !as->root_pt || size == 0) return -1; + vaddr_t end = va + size; + for (vaddr_t v = va & ~PAGE_MASK; v < end; v += PAGE_SIZE) { + riscv_pt_unmap_page(as->root_pt, v, false); + } + riscv_aspace_flush(as); + return 0; +} + +int riscv_aspace_update(riscv_aspace_t* as, vaddr_t va, uint64_t size, + uint32_t flags) +{ + if (!as || !as->root_pt || size == 0) return -1; + if ((flags & RISCV_M_UPDATE) == 0) return 0; + + vaddr_t end = va + size; + for (vaddr_t v = va & ~PAGE_MASK; v < end; v += PAGE_SIZE) { + pte_t* pte = riscv_pt_walk(as->root_pt, v, false); + if (!pte || !pte_valid(*pte)) continue; + paddr_t pa = pte_addr(*pte); + uint64_t new_flags = flags_to_pte(flags) | (pte_to_flags(*pte) & ~RISCV_M_PERM_MASK); + *pte = pte_make(pa, new_flags); + } + riscv_aspace_flush(as); + return 0; +} + +int riscv_aspace_unmap_all(riscv_aspace_t* as) +{ + if (!as || !as->root_pt) return -1; + /* Clear root page table (leaks intermediate tables). */ + universalisos::baremetal::memset((void*)as->root_pt, 0, PAGE_SIZE); + riscv_aspace_flush(as); + return 0; +} + +paddr_t riscv_aspace_translate(riscv_aspace_t* as, vaddr_t va) +{ + pte_t* pte = riscv_aspace_lookup(as, va); + if (!pte || !pte_valid(*pte)) return 0; + return pte_addr(*pte) | (va & PAGE_MASK); +} + +pte_t* riscv_aspace_lookup(riscv_aspace_t* as, vaddr_t va) +{ + if (!as || !as->root_pt) return nullptr; + return riscv_pt_walk(as->root_pt, va, false); +} + +void riscv_aspace_flush(riscv_aspace_t* as) +{ + if (as && as->asid) + riscv_tlb_inval_all(); /* TODO: per-ASID flush using as->asid */ + else + riscv_tlb_inval_all(); +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/page_table.cpp b/kernel/src/arch/riscv/page_table.cpp new file mode 100644 index 000000000..1d9e8926a --- /dev/null +++ b/kernel/src/arch/riscv/page_table.cpp @@ -0,0 +1,103 @@ +/* + * UniversalisOS RISC-V Sv39 Page Table Implementation + * + * Reference: Bao src/arch/riscv/page_table.c, seL4 src/arch/riscv/kernel/vspace.c + */ + +#include "page_table.h" +#include "csr.h" +#include "universalisos/baremetal.h" + +#define PT_POOL_PAGES 256 +static uint8_t pt_pool[PT_POOL_PAGES * PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); +static bool pt_used[PT_POOL_PAGES]; + +extern "C" { + +paddr_t riscv_pt_alloc_frame(void) +{ + for (size_t i = 0; i < PT_POOL_PAGES; ++i) { + if (!pt_used[i]) { + pt_used[i] = true; + paddr_t frame = (paddr_t)&pt_pool[i * PAGE_SIZE]; + universalisos::baremetal::memset((void*)frame, 0, PAGE_SIZE); + return frame; + } + } + return 0; +} + +void riscv_pt_free_frame(paddr_t frame) +{ + if (frame < (paddr_t)pt_pool || frame >= (paddr_t)(pt_pool + sizeof(pt_pool))) + return; + size_t i = (frame - (paddr_t)pt_pool) / PAGE_SIZE; + if (i < PT_POOL_PAGES) + pt_used[i] = false; +} + +static inline uint64_t vpn_level(vaddr_t va, unsigned level) +{ + return (va >> (PAGE_SHIFT + 9 * level)) & VPN_MASK; +} + +pte_t* riscv_pt_walk(paddr_t root_pa, vaddr_t va, bool is_gstage) +{ + (void)is_gstage; /* Sv39x4 uses same VPN layout for this walk */ + pte_t* table = (pte_t*)root_pa; + for (int level = PT_LEVELS - 1; level >= 0; --level) { + uint64_t idx = vpn_level(va, level); + pte_t* pte = &table[idx]; + if (!pte_valid(*pte)) + return nullptr; + if (pte_leaf(*pte)) + return pte; + table = (pte_t*)pte_addr(*pte); + } + return nullptr; +} + +int riscv_pt_map_page(paddr_t root_pa, vaddr_t va, paddr_t pa, uint64_t flags, bool is_gstage) +{ + (void)is_gstage; + pte_t* table = (pte_t*)root_pa; + for (int level = PT_LEVELS - 1; level > 0; --level) { + uint64_t idx = vpn_level(va, level); + pte_t* pte = &table[idx]; + if (!pte_valid(*pte)) { + paddr_t next = riscv_pt_alloc_frame(); + if (!next) + return -1; + *pte = pte_make(next, PTE_TABLE_FLAGS); + } + table = (pte_t*)pte_addr(*pte); + } + uint64_t idx = vpn_level(va, 0); + table[idx] = pte_make(pa, flags); + return 0; +} + +int riscv_pt_unmap_page(paddr_t root_pa, vaddr_t va, bool is_gstage) +{ + pte_t* pte = riscv_pt_walk(root_pa, va, is_gstage); + if (!pte) + return -1; + *pte = 0; + return 0; +} + +void riscv_tlb_inval_va(vaddr_t va, uint64_t asid) +{ + if (asid) { + __asm__ volatile("sfence.vma %0, %1" :: "r"(va), "r"(asid)); + } else { + __asm__ volatile("sfence.vma %0, zero" :: "r"(va)); + } +} + +void riscv_tlb_inval_all(void) +{ + __asm__ volatile("sfence.vma zero, zero"); +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/plic.cpp b/kernel/src/arch/riscv/plic.cpp new file mode 100644 index 000000000..2add0a664 --- /dev/null +++ b/kernel/src/arch/riscv/plic.cpp @@ -0,0 +1,100 @@ +/* + * UniversalisOS RISC-V PLIC Driver + */ + +#include "plic.h" +#include "uart.h" + +#define REG32(addr) (*(volatile uint32_t*)(addr)) + +/* QEMU virt PLIC layout */ +#define PLIC_PRIORITY_BASE (PLIC_BASE + 0x000000) +#define PLIC_PENDING_BASE (PLIC_BASE + 0x001000) +#define PLIC_ENABLE_BASE (PLIC_BASE + 0x002000) +#define PLIC_ENABLE_STRIDE 0x80 +#define PLIC_CONTEXT_BASE (PLIC_BASE + 0x200000) +#define PLIC_CONTEXT_STRIDE 0x1000 +#define PLIC_THRESHOLD_OFF 0x00 +#define PLIC_CLAIM_OFF 0x04 + +static inline volatile uint32_t* plic_priority(unsigned int irq) { + return (volatile uint32_t*)(PLIC_PRIORITY_BASE + irq * 4); +} + +static inline volatile uint32_t* plic_enable(unsigned int context, unsigned int irq) { + return (volatile uint32_t*)(PLIC_ENABLE_BASE + context * PLIC_ENABLE_STRIDE + (irq / 32) * 4); +} + +static inline volatile uint32_t* plic_threshold(unsigned int context) { + return (volatile uint32_t*)(PLIC_CONTEXT_BASE + context * PLIC_CONTEXT_STRIDE + PLIC_THRESHOLD_OFF); +} + +static inline volatile uint32_t* plic_claim(unsigned int context) { + return (volatile uint32_t*)(PLIC_CONTEXT_BASE + context * PLIC_CONTEXT_STRIDE + PLIC_CLAIM_OFF); +} + +extern "C" { + +void riscv_plic_init(void) { + /* Set all priorities to 0 */ + for (unsigned int i = 0; i <= PLIC_MAX_IRQ; ++i) { + *plic_priority(i) = 0; + } + /* Disable all interrupts for all contexts */ + for (unsigned int ctx = 0; ctx < PLIC_NUM_CONTEXTS; ++ctx) { + for (unsigned int word = 0; word <= PLIC_MAX_IRQ / 32; ++word) { + *plic_enable(ctx, word * 32) = 0; + } + *plic_threshold(ctx) = 7; /* threshold = 7 masks all but priority > 7 */ + } +} + +void riscv_plic_enable(unsigned int context, unsigned int irq) { + if (irq > PLIC_MAX_IRQ || context >= PLIC_NUM_CONTEXTS) return; + *plic_enable(context, irq) |= (1U << (irq % 32)); +} + +void riscv_plic_disable(unsigned int context, unsigned int irq) { + if (irq > PLIC_MAX_IRQ || context >= PLIC_NUM_CONTEXTS) return; + *plic_enable(context, irq) &= ~(1U << (irq % 32)); +} + +void riscv_plic_set_priority(unsigned int irq, unsigned int priority) { + if (irq > PLIC_MAX_IRQ) return; + *plic_priority(irq) = priority & 7; +} + +void riscv_plic_set_threshold(unsigned int context, unsigned int threshold) { + if (context >= PLIC_NUM_CONTEXTS) return; + *plic_threshold(context) = threshold & 7; +} + +unsigned int riscv_plic_claim(unsigned int context) { + if (context >= PLIC_NUM_CONTEXTS) return 0; + return *plic_claim(context); +} + +void riscv_plic_complete(unsigned int context, unsigned int irq) { + if (context >= PLIC_NUM_CONTEXTS) return; + *plic_claim(context) = irq; +} + +void riscv_plic_handle_interrupt(void) { + unsigned int irq = riscv_plic_claim(0); + if (irq == 0) return; + + /* Acknowledge UART interrupt depending on the platform */ +#ifdef PLATFORM_POLARFIRE + if (irq == 90 || irq == 91) { + universalisos::uart::puts("[PLIC] PolarFire MMUART interrupt\r\n"); + } +#else + if (irq == 10) { + universalisos::uart::puts("[PLIC] QEMU UART interrupt\r\n"); + } +#endif + + riscv_plic_complete(0, irq); +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/sbi.cpp b/kernel/src/arch/riscv/sbi.cpp new file mode 100644 index 000000000..804692c98 --- /dev/null +++ b/kernel/src/arch/riscv/sbi.cpp @@ -0,0 +1,32 @@ +/* + * UniversalisOS RISC-V SBI ecall implementation + */ + +#include "sbi.h" + +extern "C" { + +struct sbiret sbi_ecall(long eid, long fid, long arg0, long arg1, + long arg2, long arg3, long arg4, long arg5) +{ + struct sbiret ret; + register long a0 asm("a0") = arg0; + register long a1 asm("a1") = arg1; + register long a2 asm("a2") = arg2; + register long a3 asm("a3") = arg3; + register long a4 asm("a4") = arg4; + register long a5 asm("a5") = arg5; + register long a6 asm("a6") = fid; + register long a7 asm("a7") = eid; + + __asm__ volatile("ecall" + : "+r"(a0), "+r"(a1) + : "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7) + : "memory"); + + ret.error = a0; + ret.value = a1; + return ret; +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/sched_pikeos.cpp b/kernel/src/arch/riscv/sched_pikeos.cpp new file mode 100644 index 000000000..bcf616101 --- /dev/null +++ b/kernel/src/arch/riscv/sched_pikeos.cpp @@ -0,0 +1,295 @@ +/* + * UniversalisOS RISC-V PikeOS-Style Scheduler Implementation + */ + +#include "sched_pikeos.h" +#include "context_switch.h" +#include "timer.h" +#include "csr.h" +#include "uart.h" +#include "universalisos/baremetal.h" + +riscv_sched_state_t riscv_sched_state; + +extern "C" { + +static void riscv_idle_task(void) +{ + while (1) { + __asm__ volatile("wfi"); + } +} + +void riscv_sched_init(uint64_t tick_interval_us) +{ + universalisos::baremetal::memset(&riscv_sched_state, 0, sizeof(riscv_sched_state)); + riscv_sched_state.tick_interval_us = tick_interval_us; + riscv_sched_state.monotonic_time_us = 0; + riscv_sched_state.initialized = true; + + /* Create the idle task (lowest priority) on the bootstrap stack. + * A real system would allocate a dedicated idle stack. */ + static uint8_t idle_stack[4096] __attribute__((aligned(16))); + riscv_sched_state.cpus[0].idle_task = &riscv_sched_state.tasks[0]; + riscv_task_t* idle = riscv_sched_state.cpus[0].idle_task; + idle->task_id = 0; + idle->partition_id = 0; + idle->timepart_id = 0; + idle->priority = RISCV_SCHED_PRIO_IDLE; + idle->base_priority = RISCV_SCHED_PRIO_IDLE; + idle->state = RISCV_TASK_READY; + idle->stack_top = (void*)((uintptr_t)idle_stack + sizeof(idle_stack)); + idle->entry = riscv_idle_task; + riscv_sched_state.num_tasks = 1; + + riscv_sched_add_ready(idle); +} + +int riscv_sched_schema_create(uint32_t schema_id, uint32_t major_frame_us) +{ + if (schema_id >= RISCV_SCHED_MAX_TIMEPART) return -1; + riscv_timepart_schema_t* schema = &riscv_sched_state.schemas[schema_id]; + schema->schema_id = schema_id; + schema->major_frame_us = major_frame_us; + schema->num_windows = 0; + schema->current_window = 0; + schema->window_start_us = 0; + if (schema_id >= riscv_sched_state.num_schemas) + riscv_sched_state.num_schemas = schema_id + 1; + return 0; +} + +int riscv_sched_window_add(uint32_t schema_id, uint32_t timepart_id, + uint32_t duration_us, uint32_t min_duration_us, + uint16_t flags, uint16_t userdata, uint32_t window_id) +{ + if (schema_id >= riscv_sched_state.num_schemas) return -1; + riscv_timepart_schema_t* schema = &riscv_sched_state.schemas[schema_id]; + if (schema->num_windows >= RISCV_SCHED_MAX_WINDOWS) return -1; + + riscv_window_t* w = &schema->windows[schema->num_windows++]; + w->timepart_id = timepart_id; + w->duration_us = duration_us; + w->min_duration_us = min_duration_us; + w->flags = flags; + w->userdata = userdata; + w->window_id = window_id; + return 0; +} + +int riscv_sched_task_create(uint32_t partition_id, uint32_t timepart_id, + uos_priority_t priority, uint64_t period_us, + uint64_t deadline_us, uint64_t time_slice_us, + void* stack_top, void (*entry)(void)) +{ + if (priority > RISCV_SCHED_PRIO_MAX) return -1; + if (riscv_sched_state.num_tasks >= RISCV_SCHED_MAX_TASKS) return -1; + + riscv_task_t* task = &riscv_sched_state.tasks[riscv_sched_state.num_tasks++]; + universalisos::baremetal::memset(task, 0, sizeof(*task)); + task->task_id = riscv_sched_state.num_tasks - 1; + task->partition_id = partition_id; + task->timepart_id = timepart_id; + task->priority = priority; + task->base_priority = priority; + task->state = RISCV_TASK_READY; + task->period_us = period_us; + task->deadline_us = deadline_us; + task->time_slice_us = time_slice_us; + task->stack_top = stack_top; + task->entry = entry; + task->next_release_us = 0; + + riscv_sched_add_ready(task); + return (int)task->task_id; +} + +void riscv_sched_add_ready(riscv_task_t* task) +{ + if (task->state == RISCV_TASK_READY) return; + task->state = RISCV_TASK_READY; + + riscv_ready_queue_t* q = &riscv_sched_state.cpus[0].readyq[task->priority]; + task->next = nullptr; + if (!q->head) { + q->head = q->tail = task; + } else { + q->tail->next = task; + q->tail = task; + } +} + +void riscv_sched_remove_ready(riscv_task_t* task) +{ + riscv_ready_queue_t* q = &riscv_sched_state.cpus[0].readyq[task->priority]; + riscv_task_t* prev = nullptr; + riscv_task_t* cur = q->head; + while (cur) { + if (cur == task) { + if (prev) prev->next = cur->next; + else q->head = cur->next; + if (q->tail == cur) q->tail = prev; + task->next = nullptr; + return; + } + prev = cur; + cur = cur->next; + } +} + +riscv_task_t* riscv_sched_pick_next(void) +{ + riscv_sched_cpu_t* cpu = &riscv_sched_state.cpus[0]; + uint32_t current_tp = cpu->current_timepart; + + /* Highest-priority ready task that belongs to the current time partition + * (or to the idle partition 0). */ + for (int p = RISCV_SCHED_PRIO_MAX; p >= RISCV_SCHED_PRIO_IDLE; --p) { + riscv_ready_queue_t* q = &cpu->readyq[p]; + riscv_task_t* cur = q->head; + while (cur) { + if (cur->timepart_id == current_tp || cur->timepart_id == 0) + return cur; + cur = cur->next; + } + } + return cpu->idle_task; +} + +void riscv_sched_tick(uint64_t now_us) +{ + if (!riscv_sched_state.initialized) return; + + riscv_sched_state.monotonic_time_us = now_us; + riscv_sched_cpu_t* cpu = &riscv_sched_state.cpus[0]; + riscv_timepart_schema_t* schema = &riscv_sched_state.schemas[cpu->current_schema]; + + /* Time-partition window switch */ + if (schema->num_windows > 0) { + uint64_t elapsed = now_us - schema->window_start_us; + riscv_window_t* w = &schema->windows[schema->current_window]; + if (elapsed >= w->duration_us) { + schema->current_window++; + if (schema->current_window >= schema->num_windows) { + schema->current_window = 0; + cpu->major_frame_start_us = now_us; + } + schema->window_start_us = now_us; + w = &schema->windows[schema->current_window]; + cpu->current_timepart = w->timepart_id; + } + } + + /* Wake sleeping tasks whose timeout expired */ + for (uint32_t i = 1; i < riscv_sched_state.num_tasks; ++i) { + riscv_task_t* t = &riscv_sched_state.tasks[i]; + if (t->state == RISCV_TASK_SLEEPING && now_us >= t->next_release_us) { + t->state = RISCV_TASK_READY; + t->time_used_us = 0; + riscv_sched_add_ready(t); + } + } + + riscv_sched_reschedule(); +} + +void riscv_sched_reschedule(void) +{ + riscv_sched_cpu_t* cpu = &riscv_sched_state.cpus[0]; + riscv_task_t* next = riscv_sched_pick_next(); + riscv_task_t* prev = cpu->current_task; + + if (next == prev) return; + if (prev) { + if (prev->state == RISCV_TASK_RUNNING) { + prev->state = RISCV_TASK_READY; + riscv_sched_add_ready(prev); + } + } + cpu->current_task = next; + next->state = RISCV_TASK_RUNNING; + riscv_sched_remove_ready(next); + + /* First switch ever: just restore next context (no prev to save). */ + if (!prev) { + static riscv_context_t dummy; + riscv_context_switch(&cpu->current_task->_ctx, &dummy); + return; + } + + riscv_sched_context_switch(prev, next); +} + +void riscv_sched_context_switch(riscv_task_t* prev, riscv_task_t* next) +{ + if (!prev || !next) return; + + static riscv_context_t ctx[RISCV_SCHED_MAX_TASKS]; + static bool ctx_inited[RISCV_SCHED_MAX_TASKS]; + + if (!ctx_inited[next->task_id]) { + riscv_context_init(&ctx[next->task_id], (void*)next->entry, next->stack_top); + ctx_inited[next->task_id] = true; + } + + /* The real switch is architecture-specific; here we use the generic + * RISC-V context switch. In a real implementation this must also + * save/restore FPU and CSRs. */ + riscv_context_switch(&ctx[next->task_id], &ctx[prev->task_id]); +} + +void riscv_sched_task_yield(void) +{ + riscv_sched_cpu_t* cpu = &riscv_sched_state.cpus[0]; + riscv_task_t* cur = cpu->current_task; + if (cur && cur->task_id != 0) { + cur->state = RISCV_TASK_READY; + riscv_sched_add_ready(cur); + } + riscv_sched_reschedule(); +} + +void riscv_sched_task_block(uint32_t task_id) +{ + if (task_id >= riscv_sched_state.num_tasks) return; + riscv_task_t* t = &riscv_sched_state.tasks[task_id]; + if (t->state == RISCV_TASK_READY) riscv_sched_remove_ready(t); + t->state = RISCV_TASK_BLOCKED; + if (riscv_sched_state.cpus[0].current_task == t) + riscv_sched_reschedule(); +} + +void riscv_sched_task_unblock(uint32_t task_id) +{ + if (task_id >= riscv_sched_state.num_tasks) return; + riscv_task_t* t = &riscv_sched_state.tasks[task_id]; + if (t->state == RISCV_TASK_BLOCKED || t->state == RISCV_TASK_SLEEPING) { + riscv_sched_add_ready(t); + riscv_sched_reschedule(); + } +} + +void riscv_sched_task_sleep(uint32_t task_id, uint64_t us) +{ + if (task_id >= riscv_sched_state.num_tasks) return; + riscv_task_t* t = &riscv_sched_state.tasks[task_id]; + if (t->state == RISCV_TASK_READY) riscv_sched_remove_ready(t); + t->state = RISCV_TASK_SLEEPING; + t->next_release_us = riscv_sched_state.monotonic_time_us + us; + if (riscv_sched_state.cpus[0].current_task == t) + riscv_sched_reschedule(); +} + +void riscv_sched_start(void) +{ + riscv_sched_state.cpus[0].major_frame_start_us = riscv_sched_state.monotonic_time_us; + riscv_sched_state.cpus[0].current_schema = 0; + riscv_sched_state.cpus[0].current_timepart = + riscv_sched_state.schemas[0].num_windows > 0 + ? riscv_sched_state.schemas[0].windows[0].timepart_id + : 0; + riscv_sched_state.schemas[0].window_start_us = riscv_sched_state.monotonic_time_us; + riscv_sched_reschedule(); +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/timer.cpp b/kernel/src/arch/riscv/timer.cpp new file mode 100644 index 000000000..e966f444b --- /dev/null +++ b/kernel/src/arch/riscv/timer.cpp @@ -0,0 +1,40 @@ +/* + * UniversalisOS RISC-V Timer Implementation + * + * Uses the SBI TIME extension. If the Sstc extension is available, + * this can be replaced with direct stimecmp access. + */ + +#include "timer.h" +#include "sbi.h" +#include "csr.h" + +extern "C" { + +static uint64_t timebase_frequency = 10000000ULL; /* QEMU virt default 10 MHz */ + +void riscv_timer_init(void) { + /* The timer frequency is normally extracted from the device tree. + * For the QEMU virt platform we use the well-known 10 MHz value. */ + timebase_frequency = 10000000ULL; + + /* Disable S-mode timer interrupt until explicitly armed */ + csr_clear(CSR_SIE, (1ULL << IRQ_S_TIMER)); +} + +uint64_t riscv_timer_get_time(void) { + uint64_t time; + __asm__ volatile("rdtime %0" : "=r"(time)); + return time; +} + +void riscv_timer_set(uint64_t ticks) { + sbi_set_timer(ticks); + csr_set(CSR_SIE, (1ULL << IRQ_S_TIMER)); +} + +void riscv_timer_clear(void) { + csr_clear(CSR_SIE, (1ULL << IRQ_S_TIMER)); +} + +} /* extern "C" */ diff --git a/kernel/src/arch/riscv/trap.S b/kernel/src/arch/riscv/trap.S new file mode 100644 index 000000000..511ccc2a9 --- /dev/null +++ b/kernel/src/arch/riscv/trap.S @@ -0,0 +1,135 @@ +/* + * UniversalisOS RISC-V HS-mode Trap Vector + * + * Saves full integer register context and key hypervisor CSRs, then + * calls riscv_trap_handler_c(scause, stval, sepc, sstatus, frame). + * + * sscratch holds a pointer to the current trap frame (set before entering + * a guest, or zero in the hypervisor's own code). The entry sequence + * atomically swaps t6 with sscratch, saves the guest's original t6 into + * the frame, restores sscratch to the frame pointer, and then saves the + * remaining registers. + * + * Reference: Bao src/arch/riscv/exceptions.S, seL4 src/arch/riscv/traps.S + */ + +.section .text +.global riscv_trap_vector +.align 4 + +riscv_trap_vector: + /* Swap t6 with sscratch. After this: + * t6 = trap frame pointer (old sscratch) + * sscratch = guest's original t6 */ + csrrw t6, sscratch, t6 + + /* Preserve frame pointer in t1, allocate stack frame */ + mv t1, t6 + addi sp, sp, -320 + + /* Save guest's original t6 (currently in sscratch) into frame slot 31 */ + csrr t6, sscratch + sd t6, 240(sp) + + /* Restore sscratch to the frame pointer for the next trap */ + csrw sscratch, t1 + + /* Save general purpose registers x1-x30 */ + sd x1, 0(sp) + sd x2, 8(sp) + sd x3, 16(sp) + sd x4, 24(sp) + sd x5, 32(sp) + sd x6, 40(sp) + sd x7, 48(sp) + sd x8, 56(sp) + sd x9, 64(sp) + sd x10, 72(sp) + sd x11, 80(sp) + sd x12, 88(sp) + sd x13, 96(sp) + sd x14, 104(sp) + sd x15, 112(sp) + sd x16, 120(sp) + sd x17, 128(sp) + sd x18, 136(sp) + sd x19, 144(sp) + sd x20, 152(sp) + sd x21, 160(sp) + sd x22, 168(sp) + sd x23, 176(sp) + sd x24, 184(sp) + sd x25, 192(sp) + sd x26, 200(sp) + sd x27, 208(sp) + sd x28, 216(sp) + sd x29, 224(sp) + sd x30, 232(sp) + + /* Save trap CSRs */ + csrr t0, sepc + sd t0, 248(sp) + csrr t0, sstatus + sd t0, 256(sp) + csrr t0, scause + sd t0, 264(sp) + csrr t0, stval + sd t0, 272(sp) + csrr t0, hstatus + sd t0, 280(sp) + csrr t0, scounteren + sd t0, 288(sp) + + /* Arguments for C handler */ + mv a0, t0 /* a0 = scause (from last csrr) */ + csrr a0, scause /* reload cleanly */ + csrr a1, stval + csrr a2, sepc + csrr a3, sstatus + mv a4, sp /* a4 = pointer to trap frame */ + + call riscv_trap_handler_c + + /* Restore trap CSRs that may have been modified */ + ld t0, 248(sp) + csrw sepc, t0 + ld t0, 256(sp) + csrw sstatus, t0 + + /* Restore general registers x1-x30 */ + ld x1, 0(sp) + ld x2, 8(sp) + ld x3, 16(sp) + ld x4, 24(sp) + ld x5, 32(sp) + ld x6, 40(sp) + ld x7, 48(sp) + ld x8, 56(sp) + ld x9, 64(sp) + ld x10, 72(sp) + ld x11, 80(sp) + ld x12, 88(sp) + ld x13, 96(sp) + ld x14, 104(sp) + ld x15, 112(sp) + ld x16, 120(sp) + ld x17, 128(sp) + ld x18, 136(sp) + ld x19, 144(sp) + ld x20, 152(sp) + ld x21, 160(sp) + ld x22, 168(sp) + ld x23, 176(sp) + ld x24, 184(sp) + ld x25, 192(sp) + ld x26, 200(sp) + ld x27, 208(sp) + ld x28, 216(sp) + ld x29, 224(sp) + ld x30, 232(sp) + + /* Restore x31 (guest t6) */ + ld x31, 240(sp) + + addi sp, sp, 320 + sret diff --git a/kernel/src/arch/riscv/uart.cpp b/kernel/src/arch/riscv/uart.cpp new file mode 100644 index 000000000..b1f4337fb --- /dev/null +++ b/kernel/src/arch/riscv/uart.cpp @@ -0,0 +1,79 @@ +/* + * UniversalisOS RISC-V NS16550a UART Driver + * QEMU virt UART0 at 0x10000000 + */ + +#include +#include "uart.h" + +namespace universalisos { +namespace uart { + +#ifdef PLATFORM_POLARFIRE +static constexpr uintptr_t UART_BASE = 0x20000000ULL; /* MMUART0 on PolarFire SoC */ +#define REG_ACCESS(off) (*(volatile uint32_t*)(UART_BASE + ((off) * 4))) +#else +static constexpr uintptr_t UART_BASE = 0x10000000ULL; /* QEMU virt UART0 */ +#define REG_ACCESS(off) (*(volatile uint8_t*)(UART_BASE + (off))) +#endif + +static constexpr uint8_t THR = 0x00; /* Transmit Holding Register */ +static constexpr uint8_t RBR = 0x00; /* Receive Buffer Register */ +static constexpr uint8_t IER = 0x01; /* Interrupt Enable */ +static constexpr uint8_t FCR = 0x02; /* FIFO Control */ +static constexpr uint8_t LCR = 0x03; /* Line Control */ +static constexpr uint8_t LSR = 0x05; /* Line Status */ + +static constexpr uint8_t LSR_THRE = 0x20; /* Transmitter Holding Empty */ + +void init() { + REG_ACCESS(IER) = 0x00; /* Disable interrupts */ + REG_ACCESS(LCR) = 0x80; /* Enable DLAB */ + /* divisor = 3686400 / (16 * 115200) = 2 for QEMU virt clock */ + REG_ACCESS(THR) = 0x02; /* DLL */ + REG_ACCESS(IER) = 0x00; /* DLM */ + REG_ACCESS(LCR) = 0x03; /* 8N1, clear DLAB */ + REG_ACCESS(FCR) = 0x07; /* Enable FIFO, clear, 14-byte threshold */ +} + +void putc(char c) { + while ((REG_ACCESS(LSR) & LSR_THRE) == 0) { + /* wait */ + } + REG_ACCESS(THR) = static_cast(c); +} + +void puts(const char* s) { + while (*s) { + if (*s == '\n') { + putc('\r'); + } + putc(*s++); + } +} + +void print_dec(unsigned int val) { + if (val == 0) { + putc('0'); + return; + } + char buf[10]; + int i = 0; + while (val > 0) { + buf[i++] = '0' + (val % 10); + val /= 10; + } + while (--i >= 0) { + putc(buf[i]); + } +} + +void print_hex(unsigned int val) { + const char* hex_digits = "0123456789ABCDEF"; + for (int i = 28; i >= 0; i -= 4) { + putc(hex_digits[(val >> i) & 0xF]); + } +} + +} // namespace uart +} // namespace universalisos diff --git a/kernel/src/arch/riscv/vm.cpp b/kernel/src/arch/riscv/vm.cpp new file mode 100644 index 000000000..01cedf2a5 --- /dev/null +++ b/kernel/src/arch/riscv/vm.cpp @@ -0,0 +1,132 @@ +/* + * UniversalisOS RISC-V VM / vCPU Implementation + */ + +#include "vm.h" +#include "cpu.h" +#include "csr.h" +#include "page_table.h" +#include "uart.h" +#include "universalisos/baremetal.h" + +riscv_vm_t riscv_vms[RISCV_VM_MAX]; +riscv_vcpu_t riscv_vcpus[RISCV_VCPU_MAX]; +riscv_vcpu_t* riscv_current_vcpu = nullptr; + +extern "C" { + +int riscv_vm_create(uint32_t vm_id, uint64_t mem_base, uint64_t mem_size) { + if (vm_id >= RISCV_VM_MAX) return -1; + riscv_vm_t* vm = &riscv_vms[vm_id]; + if (vm->state != RISCV_VM_STOPPED) return -1; + + paddr_t root = riscv_pt_alloc_frame(); + if (!root) return -1; + + vm->vm_id = vm_id; + vm->state = RISCV_VM_STOPPED; + vm->gstage_root = root; + vm->vmid = vm_id + 1; + vm->entry_point = 0; + vm->dtb_phys = 0; + vm->mem_base = mem_base; + vm->mem_size = mem_size; + + /* Identity-map guest RAM 1:1 as a starting point */ + for (uint64_t off = 0; off < mem_size; off += PAGE_SIZE) { + riscv_pt_map_page(root, mem_base + off, mem_base + off, + PTE_PAGE_FLAGS, true); + } + + return 0; +} + +int riscv_vm_destroy(uint32_t vm_id) { + if (vm_id >= RISCV_VM_MAX) return -1; + riscv_vm_t* vm = &riscv_vms[vm_id]; + vm->state = RISCV_VM_STOPPED; + riscv_pt_free_frame(vm->gstage_root); + vm->gstage_root = 0; + return 0; +} + +int riscv_vcpu_create(uint32_t vm_id, uint32_t vcpu_no, uint32_t phys_hart) { + if (vm_id >= RISCV_VM_MAX) return -1; + for (uint32_t i = 0; i < RISCV_VCPU_MAX; ++i) { + if (!riscv_vcpus[i].active) { + riscv_vcpu_t* vcpu = &riscv_vcpus[i]; + universalisos::baremetal::memset(vcpu, 0, sizeof(*vcpu)); + vcpu->vcpu_id = i; + vcpu->vm_id = vm_id; + vcpu->vcpu_no = vcpu_no; + vcpu->phys_hart = phys_hart; + vcpu->active = true; + return (int)i; + } + } + return -1; +} + +int riscv_vcpu_reset(uint32_t vcpu_id, uint64_t entry, uint64_t dtb) { + if (vcpu_id >= RISCV_VCPU_MAX) return -1; + riscv_vcpu_t* vcpu = &riscv_vcpus[vcpu_id]; + if (!vcpu->active) return -1; + riscv_vm_t* vm = &riscv_vms[vcpu->vm_id]; + + universalisos::baremetal::memset(&vcpu->regs, 0, sizeof(vcpu->regs)); + vcpu->regs.sepc = entry; + vcpu->regs.sstatus = SSTATUS_SPIE | SSTATUS_SPP; /* VS-mode, interrupts enabled on sret */ + vcpu->regs.a0 = vcpu->vcpu_no; /* hart id */ + vcpu->regs.a1 = dtb; /* dtb physical address */ + + vm->entry_point = entry; + vm->dtb_phys = dtb; + return 0; +} + +/* Implemented in context_switch.S */ +extern void riscv_vcpu_entry(riscv_trap_frame_t* regs); + +void riscv_vcpu_run(uint32_t vcpu_id) { + if (vcpu_id >= RISCV_VCPU_MAX) return; + riscv_vcpu_t* vcpu = &riscv_vcpus[vcpu_id]; + if (!vcpu->active) return; + riscv_vm_t* vm = &riscv_vms[vcpu->vm_id]; + + riscv_current_vcpu = vcpu; + vm->state = RISCV_VM_RUNNING; + + /* Program hgatp with guest root and VMID */ + uint64_t hgatp = (vm->gstage_root >> PAGE_SHIFT) | + (HGATP_MODE_SV39X4 << HGATP_MODE_SHIFT) | + ((uint64_t)vm->vmid << HGATP_VMID_SHIFT); + csr_write(CSR_HGATP, hgatp); + __asm__ volatile("sfence.vma zero, zero"); + + riscv_vcpu_entry(&vcpu->regs); + + vm->state = RISCV_VM_PAUSED; + riscv_current_vcpu = nullptr; +} + +void riscv_vm_handle_guest_page_fault(riscv_trap_frame_t* frame, uint64_t gpa, bool inst) { + (void)inst; + universalisos::uart::puts("GUEST PAGE FAULT at GPA: 0x"); + universalisos::uart::print_hex((uint32_t)(gpa >> 32)); + universalisos::uart::print_hex((uint32_t)gpa); + universalisos::uart::puts(" PC: 0x"); + universalisos::uart::print_hex((uint32_t)(frame->sepc >> 32)); + universalisos::uart::print_hex((uint32_t)frame->sepc); + universalisos::uart::puts("\r\n"); + /* TODO: MMIO emulation or page allocation */ +} + +void riscv_vm_handle_virtual_instruction(riscv_trap_frame_t* frame) { + universalisos::uart::puts("VIRTUAL INSTRUCTION at PC: 0x"); + universalisos::uart::print_hex((uint32_t)(frame->sepc >> 32)); + universalisos::uart::print_hex((uint32_t)frame->sepc); + universalisos::uart::puts("\r\n"); + /* TODO: emulate CSR instructions */ +} + +} /* extern "C" */ diff --git a/kernel/kernel.cpp b/kernel/src/core/kernel.cpp similarity index 100% rename from kernel/kernel.cpp rename to kernel/src/core/kernel.cpp diff --git a/kernel/src/uos_api.cpp b/kernel/src/core/uos_api.cpp similarity index 100% rename from kernel/src/uos_api.cpp rename to kernel/src/core/uos_api.cpp diff --git a/kernel/src/uos_drivers.cpp b/kernel/src/core/uos_drivers.cpp similarity index 100% rename from kernel/src/uos_drivers.cpp rename to kernel/src/core/uos_drivers.cpp diff --git a/kernel/src/uos_syscalls.cpp b/kernel/src/core/uos_syscalls.cpp similarity index 100% rename from kernel/src/uos_syscalls.cpp rename to kernel/src/core/uos_syscalls.cpp diff --git a/kernel/src/usp.cpp b/kernel/src/core/usp.cpp similarity index 100% rename from kernel/src/usp.cpp rename to kernel/src/core/usp.cpp diff --git a/kernel/src/usp_arm.cpp b/kernel/src/core/usp_arm.cpp similarity index 100% rename from kernel/src/usp_arm.cpp rename to kernel/src/core/usp_arm.cpp diff --git a/kernel/src/usp_board.cpp b/kernel/src/core/usp_board.cpp similarity index 100% rename from kernel/src/usp_board.cpp rename to kernel/src/core/usp_board.cpp diff --git a/kernel/drivers/block.cpp b/kernel/src/platform/drivers/block.cpp similarity index 100% rename from kernel/drivers/block.cpp rename to kernel/src/platform/drivers/block.cpp diff --git a/kernel/drivers/block.h b/kernel/src/platform/drivers/block.h similarity index 100% rename from kernel/drivers/block.h rename to kernel/src/platform/drivers/block.h diff --git a/kernel/drivers/driver.cpp b/kernel/src/platform/drivers/driver.cpp similarity index 100% rename from kernel/drivers/driver.cpp rename to kernel/src/platform/drivers/driver.cpp diff --git a/kernel/drivers/driver.h b/kernel/src/platform/drivers/driver.h similarity index 100% rename from kernel/drivers/driver.h rename to kernel/src/platform/drivers/driver.h diff --git a/kernel/drivers/font_8x16.cpp b/kernel/src/platform/drivers/font_8x16.cpp similarity index 100% rename from kernel/drivers/font_8x16.cpp rename to kernel/src/platform/drivers/font_8x16.cpp diff --git a/kernel/drivers/gpio.cpp b/kernel/src/platform/drivers/gpio.cpp similarity index 100% rename from kernel/drivers/gpio.cpp rename to kernel/src/platform/drivers/gpio.cpp diff --git a/kernel/drivers/gpio.h b/kernel/src/platform/drivers/gpio.h similarity index 100% rename from kernel/drivers/gpio.h rename to kernel/src/platform/drivers/gpio.h diff --git a/kernel/drivers/i2c.cpp b/kernel/src/platform/drivers/i2c.cpp similarity index 100% rename from kernel/drivers/i2c.cpp rename to kernel/src/platform/drivers/i2c.cpp diff --git a/kernel/drivers/i2c.h b/kernel/src/platform/drivers/i2c.h similarity index 100% rename from kernel/drivers/i2c.h rename to kernel/src/platform/drivers/i2c.h diff --git a/kernel/drivers/network.cpp b/kernel/src/platform/drivers/network.cpp similarity index 100% rename from kernel/drivers/network.cpp rename to kernel/src/platform/drivers/network.cpp diff --git a/kernel/drivers/network.h b/kernel/src/platform/drivers/network.h similarity index 100% rename from kernel/drivers/network.h rename to kernel/src/platform/drivers/network.h diff --git a/kernel/drivers/pci.cpp b/kernel/src/platform/drivers/pci.cpp similarity index 100% rename from kernel/drivers/pci.cpp rename to kernel/src/platform/drivers/pci.cpp diff --git a/kernel/drivers/pci.h b/kernel/src/platform/drivers/pci.h similarity index 100% rename from kernel/drivers/pci.h rename to kernel/src/platform/drivers/pci.h diff --git a/kernel/drivers/spi.cpp b/kernel/src/platform/drivers/spi.cpp similarity index 100% rename from kernel/drivers/spi.cpp rename to kernel/src/platform/drivers/spi.cpp diff --git a/kernel/drivers/spi.h b/kernel/src/platform/drivers/spi.h similarity index 100% rename from kernel/drivers/spi.h rename to kernel/src/platform/drivers/spi.h diff --git a/kernel/drivers/timer.cpp b/kernel/src/platform/drivers/timer.cpp similarity index 100% rename from kernel/drivers/timer.cpp rename to kernel/src/platform/drivers/timer.cpp diff --git a/kernel/drivers/timer.h b/kernel/src/platform/drivers/timer.h similarity index 100% rename from kernel/drivers/timer.h rename to kernel/src/platform/drivers/timer.h diff --git a/kernel/drivers/uart.cpp b/kernel/src/platform/drivers/uart.cpp similarity index 100% rename from kernel/drivers/uart.cpp rename to kernel/src/platform/drivers/uart.cpp diff --git a/kernel/drivers/uart.h b/kernel/src/platform/drivers/uart.h similarity index 100% rename from kernel/drivers/uart.h rename to kernel/src/platform/drivers/uart.h diff --git a/kernel/drivers/uos_fbcon.cpp b/kernel/src/platform/drivers/uos_fbcon.cpp similarity index 100% rename from kernel/drivers/uos_fbcon.cpp rename to kernel/src/platform/drivers/uos_fbcon.cpp diff --git a/kernel/drivers/uos_fbcon.h b/kernel/src/platform/drivers/uos_fbcon.h similarity index 100% rename from kernel/drivers/uos_fbcon.h rename to kernel/src/platform/drivers/uos_fbcon.h diff --git a/kernel/drivers/usb.h b/kernel/src/platform/drivers/usb.h similarity index 100% rename from kernel/drivers/usb.h rename to kernel/src/platform/drivers/usb.h diff --git a/kernel/drivers/usb_core.cpp b/kernel/src/platform/drivers/usb_core.cpp similarity index 100% rename from kernel/drivers/usb_core.cpp rename to kernel/src/platform/drivers/usb_core.cpp diff --git a/kernel/drivers/usb_ehci.cpp b/kernel/src/platform/drivers/usb_ehci.cpp similarity index 100% rename from kernel/drivers/usb_ehci.cpp rename to kernel/src/platform/drivers/usb_ehci.cpp