From e90b72ec3082588b296956730bd2930cf8d5d671 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Fri, 10 Jul 2026 11:31:46 +0800 Subject: refactor: promote new architecture to repository root Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior. --- arch/riscv/include/arch/config.h | 106 +++++++++++++++++++++++++++ arch/riscv/include/arch/context.h | 33 +++++++++ arch/riscv/include/arch/cpu.h | 95 ++++++++++++++++++++++++ arch/riscv/include/arch/extable.h | 55 ++++++++++++++ arch/riscv/include/arch/fpstate.h | 94 ++++++++++++++++++++++++ arch/riscv/include/arch/mmu.h | 78 ++++++++++++++++++++ arch/riscv/include/arch/pmap.h | 49 +++++++++++++ arch/riscv/include/arch/random.h | 27 +++++++ arch/riscv/include/arch/sbi.h | 111 ++++++++++++++++++++++++++++ arch/riscv/include/arch/tlb.h | 19 +++++ arch/riscv/include/arch/trap.h | 94 ++++++++++++++++++++++++ arch/riscv/include/arch/trapframe.h | 140 ++++++++++++++++++++++++++++++++++++ arch/riscv/include/asm/csr.h | 37 ++++++++++ arch/riscv/include/asm/page.h | 62 ++++++++++++++++ arch/riscv/include/asm/pgtable.h | 55 ++++++++++++++ arch/riscv/include/asm/sections.h | 50 +++++++++++++ 16 files changed, 1105 insertions(+) create mode 100644 arch/riscv/include/arch/config.h create mode 100644 arch/riscv/include/arch/context.h create mode 100644 arch/riscv/include/arch/cpu.h create mode 100644 arch/riscv/include/arch/extable.h create mode 100644 arch/riscv/include/arch/fpstate.h create mode 100644 arch/riscv/include/arch/mmu.h create mode 100644 arch/riscv/include/arch/pmap.h create mode 100644 arch/riscv/include/arch/random.h create mode 100644 arch/riscv/include/arch/sbi.h create mode 100644 arch/riscv/include/arch/tlb.h create mode 100644 arch/riscv/include/arch/trap.h create mode 100644 arch/riscv/include/arch/trapframe.h create mode 100644 arch/riscv/include/asm/csr.h create mode 100644 arch/riscv/include/asm/page.h create mode 100644 arch/riscv/include/asm/pgtable.h create mode 100644 arch/riscv/include/asm/sections.h (limited to 'arch/riscv/include') diff --git a/arch/riscv/include/arch/config.h b/arch/riscv/include/arch/config.h new file mode 100644 index 0000000..8ae0502 --- /dev/null +++ b/arch/riscv/include/arch/config.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * RISC-V Architecture Configuration + * + * These constants define the architecture-specific memory layout. + * Each arch/ implementation must provide this header with the same symbols. + * + * KASLR: At boot, kaslr_init() randomises g_direct_map_base and + * g_vmemmap_base. All MI code must read these runtime variables + * (via pa_to_va / vmm helpers) rather than the static defaults. + */ + +#include + +namespace arch { + +/* ================================================================ + * Default (compile-time) virtual addresses + * + * SV39 gives a 512GB virtual address space. + * Higher-half layout (defaults before KASLR): + * + * 0xFFFFFFC0_00000000 (256) Direct map (PA+offset) + * 0xFFFFFFD0_00000000 (320) Vmemmap (Page array) + * + * The 64GB gap between them is the KASLR slide range. + * ================================================================ */ +inline constexpr uint64 kDirectMapBaseDefault = 0xFFFFFFC000000000ULL; +inline constexpr uint64 kVmemmapBaseDefault = 0xFFFFFFD000000000ULL; + +/* Physical memory base (where RAM starts on RISC-V virt platform) */ +inline constexpr uint64 kPhysBase = 0x80000000ULL; + +/* Kernel physical load address (= PHYS_BASE in linker script). + * Everything in [kPhysBase, kPhysStart) is firmware territory (PMP-protected). + * Analogous to Linux's CONFIG_PHYSICAL_START. */ +inline constexpr uint64 kPhysStart = 0x80200000ULL; + +/* ================================================================ + * Runtime (post-KASLR) virtual addresses + * + * These start equal to the defaults above. If CONFIG_KASLR is + * enabled, kaslr_init() adds a random, 1GB-aligned offset + * to each, sliding both regions upward in VA space. + * ================================================================ */ +extern uint64 g_direct_map_base; +extern uint64 g_vmemmap_base; + +/* ================================================================ + * KASLR Layout Structure (Phase A: Unified KASLR State) + * + * This struct holds all KASLR results: the random seed, derived + * hash, selected slot, and the randomised virtual address bases. + * It serves as the single source of truth for all KASLR decisions + * and is filled once during boot and read-only thereafter. + * ================================================================ */ + +enum class KaslrSeedSource : uint8 { + kFdt = 0, /* Loaded from FDT /random node */ + kHardware = 1, /* From hardware RNG (e.g., RDRAND) */ + kCycle = 2, /* Fallback: CPU cycle counter */ +}; + +struct KaslrLayout { + /* Seed and provenance */ + uint64 seed; /* 64-bit random seed value */ + uint64 seed_hash; /* Non-plaintext derived hash (splitmix64) */ + KaslrSeedSource source; /* Seed provenance (fdt/hw/cycle) */ + + /* Randomisation parameters */ + uint64 slot; /* Slot index: [0, 63] */ + uint64 slide_bytes; /* Byte offset: slot * 1GB */ + + /* Build-time policy */ + uint32 policy_id; /* CONFIG_KASLR_POLICY_ID at build time */ + + /* Randomised base addresses (post-KASLR) */ + uint64 direct_map_base; /* Direct map VA (kDirectMapBaseDefault + slide) */ + uint64 vmemmap_base; /* Vmemmap VA (kVmemmapBaseDefault + slide) */ + + /* Validation flags (for debug logging) */ + bool canonical_ok; /* All VA ranges passed canonical check */ + bool overlap_ok; /* No overlaps detected between regions */ +}; + +/* ================================================================ + * KASLR entry point + * + * Must be called AFTER random_early_init() and BEFORE vmm_init(). + * Fills g_kaslr_layout with randomised bases and self-check results. + * When CONFIG_KASLR is disabled this is a no-op. + * ================================================================ */ +extern KaslrLayout g_kaslr_layout; + +void kaslr_init(); + +/* Helper to query the KASLR layout safely */ +static inline const KaslrLayout& kaslr_layout_get() { + return g_kaslr_layout; +} + +} // namespace arch diff --git a/arch/riscv/include/arch/context.h b/arch/riscv/include/arch/context.h new file mode 100644 index 0000000..3d935fa --- /dev/null +++ b/arch/riscv/include/arch/context.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Context Switch Structure (RISC-V 64) + * + * Holds only callee-saved registers for voluntary (non-preemptive) + * context switches between kernel threads / tasks. + * + * Used by: arch::context_switch() in swtch.S + * Reference: docs/specs/arch_abstraction.md §3.1 + */ + +#include + +namespace arch { + +struct Context { + uint64 ra; // 0: Return address + uint64 sp; // 8: Stack pointer + uint64 s[12]; // 16: s0-s11 (callee-saved registers) +}; + +static_assert(sizeof(Context) == 112); + +// Assembly-implemented context switch. +// Saves current CPU registers to old_ctx, loads next_ctx into CPU. +// Defined in arch/riscv/trap/swtch.S +extern "C" void context_switch(Context* old_ctx, Context* next_ctx); + +} // namespace arch diff --git a/arch/riscv/include/arch/cpu.h b/arch/riscv/include/arch/cpu.h new file mode 100644 index 0000000..8c573ec --- /dev/null +++ b/arch/riscv/include/arch/cpu.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * CPU Control & Status Primitives (RISC-V 64) + * + * Provides interrupt enable/disable, CPU ID, timer, and halt. + * All functions are static inline — zero call overhead. + * + * Precondition: tp register must be initialized with CPU ID + * by entry.S before any of these functions are called. + * + * Reference: docs/specs/arch_abstraction.md §4.1 + */ + +#include + +/* RISC-V sstatus register bit definitions */ +#define SSTATUS_SIE (1UL << 1) /* Supervisor Interrupt Enable */ +#define SSTATUS_SPIE (1UL << 5) /* Supervisor Previous Interrupt Enable */ +#define SSTATUS_SPP (1UL << 8) /* Supervisor Previous Privilege (0=U, 1=S) */ + +/* RISC-V sie register bit definitions */ +#define SIE_SSIE (1UL << 1) /* Supervisor Software Interrupt Enable */ +#define SIE_STIE (1UL << 5) /* Supervisor Timer Interrupt Enable */ +#define SIE_SEIE (1UL << 9) /* Supervisor External Interrupt Enable */ + +namespace arch::cpu { + +// Get current CPU ID (from tp register, set by entry.S) +static inline uint64 id() { + uint64 val; + __asm__ volatile("mv %0, tp" : "=r"(val)); + return val; +} + +// Enable interrupts (set sstatus.SIE = 1) +static inline void intr_on() { + __asm__ volatile("csrs sstatus, %0" :: "r"(SSTATUS_SIE)); +} + +// Disable interrupts (clear sstatus.SIE = 0) +static inline void intr_off() { + __asm__ volatile("csrc sstatus, %0" :: "r"(SSTATUS_SIE)); +} + +// Check if interrupts are currently enabled +static inline bool intr_enabled() { + uint64 sstatus; + __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); + return (sstatus & SSTATUS_SIE) != 0; +} + +// Halt CPU until next interrupt (wfi instruction) +static inline void halt_until_interrupt() { + __asm__ volatile("wfi"); +} + +// Read hardware monotonic timer (rdtime instruction) +// Returns ticks since boot; frequency from FDT timebase-frequency. +static inline uint64 read_time() { + uint64 val; + __asm__ volatile("rdtime %0" : "=r"(val)); + return val; +} + +// Enable timer interrupt in sie (sie.STIE = 1) +static inline void timer_intr_on() { + __asm__ volatile("csrs sie, %0" :: "r"(SIE_STIE)); +} + +// Disable timer interrupt in sie (sie.STIE = 0) +static inline void timer_intr_off() { + __asm__ volatile("csrc sie, %0" :: "r"(SIE_STIE)); +} + +// Enable software interrupt in sie (sie.SSIE = 1) — needed for IPI +static inline void software_intr_on() { + __asm__ volatile("csrs sie, %0" :: "r"(SIE_SSIE)); +} + +// Enable external interrupt in sie (sie.SEIE = 1) — needed for PLIC (UART, etc.) +static inline void external_intr_on() { + __asm__ volatile("csrs sie, %0" :: "r"(SIE_SEIE)); +} + +// Clear S-mode software interrupt pending bit (sip.SSIP = 0) +// Must be called in the IPI handler to acknowledge the interrupt. +static inline void clear_sip_ssip() { + __asm__ volatile("csrc sip, %0" :: "r"(1UL << 1)); +} + +} // namespace arch::cpu diff --git a/arch/riscv/include/arch/extable.h b/arch/riscv/include/arch/extable.h new file mode 100644 index 0000000..eb2daac --- /dev/null +++ b/arch/riscv/include/arch/extable.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#ifndef __ASSEMBLER__ + +#include + +/* + * Exception table entry. + * insn: offset from this struct to the faulting instruction. + * fixup: offset from this struct to the fixup code. + * + * Using relative offsets avoids absolute relocations and saves space. + */ +struct ExceptionTableEntry { + int32 insn; + int32 fixup; +}; + +/* Search the exception table for a given PC. Returns fixup address, or 0 if not found. */ +uint64 search_extable(uint64 pc); + +/* Sorts the exception table at boot for binary search O(log N). */ +void extable_init(); + +#endif /* __ASSEMBLER__ */ + +/* + * EXTABLE macro for assembly usage. + * Appends an entry to the .extable section. + */ +#ifdef __ASSEMBLER__ + +#define EXTABLE(insn, fixup) \ + .pushsection .extable, "a"; \ + .balign 4; \ + .long insn - .; \ + .long fixup - .; \ + .popsection + +#else /* !__ASSEMBLER__ */ + +/* Stringified macro for inline assembly */ +#define _ASM_EXTABLE(insn, fixup) \ + ".pushsection .extable, \"a\"\n" \ + ".balign 4\n" \ + ".long " #insn " - .\n" \ + ".long " #fixup " - .\n" \ + ".popsection\n" + +#define EXTABLE(insn, fixup) _ASM_EXTABLE(insn, fixup) + +#endif /* __ASSEMBLER__ */ diff --git a/arch/riscv/include/arch/fpstate.h b/arch/riscv/include/arch/fpstate.h new file mode 100644 index 0000000..4b42a22 --- /dev/null +++ b/arch/riscv/include/arch/fpstate.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * FPU State — Floating-Point Register Context + * + * Pre-reserved structure for RISC-V F/D extension floating-point state. + * MUST be allocated in every TaskControlBlock so that future FPU + * save/restore logic has a stable destination without restructuring TCB. + * + * RISC-V F/D extension state: + * - 32 FP registers (f0-f31), each 64 bits for D extension + * - fcsr (Floating-point Control and Status Register) + * + * sstatus.FS field management: + * - FS=0 (Off): FPU disabled, any FP instruction traps + * - FS=1 (Initial): FPU enabled, registers are initial state + * - FS=2 (Clean): FPU enabled, registers not modified since last save + * - FS=3 (Dirty): FPU enabled, registers modified (need save) + * + * Current status: Structure defined, save/restore NOT implemented. + * When implemented, trap_dispatch and swtch.S will use lazy + * save/restore based on sstatus.FS dirty tracking. + * + * Reference: RISC-V ISA Manual §3.1.6.5 (FS field) + */ + +#include + +namespace arch { + +struct FpState { + uint64 f[32]; // f0-f31 (64-bit each for D extension) + uint32 fcsr; // Floating-point CSR (frm + fflags) + uint32 _pad; // Alignment padding +}; + +static_assert(sizeof(FpState) == 32 * 8 + 8, "FpState must be 264 bytes"); + +/* + * sstatus.FS field constants + */ +inline constexpr uint64 kSstatusFS_Off = 0ULL << 13; +inline constexpr uint64 kSstatusFS_Initial = 1ULL << 13; +inline constexpr uint64 kSstatusFS_Clean = 2ULL << 13; +inline constexpr uint64 kSstatusFS_Dirty = 3ULL << 13; +inline constexpr uint64 kSstatusFS_Mask = 3ULL << 13; + +/* + * Check if sstatus indicates FPU state is dirty (needs save). + */ +inline bool fpu_state_dirty(uint64 sstatus) { + return (sstatus & kSstatusFS_Mask) == kSstatusFS_Dirty; +} + +/* + * Check if FPU is enabled (FS != Off). + */ +inline bool fpu_enabled(uint64 sstatus) { + return (sstatus & kSstatusFS_Mask) != kSstatusFS_Off; +} + +} // namespace arch + +/* Assembly routines for FPU register save/restore (fpu.S) */ +extern "C" void fpu_save(arch::FpState* state); +extern "C" void fpu_restore(arch::FpState* state); + +namespace arch { + +/* + * Read sstatus.FS field (current FPU state) + */ +inline uint64 fpu_get_fs() { + uint64 sstatus; + __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); + return sstatus & kSstatusFS_Mask; +} + +/* + * Set sstatus.FS field. + * @fs: one of kSstatusFS_Off, kSstatusFS_Initial, kSstatusFS_Clean, kSstatusFS_Dirty + */ +inline void fpu_set_fs(uint64 fs) { + __asm__ volatile( + "csrc sstatus, %0\n" + "csrs sstatus, %1\n" + :: "r"(kSstatusFS_Mask), "r"(fs) + ); +} + +} // namespace arch diff --git a/arch/riscv/include/arch/mmu.h b/arch/riscv/include/arch/mmu.h new file mode 100644 index 0000000..f4c9b26 --- /dev/null +++ b/arch/riscv/include/arch/mmu.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * MMU Hardware Primitives (RISC-V SV39) + * + * Page table switching, TLB flushing, and fault address reading. + * All functions are static inline — zero call overhead. + * + * Note on SMP: flush_tlb_page() only flushes the LOCAL CPU's TLB. + * Cross-CPU TLB shootdown via IPI is not yet implemented. + * + * Reference: docs/specs/arch_abstraction.md §4.2 + */ + +#include + +/* SATP register: mode field for SV39 */ +#define SATP_SV39 (8UL << 60) + +/* SATP PPN mask (bits 43:0) */ +#define SATP_PPN_MASK ((1UL << 44) - 1) + +namespace arch::mmu { + +// Read the faulting virtual address from stval (set by HW on page fault) +static inline uint64 get_fault_address() { + uint64 val; + __asm__ volatile("csrr %0, stval" : "=r"(val)); + return val; +} + +// Switch the top-level page table. +// Takes a PHYSICAL ADDRESS (not PPN); internally shifts right by 12 +// and combines with SV39 mode bits before writing to satp. +static inline void set_page_table(uint64 physical_pgdir_addr) { + uint64 ppn = physical_pgdir_addr >> 12; + uint64 satp_val = SATP_SV39 | ppn; + __asm__ volatile("csrw satp, %0" :: "r"(satp_val)); + // Note: caller should call flush_tlb_all() after this +} + +// Read the current page table physical address. +// Extracts PPN from satp and converts back to physical address. +static inline uint64 get_page_table() { + uint64 satp_val; + __asm__ volatile("csrr %0, satp" : "=r"(satp_val)); + return (satp_val & SATP_PPN_MASK) << 12; +} + +// Read the raw satp CSR value (mode + PPN). +// Used when the caller needs the exact register value (e.g. for TrapFrame metadata). +static inline uint64 read_satp() { + uint64 val; + __asm__ volatile("csrr %0, satp" : "=r"(val)); + return val; +} + +// Flush the entire TLB (all ASIDs, all addresses) +static inline void flush_tlb_all() { + __asm__ volatile("sfence.vma zero, zero" ::: "memory"); +} + +// Flush a single TLB entry for the given virtual address +static inline void flush_tlb_page(uint64 vaddr) { + __asm__ volatile("sfence.vma %0, zero" :: "r"(vaddr) : "memory"); +} + +// Flush the instruction cache. +// Required after loading executable code into memory (e.g. ELF loading). +// sfence.vma only flushes the TLB; fence.i is needed for the I-cache. +static inline void flush_icache() { + __asm__ volatile("fence.i" ::: "memory"); +} + +} // namespace arch::mmu diff --git a/arch/riscv/include/arch/pmap.h b/arch/riscv/include/arch/pmap.h new file mode 100644 index 0000000..0b43b9e --- /dev/null +++ b/arch/riscv/include/arch/pmap.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * pmap — SV39 page table interface (MD layer) + * + * Namespace functions, zero runtime overhead. + * Implementation: arch/riscv/mm/pmap.cc + * + * Reference: docs/specs/vmm.md §3.2 + */ + +#include + +namespace pmap { + +/* PTE permission bits */ +inline constexpr uint64 PTE_V = 1ULL << 0; +inline constexpr uint64 PTE_R = 1ULL << 1; +inline constexpr uint64 PTE_W = 1ULL << 2; +inline constexpr uint64 PTE_X = 1ULL << 3; +inline constexpr uint64 PTE_U = 1ULL << 4; +inline constexpr uint64 PTE_G = 1ULL << 5; +inline constexpr uint64 PTE_A = 1ULL << 6; +inline constexpr uint64 PTE_D = 1ULL << 7; +inline constexpr uint64 PTE_COW = 1ULL << 8; /* Custom RSW bit for Copy-On-Write */ + +uint64 create(); +void destroy(uint64 root_pa); +int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); +int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); +void unmap(uint64 root_pa, uint64 va); +int protect(uint64 root_pa, uint64 va, uint64 perm); +int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm); +bool lookup(uint64 root_pa, uint64 va, uint64* pa_out); +uint64 lookup_pte(uint64 root_pa, uint64 va); +bool is_cow(uint64 root_pa, uint64 va); +int make_cow(uint64 root_pa, uint64 va); +void activate(uint64 root_pa); + +/* + * Translate MI-layer VM_* permission flags to hardware PTE bits. + * Called by vma.cc when faulting in a page. + */ +uint64 vm_perm_to_pte(uint64 vm_perm); + +} // namespace pmap diff --git a/arch/riscv/include/arch/random.h b/arch/riscv/include/arch/random.h new file mode 100644 index 0000000..8c8c051 --- /dev/null +++ b/arch/riscv/include/arch/random.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +namespace arch { + +/* + * Get a random 64-bit value from the hardware TRNG. + * Returns true if supported and successful, false otherwise. + */ +bool get_hw_random(uint64& value); + +/* + * Get the current high-resolution hardware cycle counter. + * Used as an entropy fallback (Jitter/Timer). + */ +inline uint64 get_cycles() +{ + uint64 cycles; + __asm__ volatile("rdcycle %0" : "=r"(cycles)); + return cycles; +} + +} // namespace arch diff --git a/arch/riscv/include/arch/sbi.h b/arch/riscv/include/arch/sbi.h new file mode 100644 index 0000000..80a6d7c --- /dev/null +++ b/arch/riscv/include/arch/sbi.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * sbi.h — RISC-V Supervisor Binary Interface (SBI) wrappers + * + * Provides inline ecall wrappers for SBI calls from S-mode. + * Uses the SBI v1.0+ extension-based calling convention. + * + * Reference: RISC-V SBI Specification v2.0 + */ + +#include + +namespace arch::sbi { + +struct sbiret { + long error; + long value; +}; + +/* SBI error codes */ +inline constexpr long SBI_SUCCESS = 0; +inline constexpr long SBI_ERR_NOT_SUPPORTED = -2; + +/* Extension IDs */ +inline constexpr int SBI_EXT_TIME = 0x54494D45; /* "TIME" */ +inline constexpr int SBI_EXT_IPI = 0x735049; /* "sPI" */ +inline constexpr int SBI_EXT_RFENCE = 0x52464E43; /* "RFNC" */ +inline constexpr int SBI_EXT_HSM = 0x48534D; /* "HSM" */ + +static inline sbiret sbi_ecall(int ext, int fid, uint64 arg0 = 0, + uint64 arg1 = 0, uint64 arg2 = 0, + uint64 arg3 = 0, uint64 arg4 = 0, + uint64 arg5 = 0) { + sbiret ret; + register uint64 a0 asm("a0") = arg0; + register uint64 a1 asm("a1") = arg1; + register uint64 a2 asm("a2") = arg2; + register uint64 a3 asm("a3") = arg3; + register uint64 a4 asm("a4") = arg4; + register uint64 a5 asm("a5") = arg5; + register uint64 a6 asm("a6") = fid; + register uint64 a7 asm("a7") = ext; + __asm__ volatile("ecall" + : "+r"(a0), "+r"(a1) + : "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7) + : "memory"); + ret.error = static_cast(a0); + ret.value = static_cast(a1); + return ret; +} + +/* ---- Timer Extension (EID 0x54494D45) ---- */ + +static inline void set_timer(uint64 stime_value) { + sbi_ecall(SBI_EXT_TIME, 0, stime_value); +} + +/* ---- IPI Extension (EID 0x735049) ---- */ + +/* + * sbi_send_ipi — Send S-mode software interrupt to target harts + * + * @hart_mask: bitmask of target harts (relative to hart_mask_base) + * @hart_mask_base: base hart ID for the mask (0 = harts 0-63) + */ +static inline sbiret sbi_send_ipi(uint64 hart_mask, + uint64 hart_mask_base = 0) { + return sbi_ecall(SBI_EXT_IPI, 0, hart_mask, hart_mask_base); +} + +/* ---- Remote Fence Extension (EID 0x52464E43) ---- */ + +/* + * sbi_remote_sfence_vma — Execute SFENCE.VMA on target harts + * + * @hart_mask/base: target harts + * @start_addr: start of VA range to invalidate + * @size: size of range (use ~0UL for full flush) + */ +static inline sbiret sbi_remote_sfence_vma(uint64 hart_mask, + uint64 hart_mask_base, + uint64 start_addr, + uint64 size) { + return sbi_ecall(SBI_EXT_RFENCE, 1, + hart_mask, hart_mask_base, + start_addr, size); +} + +/* ---- HSM Extension (EID 0x48534D) ---- */ + +/* + * sbi_hart_start — Start (wake) a hart that is in STOPPED state + * + * @hartid: target hart ID to start + * @start_addr: PHYSICAL address where the hart should begin execution + * @opaque: passed in a1 register to the hart (e.g. FDT pointer) + * + * OpenSBI v1.0+ parks non-boot harts; they must be explicitly started. + */ +static inline sbiret sbi_hart_start(uint64 hartid, + uint64 start_addr, + uint64 opaque) { + return sbi_ecall(SBI_EXT_HSM, 0, hartid, start_addr, opaque); +} + +} // namespace arch::sbi + diff --git a/arch/riscv/include/arch/tlb.h b/arch/riscv/include/arch/tlb.h new file mode 100644 index 0000000..46c6b97 --- /dev/null +++ b/arch/riscv/include/arch/tlb.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +namespace arch::tlb { + +/* + * Perform a cross-CPU TLB flush (shootdown). + * + * @param cpu_mask: Bitmask of CPUs to target. + * @param start: Start virtual address, or 0 for full address space. + * @param size: Size in bytes, or ~0ULL for full address space. + */ +void flush_remote(uint64 cpu_mask, uint64 start, uint64 size); + +} // namespace arch::tlb diff --git a/arch/riscv/include/arch/trap.h b/arch/riscv/include/arch/trap.h new file mode 100644 index 0000000..8e55250 --- /dev/null +++ b/arch/riscv/include/arch/trap.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Trap Vector Control Primitives (RISC-V 64) + * + * Set/read the S-mode trap vector (stvec), trap cause (scause), + * and trap value (stval) registers. + * + * Reference: docs/specs/arch_abstraction.md §4.3 + */ + +#include + +/* scause interrupt bit (bit 63): 1 = interrupt, 0 = exception */ +#define SCAUSE_INTERRUPT (1UL << 63) + +/* Exception cause codes (scause with bit 63 = 0) */ +#define EXC_INST_MISALIGNED 0 +#define EXC_INST_ACCESS_FAULT 1 +#define EXC_ILLEGAL_INST 2 +#define EXC_BREAKPOINT 3 +#define EXC_LOAD_MISALIGNED 4 +#define EXC_LOAD_ACCESS_FAULT 5 +#define EXC_STORE_MISALIGNED 6 +#define EXC_STORE_ACCESS_FAULT 7 +#define EXC_ECALL_FROM_U 8 +#define EXC_ECALL_FROM_S 9 +#define EXC_INST_PAGE_FAULT 12 +#define EXC_LOAD_PAGE_FAULT 13 +#define EXC_STORE_PAGE_FAULT 15 + +/* Interrupt cause codes (scause with bit 63 = 1, lower bits) */ +#define IRQ_S_SOFTWARE 1 +#define IRQ_S_TIMER 5 +#define IRQ_S_EXTERNAL 9 + +namespace arch::trap { + +// Set S-mode trap vector base address (writes stvec). +// Mode: Direct (all traps go to handler_addr). +static inline void set_vector(uint64 handler_addr) { + // Lowest 2 bits of stvec = mode. 0 = Direct. + __asm__ volatile("csrw stvec, %0" :: "r"(handler_addr)); +} + +// Read current trap vector base address +static inline uint64 get_vector() { + uint64 val; + __asm__ volatile("csrr %0, stvec" : "=r"(val)); + return val; +} + +// Read trap cause register (scause) +// Bit 63: 1=interrupt, 0=exception. Lower bits: cause code. +static inline uint64 get_cause() { + uint64 val; + __asm__ volatile("csrr %0, scause" : "=r"(val)); + return val; +} + +// Read trap value register (stval) +// Contains faulting address (page faults) or faulting instruction. +static inline uint64 get_value() { + uint64 val; + __asm__ volatile("csrr %0, stval" : "=r"(val)); + return val; +} + +// Check if a scause value represents an interrupt (vs exception) +static inline bool is_interrupt(uint64 cause) { + return (cause & SCAUSE_INTERRUPT) != 0; +} + +// Extract the cause code (strip the interrupt bit) +static inline uint64 cause_code(uint64 cause) { + return cause & ~SCAUSE_INTERRUPT; +} + +// Enable Supervisor User Memory access (set SUM bit in sstatus). +// When SUM=1, S-mode can read/write pages marked PTE_U. +// This is needed for syscall handlers to access user-space buffers. +static inline void enable_sum() { + __asm__ volatile("csrs sstatus, %0" :: "r"(1UL << 18)); +} + +// Disable Supervisor User Memory access (clear SUM bit in sstatus). +static inline void disable_sum() { + __asm__ volatile("csrc sstatus, %0" :: "r"(1UL << 18)); +} + +} // namespace arch::trap diff --git a/arch/riscv/include/arch/trapframe.h b/arch/riscv/include/arch/trapframe.h new file mode 100644 index 0000000..b198646 --- /dev/null +++ b/arch/riscv/include/arch/trapframe.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Trap Frame Structure (RISC-V 64) + * + * Saved/restored by the assembly trap_vector on every interrupt, + * exception, or system call. Contains the full CPU snapshot needed + * to resume execution after trap handling. + * + * Layout must match the assembly offsets in trap_vector.S exactly. + * Reference: docs/specs/arch_abstraction.md §3.2 + */ + +/* + * TrapFrame field offsets for use in assembly. + * These must be kept in sync with the struct layout below. + * Placed before any C++ code so assembly can #include this file. + */ +#define TF_KERNEL_SATP 0 +#define TF_KERNEL_SP 8 +#define TF_KERNEL_TRAP 16 +#define TF_KERNEL_CPUID 24 +#define TF_EPC 32 +#define TF_SSTATUS 40 +#define TF_RA 48 +#define TF_SP 56 +#define TF_GP 64 +#define TF_TP 72 +#define TF_T0 80 +#define TF_T1 88 +#define TF_T2 96 +#define TF_T3 104 +#define TF_T4 112 +#define TF_T5 120 +#define TF_T6 128 +#define TF_S0 136 +#define TF_S1 144 +#define TF_S2 152 +#define TF_S3 160 +#define TF_S4 168 +#define TF_S5 176 +#define TF_S6 184 +#define TF_S7 192 +#define TF_S8 200 +#define TF_S9 208 +#define TF_S10 216 +#define TF_S11 224 +#define TF_A0 232 +#define TF_A1 240 +#define TF_A2 248 +#define TF_A3 256 +#define TF_A4 264 +#define TF_A5 272 +#define TF_A6 280 +#define TF_A7 288 +#define TF_SIZE 296 + +#ifndef __ASSEMBLER__ + +#include +#include + +namespace arch { + +struct TrapFrame { + /* ---- Kernel metadata (offset 0-39) ---- */ + uint64 kernel_satp; // 0: Kernel page table (written to satp on trap entry) + uint64 kernel_sp; // 8: Top of kernel stack for this task + uint64 kernel_trap; // 16: Address of C++ trap_handler function + uint64 kernel_cpu_id; // 24: CPU ID (loaded into tp on trap entry) + uint64 epc; // 32: Interrupted instruction address (sepc) + + /* ---- CSR backup (offset 40-47) ---- */ + uint64 sstatus; // 40: Privilege status (SPP, SPIE bits) + + /* ---- General-purpose registers (offset 48-295) ---- */ + uint64 ra; // 48: x1 - Return address + uint64 sp; // 56: x2 - User stack pointer + uint64 gp; // 64: x3 - Global pointer + uint64 tp; // 72: x4 - Thread pointer + uint64 t[7]; // 80: x5-x7, x28-x31 (t0-t6, caller-saved temporaries) + uint64 s[12]; // 136: x8-x9, x18-x27 (s0-s11, callee-saved) + uint64 a[8]; // 232: x10-x17 (a0-a7, arguments / syscall params) +}; + +static_assert(sizeof(TrapFrame) == 296, "TrapFrame size mismatch with assembly offsets"); + +} // namespace arch + +/* Forward declaration — defined in trap_md.cc. + * Must be after TrapFrame definition. */ +extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*); + +namespace arch { + +/* + * trapframe_init_user — prepare a TrapFrame for returning to user mode. + * + * Sets arch-specific sstatus: + * SPP = 0 (return to U-mode on sret) + * SPIE = 1 (enable interrupts in U-mode) + * FS = Initial (allow FP instructions) + * + * Called from MI exec.cc instead of setting sstatus directly. + */ +static inline void trapframe_init_user(TrapFrame* tf, uint64 entry, uint64 sp) +{ + /* Clear all registers */ + for (auto& t : tf->t) { t = 0; } + for (auto& s : tf->s) { s = 0; } + for (auto& a : tf->a) { a = 0; } + tf->ra = 0; + tf->gp = 0; + tf->tp = 0; + + tf->epc = entry; + tf->sp = sp; + + /* sstatus: SPP=0 (U-mode), SPIE=1 (interrupts), FS=Initial (FP ok) */ + constexpr uint64 kSPIE = (1ULL << 5); + constexpr uint64 kFS_Initial = (1ULL << 13); + tf->sstatus = kSPIE | kFS_Initial; + + /* Kernel metadata used by trap_vector.S on every trap from U-mode. + * These are RISC-V specific — trap_vector reads them from the + * TrapFrame to switch to kernel context. */ + uint64 satp; + asm volatile("csrr %0, satp" : "=r"(satp)); + tf->kernel_satp = satp; + tf->kernel_sp = reinterpret_cast(tf); + tf->kernel_trap = reinterpret_cast(&trap_dispatch); + tf->kernel_cpu_id = cpu::id(); +} + +} // namespace arch + +#endif /* __ASSEMBLER__ */ diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h new file mode 100644 index 0000000..42f1eb4 --- /dev/null +++ b/arch/riscv/include/asm/csr.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_CSR_H +#define _ASM_RISCV_CSR_H + +/* + * RISC-V CSR (Control & Status Register) definitions used by the early + * boot / MMU-switch path. + * + * LumeOS-native subset of Linux arch/riscv/include/asm/csr.h. Only the + * symbols head.S and mm/init.cc actually reference are defined — adding + * the rest is straightforward when new code needs them, and skipping the + * Linux ALTERNATIVE / vendor-extension machinery keeps the cascade flat. + */ + +/* SATP (Supervisor Address Translation and Protection) layout — RV64 */ +#define SATP_PPN_MASK 0x00000FFFFFFFFFFFUL /* bits [43:0] = root PPN */ +#define SATP_ASID_SHIFT 44 +#define SATP_MODE_SHIFT 60 + +/* SATP MODE encodings (RV64) */ +#define SATP_MODE_BARE (0UL << SATP_MODE_SHIFT) +#define SATP_MODE_SV39 (8UL << SATP_MODE_SHIFT) +#define SATP_MODE_SV48 (9UL << SATP_MODE_SHIFT) + +/* Compose a SATP value from a page-table physical address. + * Result: MODE | (root_ppn) where root_ppn = pgd_pa >> PAGE_SHIFT. */ +#define MAKE_SATP(mode, pgd_pa) ((mode) | (((pgd_pa) >> 12) & SATP_PPN_MASK)) + +/* SSTATUS bits we touch at boot — disable FP / vector at entry so that an + * accidental FP / V instruction in early code traps cleanly rather than + * silently using dirty state from before reset. */ +#define SR_FS_OFF (0UL << 13) +#define SR_VS_OFF (0UL << 9) + +#endif /* _ASM_RISCV_CSR_H */ diff --git a/arch/riscv/include/asm/page.h b/arch/riscv/include/asm/page.h new file mode 100644 index 0000000..7b9f155 --- /dev/null +++ b/arch/riscv/include/asm/page.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_PAGE_H +#define _ASM_RISCV_PAGE_H + +/* + * RISC-V page / virtual-memory layout constants. + * + * Mirrors the subset of Linux arch/riscv/include/asm/page.h that LumeOS's + * early boot path needs: + * - PAGE_SIZE / PAGE_SHIFT / PAGE_MASK + * - PAGE_OFFSET (start of the higher-half direct map) + * - KERNEL_LINK_ADDR (link-time VA of _start) + * - SV39 PTE flag bits + * - PTRS_PER_PGD (512 for SV39) + * + * Values match Linux RISC-V SV39 defaults so that struct kernel_mapping + * (filled by setup_vm in arch/riscv/mm/init.cc) carries the same numeric + * relations Linux's mm/init.c maintains. + */ + +#define PAGE_SHIFT 12 +#define PAGE_SIZE (1UL << PAGE_SHIFT) +#define PAGE_MASK (~(PAGE_SIZE - 1)) + +/* Higher-half base of the kernel direct map. Bottom of the SV39 supervisor + * address space minus the unmapped guard region. Identical to Linux's + * CONFIG_PAGE_OFFSET default on RV64. */ +#define PAGE_OFFSET 0xFFFFFFC000000000UL + +/* Where _start was linked. OpenSBI hands control to PHYS_BASE; the kernel + * runs at KERNEL_LINK_ADDR after relocate_enable_mmu jumps to higher-half. */ +#define PHYS_BASE 0x80200000UL +#define KERNEL_LINK_ADDR 0xFFFFFFC080200000UL + +/* SATP mode field — SV39 = 8, shifted to bits [63:60]. */ +#define SATP_MODE_SV39 (8UL << 60) + +#define PTRS_PER_PGD 512 + +/* 1 GB superpage size — entry.S used to build the early page table with + * these; setup_vm in arch/riscv/mm/init.cc now does the same. */ +#define PGDIR_SHIFT 30 +#define PGDIR_SIZE (1UL << PGDIR_SHIFT) +#define PGDIR_MASK (~(PGDIR_SIZE - 1)) + +/* SV39 PTE flag bits (one byte). */ +#define _PAGE_PRESENT (1UL << 0) /* aka VALID — historic Linux name */ +#define _PAGE_READ (1UL << 1) +#define _PAGE_WRITE (1UL << 2) +#define _PAGE_EXEC (1UL << 3) +#define _PAGE_USER (1UL << 4) +#define _PAGE_GLOBAL (1UL << 5) +#define _PAGE_ACCESSED (1UL << 6) +#define _PAGE_DIRTY (1UL << 7) + +#define PAGE_KERNEL_EXEC \ + (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | _PAGE_EXEC | \ + _PAGE_ACCESSED | _PAGE_DIRTY) + +#endif /* _ASM_RISCV_PAGE_H */ diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h new file mode 100644 index 0000000..28fc4c5 --- /dev/null +++ b/arch/riscv/include/asm/pgtable.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_PGTABLE_H +#define _ASM_RISCV_PGTABLE_H + +/* + * Early page-table construction API used by arch/riscv/mm/init.cc::setup_vm. + * + * Mirrors the create_pgd_mapping interface from Linux's + * arch/riscv/mm/init.c — same name, same semantics: + * + * create_pgd_mapping(pgdir, va, pa, sz, prot) + * + * pgdir : root of the page table to populate + * va : virtual address to map + * pa : physical address to map to + * sz : mapping size — must be a supported leaf size (PAGE_SIZE, + * 2 MiB megapage, or 1 GiB gigapage) + * prot : PTE flag bits (PAGE_KERNEL_EXEC etc., from ) + * + * The boot-time variant in this file only supports gigapage (1 GiB) + * mappings — that is all setup_vm needs for the early_pg_dir / trampoline_pg_dir + * built before the slab allocator exists. Finer-grained mappings come + * later via vmm_init(). + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { unsigned long pgd; } pgd_t; + +/* Linker-provided early page-table storage (defined in lumeos.lds.S + * ARCH_BSS_EXTRA, lives in .bss so it's zero-initialized). Allocating + * them in the linker script — not in C — mirrors Linux and keeps the + * symbols visible from head.S without a forward C decl mismatch. */ +extern pgd_t early_pg_dir[PTRS_PER_PGD]; +extern pgd_t trampoline_pg_dir[PTRS_PER_PGD]; +extern pgd_t fixmap_pg_dir[PTRS_PER_PGD]; + +/* Install a single PGD entry mapping `va` → `pa` with size `sz` and PTE + * flags `prot`. Only sz == PGDIR_SIZE (1 GiB) is supported in this early + * variant — vmm_init() takes over with create_pgd_mapping_pmd later. */ +void create_pgd_mapping(pgd_t *pgdir, unsigned long va, unsigned long pa, + unsigned long sz, unsigned long prot); + +#ifdef __cplusplus +} +#endif + +#endif /* _ASM_RISCV_PGTABLE_H */ diff --git a/arch/riscv/include/asm/sections.h b/arch/riscv/include/asm/sections.h new file mode 100644 index 0000000..454d9de --- /dev/null +++ b/arch/riscv/include/asm/sections.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_SECTIONS_H +#define _ASM_RISCV_SECTIONS_H + +/* + * Linker-emitted symbols that bracket each section of the kernel image. + * + * The lumeos.lds.S linker script defines these as the start / end + * addresses of contiguous regions. Code that walks a section + * (relocate_kernel walks .rela.dyn, the C++ ctor loop walks + * .init_array, the VMM mapping code walks .text vs .rodata, ...) + * imports the pair via this header. + * + * Mirrors Linux arch/riscv/include/asm/sections.h / asm-generic/sections.h. + * Each name and pair is identical to Linux so that direct ports of mm/init.c + * relocate_kernel logic compile unchanged against LumeOS. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern char _start[]; +extern char _end[]; + +extern char _stext[]; +extern char _etext[]; +extern char _srodata[]; +extern char _erodata[]; +extern char _sdata[]; +extern char _edata[]; +extern char _sbss[]; +extern char _ebss[]; + +extern char __init_array_start[]; +extern char __init_array_end[]; + +/* .rela.dyn — array of Elf64_Rela entries emitted by the linker under + * `-shared -Bsymbolic`. relocate_kernel walks this range and patches + * each R_RISCV_RELATIVE slot. */ +extern char __rela_dyn_start[]; +extern char __rela_dyn_end[]; + +#ifdef __cplusplus +} +#endif + +#endif /* _ASM_RISCV_SECTIONS_H */ -- cgit v1.2.3