refactor: restructure kernel architecture by reorganizing source files and implementing RISC-V support

This commit is contained in:
Fábio Coutada 2026-07-09 21:03:33 +01:00
parent 9540b0528c
commit 5daa18a192
85 changed files with 3475 additions and 203 deletions

View file

@ -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:

View file

@ -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

View file

@ -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;
}

View file

@ -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 <stdint.h>
#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 */

View file

@ -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).

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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" */

View file

@ -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

View file

@ -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" */

View file

@ -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 <uos/uos_syscalls.h>
#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" */

View file

@ -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" */

View file

@ -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" */

View file

@ -0,0 +1,30 @@
#ifndef UNIVERSALISOS_RISCV_CONTEXT_SWITCH_H
#define UNIVERSALISOS_RISCV_CONTEXT_SWITCH_H
#include <stdint.h>
#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 */

View file

@ -0,0 +1,36 @@
/*
* UniversalisOS RISC-V CPU / H-extension Initialisation
*/
#ifndef UNIVERSALISOS_RISCV_CPU_H
#define UNIVERSALISOS_RISCV_CPU_H
#include <stdint.h>
#include <stdbool.h>
#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 */

View file

@ -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 <stdint.h>
#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 */

View file

@ -0,0 +1,85 @@
#ifndef UNIVERSALISOS_RISCV_EXCEPTIONS_H
#define UNIVERSALISOS_RISCV_EXCEPTIONS_H
#include <stdint.h>
#include <stddef.h>
#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 */

View file

@ -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 <stdint.h>
#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 */

View file

@ -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 <stdint.h>
#include <stdbool.h>
#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 */

View file

@ -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 <stdint.h>
#include <stddef.h>
#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 */

View file

@ -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 <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#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 */

View file

@ -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 <stdint.h>
#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 */

View file

@ -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 <stdint.h>
#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 */

View file

@ -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 <stdint.h>
#include <stdbool.h>
#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 */

View file

@ -0,0 +1,23 @@
/*
* UniversalisOS RISC-V Timer Driver (SBI-based)
*/
#ifndef UNIVERSALISOS_RISCV_TIMER_H
#define UNIVERSALISOS_RISCV_TIMER_H
#include <stdint.h>
#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 */

View file

@ -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

View file

@ -0,0 +1,64 @@
/*
* UniversalisOS RISC-V VM / vCPU State
*/
#ifndef UNIVERSALISOS_RISCV_VM_H
#define UNIVERSALISOS_RISCV_VM_H
#include <stdint.h>
#include <stdbool.h>
#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 */

View file

@ -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 <uos/uos.h>
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");
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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" */

View file

@ -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" */

View file

@ -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" */

View file

@ -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" */

View file

@ -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" */

View file

@ -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" */

View file

@ -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

View file

@ -0,0 +1,79 @@
/*
* UniversalisOS RISC-V NS16550a UART Driver
* QEMU virt UART0 at 0x10000000
*/
#include <stdint.h>
#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<uint32_t>(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

View file

@ -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" */