refactor: restructure kernel architecture by reorganizing source files and implementing RISC-V support
This commit is contained in:
parent
9540b0528c
commit
5daa18a192
85 changed files with 3475 additions and 203 deletions
|
|
@ -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:
|
||||
|
|
@ -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
|
||||
144
kernel/src/arch/armv7/config/cfg_boot_armv7.cpp
Normal file
144
kernel/src/arch/armv7/config/cfg_boot_armv7.cpp
Normal 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;
|
||||
}
|
||||
37
kernel/src/arch/armv7/config/cfg_boot_armv7.h
Normal file
37
kernel/src/arch/armv7/config/cfg_boot_armv7.h
Normal 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 */
|
||||
|
|
@ -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).
|
||||
56
kernel/src/arch/armv7/sched/ctx_switch.S
Normal file
56
kernel/src/arch/armv7/sched/ctx_switch.S
Normal 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
|
||||
139
kernel/src/arch/armv7/sched/preempt_switch.S
Normal file
139
kernel/src/arch/armv7/sched/preempt_switch.S
Normal 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
|
||||
167
kernel/src/arch/riscv/boot.S
Normal file
167
kernel/src/arch/riscv/boot.S
Normal 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:
|
||||
27
kernel/src/arch/riscv/context_switch.cpp
Normal file
27
kernel/src/arch/riscv/context_switch.cpp
Normal 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" */
|
||||
168
kernel/src/arch/riscv/context_switch_asm.S
Normal file
168
kernel/src/arch/riscv/context_switch_asm.S
Normal 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
|
||||
56
kernel/src/arch/riscv/cpu.cpp
Normal file
56
kernel/src/arch/riscv/cpu.cpp
Normal 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" */
|
||||
225
kernel/src/arch/riscv/exceptions.cpp
Normal file
225
kernel/src/arch/riscv/exceptions.cpp
Normal 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" */
|
||||
98
kernel/src/arch/riscv/guest_boot.cpp
Normal file
98
kernel/src/arch/riscv/guest_boot.cpp
Normal 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" */
|
||||
107
kernel/src/arch/riscv/hm.cpp
Normal file
107
kernel/src/arch/riscv/hm.cpp
Normal 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" */
|
||||
30
kernel/src/arch/riscv/inc/context_switch.h
Normal file
30
kernel/src/arch/riscv/inc/context_switch.h
Normal 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 */
|
||||
36
kernel/src/arch/riscv/inc/cpu.h
Normal file
36
kernel/src/arch/riscv/inc/cpu.h
Normal 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 */
|
||||
184
kernel/src/arch/riscv/inc/csr.h
Normal file
184
kernel/src/arch/riscv/inc/csr.h
Normal 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 */
|
||||
85
kernel/src/arch/riscv/inc/exceptions.h
Normal file
85
kernel/src/arch/riscv/inc/exceptions.h
Normal 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 */
|
||||
51
kernel/src/arch/riscv/inc/guest_boot.h
Normal file
51
kernel/src/arch/riscv/inc/guest_boot.h
Normal 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 */
|
||||
74
kernel/src/arch/riscv/inc/hm.h
Normal file
74
kernel/src/arch/riscv/inc/hm.h
Normal 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 */
|
||||
76
kernel/src/arch/riscv/inc/mem_pikeos.h
Normal file
76
kernel/src/arch/riscv/inc/mem_pikeos.h
Normal 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 */
|
||||
86
kernel/src/arch/riscv/inc/page_table.h
Normal file
86
kernel/src/arch/riscv/inc/page_table.h
Normal 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 */
|
||||
33
kernel/src/arch/riscv/inc/plic.h
Normal file
33
kernel/src/arch/riscv/inc/plic.h
Normal 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 */
|
||||
47
kernel/src/arch/riscv/inc/sbi.h
Normal file
47
kernel/src/arch/riscv/inc/sbi.h
Normal 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 */
|
||||
139
kernel/src/arch/riscv/inc/sched_pikeos.h
Normal file
139
kernel/src/arch/riscv/inc/sched_pikeos.h
Normal 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 */
|
||||
23
kernel/src/arch/riscv/inc/timer.h
Normal file
23
kernel/src/arch/riscv/inc/timer.h
Normal 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 */
|
||||
16
kernel/src/arch/riscv/inc/uart.h
Normal file
16
kernel/src/arch/riscv/inc/uart.h
Normal 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
|
||||
64
kernel/src/arch/riscv/inc/vm.h
Normal file
64
kernel/src/arch/riscv/inc/vm.h
Normal 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 */
|
||||
113
kernel/src/arch/riscv/kernel_riscv.cpp
Normal file
113
kernel/src/arch/riscv/kernel_riscv.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
54
kernel/src/arch/riscv/linker.ld
Normal file
54
kernel/src/arch/riscv/linker.ld
Normal 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);
|
||||
}
|
||||
54
kernel/src/arch/riscv/linker_polarfire.ld
Normal file
54
kernel/src/arch/riscv/linker_polarfire.ld
Normal 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);
|
||||
}
|
||||
139
kernel/src/arch/riscv/mem_pikeos.cpp
Normal file
139
kernel/src/arch/riscv/mem_pikeos.cpp
Normal 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" */
|
||||
103
kernel/src/arch/riscv/page_table.cpp
Normal file
103
kernel/src/arch/riscv/page_table.cpp
Normal 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" */
|
||||
100
kernel/src/arch/riscv/plic.cpp
Normal file
100
kernel/src/arch/riscv/plic.cpp
Normal 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" */
|
||||
32
kernel/src/arch/riscv/sbi.cpp
Normal file
32
kernel/src/arch/riscv/sbi.cpp
Normal 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" */
|
||||
295
kernel/src/arch/riscv/sched_pikeos.cpp
Normal file
295
kernel/src/arch/riscv/sched_pikeos.cpp
Normal 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" */
|
||||
40
kernel/src/arch/riscv/timer.cpp
Normal file
40
kernel/src/arch/riscv/timer.cpp
Normal 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" */
|
||||
135
kernel/src/arch/riscv/trap.S
Normal file
135
kernel/src/arch/riscv/trap.S
Normal 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
|
||||
79
kernel/src/arch/riscv/uart.cpp
Normal file
79
kernel/src/arch/riscv/uart.cpp
Normal 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
|
||||
132
kernel/src/arch/riscv/vm.cpp
Normal file
132
kernel/src/arch/riscv/vm.cpp
Normal 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" */
|
||||
Loading…
Reference in a new issue