From e2564d112ba03b432f9f44f445cbc4a3ed85751a Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Fri, 17 Apr 2026 01:05:31 +0800 Subject: feat: add initial RISC-V architecture support and core kernel functionality - Implement MMU hardware primitives for RISC-V SV39 in mmu.h - Create trap vector control primitives in trap.h - Define trap frame structure for RISC-V in trapframe.h - Add assembly routines for context switching in swtch.S - Implement trap vector entry/exit in trap_vector.S - Introduce basic error number definitions in errno.h - Provide placement new/delete operators in new.h - Define kernel panic handler in panic.h and implement in panic.cc - Establish basic type definitions in types.h - Create the kernel main entry point in main.cc with global initialization - Add synchronization primitives using spinlocks in spinlock.h and spinlock.cc - Define per-CPU state tracking for interrupts in cpu_state.h - Create a linker script for the kernel in linker.ld - Update module makefiles for kernel and library components --- include/lib/errno.h | 8 +- include/ulib/errno.h | 8 +- na/Makefile | 158 +++++++++++++++++++ na/arch/riscv/boot/entry.S | 271 +++++++++++++++++++++++++++++++++ na/arch/riscv/include/arch/context.h | 33 ++++ na/arch/riscv/include/arch/cpu.h | 64 ++++++++ na/arch/riscv/include/arch/mmu.h | 63 ++++++++ na/arch/riscv/include/arch/trap.h | 82 ++++++++++ na/arch/riscv/include/arch/trapframe.h | 92 +++++++++++ na/arch/riscv/module.mk | 7 + na/arch/riscv/trap/swtch.S | 40 +++++ na/arch/riscv/trap/trap_vector.S | 171 +++++++++++++++++++++ na/include/lume/errno.h | 43 ++++++ na/include/lume/new.h | 18 +++ na/include/lume/panic.h | 16 ++ na/include/lume/types.h | 39 +++++ na/kernel/main.cc | 146 ++++++++++++++++++ na/kernel/module.mk | 3 + na/kernel/sync/cpu_state.h | 34 +++++ na/kernel/sync/spinlock.cc | 111 ++++++++++++++ na/kernel/sync/spinlock.h | 60 ++++++++ na/lib/module.mk | 2 + na/lib/panic.cc | 46 ++++++ na/linker.ld | 111 ++++++++++++++ 24 files changed, 1618 insertions(+), 8 deletions(-) create mode 100644 na/Makefile create mode 100644 na/arch/riscv/boot/entry.S create mode 100644 na/arch/riscv/include/arch/context.h create mode 100644 na/arch/riscv/include/arch/cpu.h create mode 100644 na/arch/riscv/include/arch/mmu.h create mode 100644 na/arch/riscv/include/arch/trap.h create mode 100644 na/arch/riscv/include/arch/trapframe.h create mode 100644 na/arch/riscv/module.mk create mode 100644 na/arch/riscv/trap/swtch.S create mode 100644 na/arch/riscv/trap/trap_vector.S create mode 100644 na/include/lume/errno.h create mode 100644 na/include/lume/new.h create mode 100644 na/include/lume/panic.h create mode 100644 na/include/lume/types.h create mode 100644 na/kernel/main.cc create mode 100644 na/kernel/module.mk create mode 100644 na/kernel/sync/cpu_state.h create mode 100644 na/kernel/sync/spinlock.cc create mode 100644 na/kernel/sync/spinlock.h create mode 100644 na/lib/module.mk create mode 100644 na/lib/panic.cc create mode 100644 na/linker.ld diff --git a/include/lib/errno.h b/include/lib/errno.h index 62fd3e5..f0c1436 100644 --- a/include/lib/errno.h +++ b/include/lib/errno.h @@ -1,11 +1,11 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* - * Copyright (C) 2026 Richard Qin * Lume OS - Linux Compatibility Layer * * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). - * Reference: linux/include/uapi/asm-generic/errno-base.h - * Reference: linux/include/uapi/asm-generic/errno.h + * The definitions are copied from the Linux kernel's UAPI headers + * (include/uapi/asm-generic/errno-base.h and include/uapi/asm-generic/errno.h) + * to ensure ABI compatibility. Original copyrights apply. */ #ifndef _ERRNO_H diff --git a/include/ulib/errno.h b/include/ulib/errno.h index c1691db..dd51598 100644 --- a/include/ulib/errno.h +++ b/include/ulib/errno.h @@ -1,11 +1,11 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* - * Copyright (C) 2026 Richard Qin * Lume OS - Linux Compatibility Layer * * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). - * Reference: linux/include/uapi/asm-generic/errno-base.h - * Reference: linux/include/uapi/asm-generic/errno.h + * The definitions are copied from the Linux kernel's UAPI headers + * (include/uapi/asm-generic/errno-base.h and include/uapi/asm-generic/errno.h) + * to ensure ABI compatibility. Original copyrights apply. */ #ifndef _ERRNO_H diff --git a/na/Makefile b/na/Makefile new file mode 100644 index 0000000..3fc72b1 --- /dev/null +++ b/na/Makefile @@ -0,0 +1,158 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin +# +# LumeOS Kernel Makefile (Non-Recursive) +# +# Usage: +# make - Build kernel ELF +# make run - Run in QEMU +# make debug - Run in QEMU with GDB server +# make clean - Clean build artifacts +# make fmt - Format all source files +# make disasm - Generate disassembly listing +# +# Reference: docs/specs/00_conventions.md §7 + +# ============================================================ +# Toolchain Detection +# ============================================================ +ifndef TOOLCHAIN_PREFIX + TOOLCHAIN_PREFIX := $(shell \ + if command -v riscv64-linux-gnu-gcc >/dev/null 2>&1; then \ + echo riscv64-linux-gnu-; \ + elif command -v riscv64-unknown-linux-gnu-gcc >/dev/null 2>&1; then \ + echo riscv64-unknown-linux-gnu-; \ + elif command -v riscv64-unknown-elf-gcc >/dev/null 2>&1; then \ + echo riscv64-unknown-elf-; \ + else \ + echo riscv64-linux-gnu-; \ + fi) +endif + +CC := $(TOOLCHAIN_PREFIX)gcc +CXX := $(TOOLCHAIN_PREFIX)g++ +LD := $(TOOLCHAIN_PREFIX)ld +OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy +OBJDUMP := $(TOOLCHAIN_PREFIX)objdump + +# ============================================================ +# Build Configuration +# ============================================================ +ARCH := riscv +BUILD := build +TARGET := $(BUILD)/kernel.elf +IMAGE := $(BUILD)/kernel.bin +LINKER_SCRIPT := linker.ld + +# Common flags (base, before module includes) +WARNINGS := -Wall -Wextra -Wno-unused-parameter +INCLUDES := -Iinclude + +# ============================================================ +# Source Collection (Non-Recursive Module Fragments) +# Module .mk files may append to SRCS_CC, SRCS_S, INCLUDES +# ============================================================ +SRCS_CC := +SRCS_S := + +include arch/$(ARCH)/module.mk +include kernel/module.mk +# include mm/module.mk # Phase 1 +# include fs/module.mk # Phase 3 +# include drivers/module.mk # As needed +include lib/module.mk + +# Compiler flags (defined AFTER includes so INCLUDES is complete) +CFLAGS := $(WARNINGS) -O2 -g \ + -ffreestanding -nostdlib \ + -mcmodel=medany \ + -MMD -MP \ + $(INCLUDES) + +CXXFLAGS := $(CFLAGS) \ + -std=c++17 \ + -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ + -fno-threadsafe-statics + +ASFLAGS := $(CFLAGS) + +LDFLAGS := -nostdlib -static + +# ============================================================ +# Object File Computation +# ============================================================ +OBJS := $(patsubst %.cc,$(BUILD)/%.o,$(SRCS_CC)) \ + $(patsubst %.S,$(BUILD)/%.o,$(SRCS_S)) + +DEPS := $(OBJS:.o=.d) + +# ============================================================ +# Build Rules +# ============================================================ +.PHONY: all clean run debug fmt disasm + +all: $(IMAGE) + +# Link kernel ELF +$(TARGET): $(OBJS) $(LINKER_SCRIPT) + @echo " LD $@" + @mkdir -p $(dir $@) + @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJS) + @$(TOOLCHAIN_PREFIX)size $@ + +# Generate flat binary for QEMU loading +$(IMAGE): $(TARGET) + @echo " OBJCOPY $@" + @$(OBJCOPY) -O binary $< $@ + +# Compile C++ sources +$(BUILD)/%.o: %.cc + @echo " CXX $<" + @mkdir -p $(dir $@) + @$(CXX) $(CXXFLAGS) -c $< -o $@ + +# Assemble RISC-V assembly +$(BUILD)/%.o: %.S + @echo " AS $<" + @mkdir -p $(dir $@) + @$(CC) $(ASFLAGS) -c $< -o $@ + +# Auto-dependency tracking +-include $(DEPS) + +# ============================================================ +# Run & Debug +# ============================================================ +QEMU := qemu-system-riscv64 +QEMU_MACHINE := -machine virt -m 128M -nographic +QEMU_BIOS := -bios default + +run: $(IMAGE) + @echo " QEMU Running..." + $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(IMAGE) + +debug: $(IMAGE) + @echo " QEMU Waiting for GDB on :1234..." + $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(IMAGE) -s -S + +# ============================================================ +# Utilities +# ============================================================ +clean: + rm -rf $(BUILD) + +disasm: $(TARGET) + @echo " OBJDUMP $<" + @$(OBJDUMP) -d -S $(TARGET) > $(TARGET).asm + @echo " Written to $(TARGET).asm" + +fmt: + @echo " FMT Formatting source files..." + @find . -name '*.cc' -o -name '*.h' | xargs clang-format -i 2>/dev/null || true + +# Print build info (for debugging Makefile issues) +info: + @echo "TOOLCHAIN_PREFIX = $(TOOLCHAIN_PREFIX)" + @echo "SRCS_CC = $(SRCS_CC)" + @echo "SRCS_S = $(SRCS_S)" + @echo "OBJS = $(OBJS)" diff --git a/na/arch/riscv/boot/entry.S b/na/arch/riscv/boot/entry.S new file mode 100644 index 0000000..895c778 --- /dev/null +++ b/na/arch/riscv/boot/entry.S @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * LumeOS Kernel Entry Point (RISC-V 64) + * + * This file implements the earliest boot sequence: + * 1. RISC-V Linux Boot Protocol Image Header (64 bytes) + * 2. BSP/AP synchronization + * 3. BSS zeroing (BSP only) + * 4. Early page table setup (1GB superpages for identity + higher-half) + * 5. MMU enable and higher-half trampoline + * 6. C++ global constructor invocation + * 7. Jump to kernel_main() + * + * Register contract on entry (from OpenSBI / GRUB / U-Boot): + * a0 = CPU ID (Hart ID) + * a1 = FDT physical base address + * + * Reference: docs/specs/linker_and_entry.md + */ + +#define PAGE_OFFSET 0xFFFFFFC000000000 +#define BOOT_STACK_SIZE 8192 +#define NCPU_MAX 8 + +/* SV39 PTE flags */ +#define PTE_V (1 << 0) /* Valid */ +#define PTE_R (1 << 1) /* Readable */ +#define PTE_W (1 << 2) /* Writable */ +#define PTE_X (1 << 3) /* Executable */ +#define PTE_A (1 << 6) /* Accessed */ +#define PTE_D (1 << 7) /* Dirty */ + +/* SATP mode field for SV39 */ +#define SATP_SV39 (8UL << 60) + +/* ======================================================================== + * Section: .head.text — RISC-V Linux Boot Protocol Image Header + * Must be at byte 0 of the kernel binary. + * ======================================================================== */ + .section .head.text, "ax" + .globl _start +_start: + /* code0 (u32, offset 0): executable jump for OpenSBI bare boot */ + j real_start + /* code1 (u32, offset 4): reserved */ + .word 0 + /* text_offset (u64, offset 8): load offset from RAM base */ + .dword 0x200000 + /* image_size (u64, offset 16): will be filled post-link, 0 = unknown */ + .dword 0 + /* flags (u64, offset 24): bit 0: 0=LE, 1=BE */ + .dword 0 + /* version (u32, offset 32): header version 0.2 */ + .word 0x0002 + /* res1 (u32, offset 36) */ + .word 0 + /* res2 (u64, offset 40) */ + .dword 0 + /* magic (u64, offset 48): RISCV_IMAGE_MAGIC "RISCV\0\0\0" LE */ + .dword 0x5643534952 + /* magic2 (u32, offset 56): RISCV_IMAGE_MAGIC2 "RSC\x05" LE */ + .word 0x05435352 + /* res3 (u32, offset 60): reserved for PE/COFF */ + .word 0 + +/* ======================================================================== + * real_start: Actual boot logic begins here (all CPUs land here) + * ======================================================================== */ + .text + .globl real_start +real_start: + /* ---- Step 1: Disable interrupts ---- */ + csrw sie, zero + csrw sip, zero + + /* ---- Step 2: Set up gp (global pointer) ---- */ + /* Critical for medany model: all global variable access goes through gp */ + .option push + .option norelax + la gp, __global_pointer$ + .option pop + + /* ---- Step 3: Set up per-CPU boot stack ---- */ + /* sp = boot_stack + (cpu_id + 1) * BOOT_STACK_SIZE */ + la t0, boot_stack + li t1, BOOT_STACK_SIZE + addi t2, a0, 1 /* t2 = cpu_id + 1 */ + mul t1, t1, t2 /* t1 = (cpu_id + 1) * BOOT_STACK_SIZE */ + add sp, t0, t1 + + /* ---- Step 4: Store CPU ID in tp for arch::cpu::id() ---- */ + mv tp, a0 + + /* ---- Step 5: BSP / AP divergence ---- */ + /* CPU 0 is the BSP; all others spin-wait */ + bnez a0, .Lap_wait + +/* =================================================================== + * BSP-only path: Clear BSS, set up early page table, release APs + * =================================================================== */ +.Lbsp_init: + /* ---- 5.1: Clear BSS ---- */ + la t0, _sbss + la t1, _ebss +.Lbss_loop: + bgeu t0, t1, .Lbss_done + sd zero, 0(t0) + addi t0, t0, 8 + j .Lbss_loop +.Lbss_done: + + /* ---- 5.2: Build early page table (1GB superpages) ---- */ + /* + * SV39 root page table (512 entries x 8 bytes = 4KB) + * We need 4 entries: + * [0] = Identity map: 0x00000000 -> 0x00000000 (MMIO: UART, PLIC, CLINT) + * [2] = Identity map: 0x80000000 -> 0x80000000 (RAM) + * [256] = Higher-half: 0xFFFFFFC000000000 -> 0x00000000 (MMIO via VA) + * [258] = Higher-half: 0xFFFFFFC080000000 -> 0x80000000 (RAM via VA) + * + * VPN[2] index = (VA >> 30) & 0x1FF + * 0x00000000 -> 0 0xFFFFFFC000000000 -> 256 + * 0x80000000 -> 2 0xFFFFFFC080000000 -> 258 + */ + la t0, early_pgdir + + /* PTE flags: V|R|W|X|A|D (1GB leaf superpage) */ + li t2, (PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D) + + /* --- MMIO region: physical 0x00000000 --- */ + /* PPN = 0x00000000 >> 12 = 0, PTE = flags only */ + mv t1, t2 /* PTE = 0 | flags */ + + /* Entry [0]: Identity map MMIO */ + sd t1, (0 * 8)(t0) + + /* Entry [256]: Higher-half MMIO (0xFFFFFFC000000000) */ + li t3, (256 * 8) + add t3, t0, t3 + sd t1, 0(t3) + + /* --- RAM region: physical 0x80000000 --- */ + li t1, 0x80000 /* PPN = 0x80000000 >> 12 */ + slli t1, t1, 10 /* PPN field in PTE */ + or t1, t1, t2 /* t1 = complete PTE */ + + /* Entry [2]: Identity map RAM */ + sd t1, (2 * 8)(t0) + + /* Entry [258]: Higher-half RAM (0xFFFFFFC080000000) */ + li t3, (258 * 8) + add t3, t0, t3 + sd t1, 0(t3) + + /* ---- 5.3: Memory fence ---- */ + fence rw, rw + + /* ---- 5.4: Release APs ---- */ + la t0, boot_sync_flag + li t1, 1 + amoswap.w.rl zero, t1, (t0) + j .Lenable_mmu + +/* =================================================================== + * AP path: spin-wait until BSP signals readiness + * =================================================================== */ +.Lap_wait: + la t0, boot_sync_flag +.Lap_spin: + lw t1, 0(t0) + beqz t1, .Lap_spin + fence r, rw /* Acquire: ensure we see BSP's writes */ + +/* =================================================================== + * All CPUs: Enable MMU and jump to higher-half virtual addresses + * =================================================================== */ +.Lenable_mmu: + /* ---- 6.1: Write satp = SV39 mode | (early_pgdir >> 12) ---- */ + /* NOTE: `la` at this point is PC-relative. Since we are still executing + * at physical addresses, it yields the PHYSICAL address directly. + * No VA-to-PA conversion needed. */ + la t0, early_pgdir + srli t0, t0, 12 /* PPN = PA >> 12 */ + li t1, SATP_SV39 + or t0, t0, t1 /* satp = mode | PPN */ + csrw satp, t0 + + /* ---- 6.2: Flush TLB ---- */ + sfence.vma zero, zero + + /* ---- 6.3: Jump to higher-half virtual address ---- */ + /* Now we are executing via the identity map (PA == VA for 0x8...). + * Load the VA of the next instruction and jump there, + * so PC switches from physical to virtual address space. */ + la t0, .Lhigher_half + jr t0 + +.Lhigher_half: + /* We are now running at virtual addresses (0xFFFFFFC0...) ! + * The identity map is still active but will be removed by vmm_init(). */ + + /* ---- 6.4: Reload sp with virtual address ---- */ + /* Boot stack was set up using physical-linked VA already (linker placed + * boot_stack in higher-half), so sp is valid. Reload gp just in case. */ + .option push + .option norelax + la gp, __global_pointer$ + .option pop + + /* ---- Step 7: Save fdt_paddr, BSP calls early_cpp_init ---- */ + /* a1 (fdt_paddr) is caller-saved and may be clobbered by function calls. + * Save it in s0 (callee-saved) so it survives early_cpp_init. */ + mv s0, a1 /* s0 = fdt_paddr (preserved across calls) */ + + bnez tp, .Lskip_cpp_init + + /* BSP only: invoke C++ global constructors */ + call early_cpp_init + +.Lskip_cpp_init: + /* All CPUs: enter kernel_main(cpu_id, fdt_paddr) */ + mv a0, tp /* a0 = cpu_id (from tp) */ + mv a1, s0 /* a1 = fdt_paddr (from s0) */ + call kernel_main + + /* kernel_main should never return; if it does, halt */ +.Lhalt: + csrc sstatus, 2 /* Clear SIE */ + wfi + j .Lhalt + + +/* ======================================================================== + * early_cpp_init: Traverse .init_array and call global constructors + * Called by BSP only, before kernel_main. + * ======================================================================== */ + .globl early_cpp_init +early_cpp_init: + /* Prologue: save ra and callee-saved regs we use as loop state */ + addi sp, sp, -32 + sd ra, 0(sp) + sd s1, 8(sp) + sd s2, 16(sp) + + la s1, __init_array_start + la s2, __init_array_end +.Linit_loop: + bgeu s1, s2, .Linit_done + ld t0, 0(s1) /* Load function pointer */ + jalr ra, t0 /* Call constructor (sets ra = return addr) */ + addi s1, s1, 8 /* Next entry */ + j .Linit_loop +.Linit_done: + /* Epilogue: restore and return */ + ld ra, 0(sp) + ld s1, 8(sp) + ld s2, 16(sp) + addi sp, sp, 32 + ret + + +/* ======================================================================== + * BSS Data: boot_sync_flag + * ======================================================================== */ + .section .bss + .align 2 + .globl boot_sync_flag +boot_sync_flag: + .word 0 diff --git a/na/arch/riscv/include/arch/context.h b/na/arch/riscv/include/arch/context.h new file mode 100644 index 0000000..3d935fa --- /dev/null +++ b/na/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/na/arch/riscv/include/arch/cpu.h b/na/arch/riscv/include/arch/cpu.h new file mode 100644 index 0000000..644ae37 --- /dev/null +++ b/na/arch/riscv/include/arch/cpu.h @@ -0,0 +1,64 @@ +/* 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) */ + +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; +} + +} // namespace arch::cpu diff --git a/na/arch/riscv/include/arch/mmu.h b/na/arch/riscv/include/arch/mmu.h new file mode 100644 index 0000000..a0ee1ed --- /dev/null +++ b/na/arch/riscv/include/arch/mmu.h @@ -0,0 +1,63 @@ +/* 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; +} + +// 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"); +} + +} // namespace arch::mmu diff --git a/na/arch/riscv/include/arch/trap.h b/na/arch/riscv/include/arch/trap.h new file mode 100644 index 0000000..c99d8cb --- /dev/null +++ b/na/arch/riscv/include/arch/trap.h @@ -0,0 +1,82 @@ +/* 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; +} + +} // namespace arch::trap diff --git a/na/arch/riscv/include/arch/trapframe.h b/na/arch/riscv/include/arch/trapframe.h new file mode 100644 index 0000000..de49e9c --- /dev/null +++ b/na/arch/riscv/include/arch/trapframe.h @@ -0,0 +1,92 @@ +/* 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 + +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 + +#endif /* __ASSEMBLER__ */ diff --git a/na/arch/riscv/module.mk b/na/arch/riscv/module.mk new file mode 100644 index 0000000..85ed5dd --- /dev/null +++ b/na/arch/riscv/module.mk @@ -0,0 +1,7 @@ +# arch/riscv/module.mk +SRCS_S += arch/riscv/boot/entry.S +SRCS_S += arch/riscv/trap/swtch.S +SRCS_S += arch/riscv/trap/trap_vector.S + +# Arch-specific headers (added to include path) +INCLUDES += -Iarch/riscv/include diff --git a/na/arch/riscv/trap/swtch.S b/na/arch/riscv/trap/swtch.S new file mode 100644 index 0000000..4393505 --- /dev/null +++ b/na/arch/riscv/trap/swtch.S @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + + .section .text + .globl context_switch + .align 2 +# void context_switch(Context* old_ctx, Context* next_ctx); +# a0 = old_ctx_ptr, a1 = next_ctx_ptr +context_switch: + sd ra, 0(a0) + sd sp, 8(a0) + sd s0, 16(a0) + sd s1, 24(a0) + sd s2, 32(a0) + sd s3, 40(a0) + sd s4, 48(a0) + sd s5, 56(a0) + sd s6, 64(a0) + sd s7, 72(a0) + sd s8, 80(a0) + sd s9, 88(a0) + sd s10, 96(a0) + sd s11, 104(a0) + + ld ra, 0(a1) + ld sp, 8(a1) + ld s0, 16(a1) + ld s1, 24(a1) + ld s2, 32(a1) + ld s3, 40(a1) + ld s4, 48(a1) + ld s5, 56(a1) + ld s6, 64(a1) + ld s7, 72(a1) + ld s8, 80(a1) + ld s9, 88(a1) + ld s10, 96(a1) + ld s11, 104(a1) + + ret \ No newline at end of file diff --git a/na/arch/riscv/trap/trap_vector.S b/na/arch/riscv/trap/trap_vector.S new file mode 100644 index 0000000..f0b65b5 --- /dev/null +++ b/na/arch/riscv/trap/trap_vector.S @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Trap Vector Entry / Exit (RISC-V 64, S-mode) + * + * On trap entry (interrupt / exception / ecall): + * sscratch holds the TrapFrame pointer (set up by kernel before sret). + * We swap sscratch <-> tp, save all 31 GPRs + sstatus + sepc into TrapFrame, + * then load kernel metadata (sp, satp, tp, trap handler) and jump to C++. + * + * On trap return (userret): + * Restore all GPRs + sstatus + sepc from TrapFrame, swap sscratch <-> tp, + * then sret back to user mode. + * + * Layout must match arch::TrapFrame in arch/riscv/include/arch/trapframe.h + * Reference: docs/specs/arch_abstraction.md §7.2 + */ + +#include + + .section .text + .globl trap_vector + .globl userret + .align 4 + +/* + * trap_vector — unified S-mode trap entry point (Direct mode) + * + * Precondition: sscratch = pointer to current task's TrapFrame + */ +trap_vector: + /* Step 1: swap sscratch <-> tp. Now tp = TrapFrame*, sscratch = old tp */ + csrrw tp, sscratch, tp + + /* Step 2: save all 31 general-purpose registers into TrapFrame */ + sd ra, TF_RA(tp) + sd sp, TF_SP(tp) + sd gp, TF_GP(tp) + /* tp (x4) is in sscratch — save it later */ + sd t0, TF_T0(tp) + sd t1, TF_T1(tp) + sd t2, TF_T2(tp) + sd s0, TF_S0(tp) + sd s1, TF_S1(tp) + sd a0, TF_A0(tp) + sd a1, TF_A1(tp) + sd a2, TF_A2(tp) + sd a3, TF_A3(tp) + sd a4, TF_A4(tp) + sd a5, TF_A5(tp) + sd a6, TF_A6(tp) + sd a7, TF_A7(tp) + sd s2, TF_S2(tp) + sd s3, TF_S3(tp) + sd s4, TF_S4(tp) + sd s5, TF_S5(tp) + sd s6, TF_S6(tp) + sd s7, TF_S7(tp) + sd s8, TF_S8(tp) + sd s9, TF_S9(tp) + sd s10, TF_S10(tp) + sd s11, TF_S11(tp) + sd t3, TF_T3(tp) + sd t4, TF_T4(tp) + sd t5, TF_T5(tp) + sd t6, TF_T6(tp) + + /* Save the real tp (user's tp) which is now in sscratch */ + csrr t0, sscratch + sd t0, TF_TP(tp) + + /* Step 3: save sstatus */ + csrr t0, sstatus + sd t0, TF_SSTATUS(tp) + + /* Step 4: save sepc (interrupted PC) */ + csrr t0, sepc + sd t0, TF_EPC(tp) + + /* Step 5: load kernel context from TrapFrame metadata */ + ld sp, TF_KERNEL_SP(tp) /* switch to kernel stack */ + ld t1, TF_KERNEL_SATP(tp) /* kernel page table */ + ld t2, TF_KERNEL_CPUID(tp) /* CPU ID for tp */ + ld t3, TF_KERNEL_TRAP(tp) /* C++ trap_handler address */ + + /* Switch to kernel page table */ + csrw satp, t1 + + /* Step 6: flush TLB after page table switch */ + sfence.vma zero, zero + + /* Set tp = CPU ID (kernel convention) */ + mv tp, t2 + + /* Step 7: jump to C++ trap_handler */ + jalr t3 + + +/* + * userret — return to user mode + * + * Called from C++ as: userret(TrapFrame* tf) + * a0 = pointer to TrapFrame (with user satp already written by caller) + * + * Precondition: interrupts must be disabled by caller. + */ +userret: + /* a0 = TrapFrame pointer, use it as base */ + + /* Step 1: write back epc -> sepc */ + ld t0, TF_EPC(a0) + csrw sepc, t0 + + /* Step 2: write back sstatus */ + ld t0, TF_SSTATUS(a0) + csrw sstatus, t0 + + /* Step 3: caller is responsible for switching satp to user page table + * and flushing TLB before calling userret. */ + + /* Store TrapFrame address into sscratch for next trap entry */ + csrw sscratch, a0 + + /* Use a0 as base to restore all registers */ + mv tp, a0 + + /* Step 4: restore all 31 general-purpose registers */ + ld ra, TF_RA(tp) + ld sp, TF_SP(tp) + ld gp, TF_GP(tp) + /* defer tp restore — we're using it as base */ + ld t0, TF_T0(tp) + ld t1, TF_T1(tp) + ld t2, TF_T2(tp) + ld s0, TF_S0(tp) + ld s1, TF_S1(tp) + ld a0, TF_A0(tp) + ld a1, TF_A1(tp) + ld a2, TF_A2(tp) + ld a3, TF_A3(tp) + ld a4, TF_A4(tp) + ld a5, TF_A5(tp) + ld a6, TF_A6(tp) + ld a7, TF_A7(tp) + ld s2, TF_S2(tp) + ld s3, TF_S3(tp) + ld s4, TF_S4(tp) + ld s5, TF_S5(tp) + ld s6, TF_S6(tp) + ld s7, TF_S7(tp) + ld s8, TF_S8(tp) + ld s9, TF_S9(tp) + ld s10, TF_S10(tp) + ld s11, TF_S11(tp) + ld t3, TF_T3(tp) + ld t4, TF_T4(tp) + ld t5, TF_T5(tp) + ld t6, TF_T6(tp) + + /* Step 5: restore user tp (swap sscratch <-> tp) */ + /* sscratch currently = TrapFrame address, tp = TrapFrame address */ + /* We need: tp = user's tp, sscratch = TrapFrame address */ + /* Load user tp from TrapFrame first, but we can't use any temp reg now. + * Use the sscratch swap trick: */ + ld tp, TF_TP(tp) + + /* sscratch already holds TrapFrame address (set above with csrw) */ + + /* Step 6: sret — return to user mode */ + sret diff --git a/na/include/lume/errno.h b/na/include/lume/errno.h new file mode 100644 index 0000000..e0786ce --- /dev/null +++ b/na/include/lume/errno.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2026 Richard Qin */ +/* from linux kernel linux/include/uapi/asm-generic/errno-base.h*/ + +#ifndef _ASM_GENERIC_ERRNO_BASE_H +#define _ASM_GENERIC_ERRNO_BASE_H + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file number */ +#define ECHILD 10 /* No child processes */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* File table overflow */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Not a typewriter */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ +#define EDOM 33 /* Math argument out of domain of func */ +#define ERANGE 34 /* Math result not representable */ + +#endif \ No newline at end of file diff --git a/na/include/lume/new.h b/na/include/lume/new.h new file mode 100644 index 0000000..cdcacb8 --- /dev/null +++ b/na/include/lume/new.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Placement New for Freestanding Environments + * + * In -ffreestanding -nostdlib mode, may not be available. + * LumeOS provides its own placement new/delete operators. + * + * Usage: g_obj = new (g_obj_buf) MyClass(args...); + * + * Reference: docs/specs/global_init.md §3.1 + */ + +inline void* operator new(decltype(sizeof(0)), void* ptr) noexcept { return ptr; } +inline void operator delete(void*, void*) noexcept {} diff --git a/na/include/lume/panic.h b/na/include/lume/panic.h new file mode 100644 index 0000000..6316c28 --- /dev/null +++ b/na/include/lume/panic.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Kernel Panic — fatal error handler. + * + * Prints the message (if UART is ready), disables interrupts, + * and enters an infinite wfi loop. Never returns. + * + * Implementation will be provided by lib/kprintf.cc or a + * dedicated panic module. For now this header declares the interface. + */ + +extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail = nullptr); diff --git a/na/include/lume/types.h b/na/include/lume/types.h new file mode 100644 index 0000000..a8440f6 --- /dev/null +++ b/na/include/lume/types.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * LumeOS Base Type Definitions + * + * All kernel code should use these types instead of + * stdint.h (which may not exist in freestanding mode). + */ + +using uint8 = unsigned char; +using uint16 = unsigned short; +using uint32 = unsigned int; +using uint64 = unsigned long long; + +using int8 = signed char; +using int16 = signed short; +using int32 = signed int; +using int64 = signed long long; + +using size_t = uint64; +using ssize_t = int64; +using uintptr = uint64; + +/* Null pointer constant */ +#ifndef nullptr +/* C++11 and later have nullptr built-in */ +#endif + +/* Max CPUs supported */ +inline constexpr int kMaxCPUs = 8; + +/* Page size */ +inline constexpr uint64 kPageSize = 4096; + +/* Kernel virtual address offset */ +inline constexpr uint64 kPageOffset = 0xFFFFFFC000000000ULL; diff --git a/na/kernel/main.cc b/na/kernel/main.cc new file mode 100644 index 0000000..b4d792e --- /dev/null +++ b/na/kernel/main.cc @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * LumeOS Kernel Main Entry — Global Initialization Sequence + * + * All CPUs enter kernel_main() after the assembly bootstrap (entry.S) + * has enabled MMU, cleared BSS, and called early_cpp_init(). + * + * BSP (cpu_id == 0) executes the full subsystem initialization DAG. + * APs spin on ap_boot_sync, then perform per-CPU init only. + * + * Reference: docs/specs/global_init.md + */ + +#include +#include +#include + +// ============================================================ +// Forward declarations for subsystem init functions. +// Each is implemented by its owning module. On failure they +// call kernel_panic() directly — no error return. +// ============================================================ + +// BSP global init (strict order) +void console_init(); // 1. UART ready; kprintf/panic can output +void fdt_init(uint64 fdt_paddr); // 2. Parse FDT, build device node table +void pmm_init(); // 3. Buddy allocator ready +void slab_init(); // 4. kmem_cache ready; operator new usable +void vmm_init(); // 5. Fine-grained kernel page table; retire early_pgdir +void trap_init(); // 6. Set stvec, register IRQ dispatcher +void plic_init(); // 7. Configure PLIC (threshold, priority, enable) +void timer_init(); // 8. Configure timer interrupt (scheduler tick) +void sched_init(); // 9. Create BSP Idle Task, init ready queues + +// AP per-CPU init +void vmm_init_ap(); // Switch satp to BSP-built kernel page table +void trap_init_ap(); // Set this CPU's stvec +void plic_init_ap(); // Enable this CPU's PLIC context +void timer_init_ap(); // Start this CPU's timer interrupt +void sched_init_ap(); // Init per-CPU ready queue, create Idle Task + +// ============================================================ +// Second-stage synchronization barrier (C++ layer) +// AP cores spin here until BSP finishes all global init. +// ============================================================ +static volatile uint32 ap_boot_sync = 0; + +// Early UART output functions, defined in lib/panic.cc +// They are safe to use before console_init(). +extern "C" void early_putc(char c); +extern "C" void early_puts(const char* s); + +// ============================================================ +// kernel_main — C++ entry point for all CPUs +// ============================================================ +extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { + + if (cpu_id == 0) { + // ======================================================== + // BSP: Absolute single-core period — no locks needed. + // APs are spinning on ap_boot_sync. + // ======================================================== + + early_puts("\n\n"); + early_puts("====================================\n"); + early_puts(" LumeOS Kernel v0.1 (RISC-V 64)\n"); + early_puts("====================================\n"); + early_puts("[boot] BSP online, cpu_id=0\n"); + early_puts("[boot] Higher-half kernel active\n"); + + // --- Subsystem init DAG (strict top-down order) --- + + // 1. UART driver — after this, kernel_panic() can output + // console_init(); + early_puts("[init] console_init (stub)\n"); + + // 2. FDT parsing + // fdt_init(fdt_paddr); + (void)fdt_paddr; + early_puts("[init] fdt_init (stub)\n"); + + // 3. Physical memory manager (Buddy) + // pmm_init(); + early_puts("[init] pmm_init (stub)\n"); + + // 4. Slab allocator — operator new usable after this + // slab_init(); + early_puts("[init] slab_init (stub)\n"); + + // 5. Virtual memory manager — retire early_pgdir + // vmm_init(); + early_puts("[init] vmm_init (stub)\n"); + + // 6. Trap handler — set stvec + // trap_init(); + early_puts("[init] trap_init (stub)\n"); + + // 7. PLIC interrupt controller + // plic_init(); + early_puts("[init] plic_init (stub)\n"); + + // 8. Timer interrupt (scheduler tick source) + // timer_init(); + early_puts("[init] timer_init (stub)\n"); + + // 9. Scheduler — create Idle Task 0, init ready queues + // sched_init(); + early_puts("[init] sched_init (stub)\n"); + + early_puts("[boot] BSP init complete, releasing APs\n"); + + // Release APs with RELEASE semantics: + // guarantees all global data is visible to APs. + __atomic_store_n(&ap_boot_sync, 1, __ATOMIC_RELEASE); + + // BSP enables interrupts — first timer tick will preempt + // into the scheduler loop. + // arch::cpu::intr_on(); + + early_puts("[boot] BSP entering idle loop\n"); + + } else { + // ======================================================== + // AP: spin until BSP finishes global init + // ======================================================== + while (__atomic_load_n(&ap_boot_sync, __ATOMIC_ACQUIRE) == 0) + ; + + // --- Per-CPU init only (no global data mutation) --- + // vmm_init_ap(); + // trap_init_ap(); + // plic_init_ap(); + // timer_init_ap(); + // sched_init_ap(); + + // AP enables interrupts — becomes Idle Task on this CPU + // arch::cpu::intr_on(); + } + + // All CPUs: idle loop (wfi until interrupt) + while (true) { + arch::cpu::halt_until_interrupt(); + } +} diff --git a/na/kernel/module.mk b/na/kernel/module.mk new file mode 100644 index 0000000..208064f --- /dev/null +++ b/na/kernel/module.mk @@ -0,0 +1,3 @@ +# kernel/module.mk +SRCS_CC += kernel/main.cc +SRCS_CC += kernel/sync/spinlock.cc diff --git a/na/kernel/sync/cpu_state.h b/na/kernel/sync/cpu_state.h new file mode 100644 index 0000000..d0e3814 --- /dev/null +++ b/na/kernel/sync/cpu_state.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Per-CPU Interrupt Nesting Tracker + * + * Tracks spinlock nesting depth on each CPU to ensure interrupts + * are only restored when the last spinlock is released. + * + * BSS-segment global array; zero-initialized by entry.S BSS clear. + * Reference: docs/specs/spinlock.md §3.1 + */ + +#include + +namespace sync { + +struct CpuSyncState { + int lock_depth; // Number of spinlocks held by this CPU + bool intr_was_on; // Were interrupts enabled before the first lock? +}; + +inline constexpr uint64 kInvalidCpuId = ~0ULL; + +// BSS-segment global array, zero-initialized by entry.S +extern CpuSyncState g_cpu_sync_states[kMaxCPUs]; + +// Internal API — called only by Spinlock::acquire/release +void push_intr(); +void pop_intr(); + +} // namespace sync diff --git a/na/kernel/sync/spinlock.cc b/na/kernel/sync/spinlock.cc new file mode 100644 index 0000000..a03130d --- /dev/null +++ b/na/kernel/sync/spinlock.cc @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Spinlock Implementation + * + * TTAS (Test-and-Test-and-Set) spinlock with: + * - Acquire: __ATOMIC_ACQUIRE memory ordering + * - Release: __ATOMIC_RELEASE memory ordering + * - Automatic interrupt disable/enable via push_intr/pop_intr + * - Dead-lock and invalid-release detection via kernel_panic + * + * Reference: docs/specs/spinlock.md + */ + +#include "spinlock.h" +#include "cpu_state.h" +#include +#include + +// ============================================================ +// Per-CPU sync state (BSS zero-initialized) +// ============================================================ +namespace sync { + +CpuSyncState g_cpu_sync_states[kMaxCPUs]; + +void push_intr() { + bool was_on = arch::cpu::intr_enabled(); + arch::cpu::intr_off(); + + auto& state = g_cpu_sync_states[arch::cpu::id()]; + if (state.lock_depth == 0) { + state.intr_was_on = was_on; + } + state.lock_depth++; +} + +void pop_intr() { + auto& state = g_cpu_sync_states[arch::cpu::id()]; + state.lock_depth--; + + if (state.lock_depth == 0 && state.intr_was_on) { + arch::cpu::intr_on(); + } +} + +} // namespace sync + +// ============================================================ +// Spinlock methods +// ============================================================ + +void Spinlock::init(const char* name) { + name_ = name; + locked_ = 0; + cpu_id_ = sync::kInvalidCpuId; +} + +void Spinlock::acquire() { +#ifdef DEBUG + // Optional: detect interrupt-state leakage + if (arch::cpu::intr_enabled()) { + auto& state = sync::g_cpu_sync_states[arch::cpu::id()]; + if (state.lock_depth > 0) { + kernel_panic("spinlock: interrupts enabled while holding lock"); + } + } +#endif + + // Step 1: disable interrupts, track nesting depth + sync::push_intr(); + + // Step 2: dead-lock detection (same CPU re-acquiring) + if (is_held_by_current_cpu()) { + kernel_panic("spinlock: double lock", name_); + } + + // Step 3: TTAS spin loop + while (true) { + // Outer: spin on local cache copy (no bus traffic) + while (__atomic_load_n(&locked_, __ATOMIC_RELAXED)) + ; + // Inner: attempt atomic exchange with acquire semantics + if (__atomic_exchange_n(&locked_, 1, __ATOMIC_ACQUIRE) == 0) + break; + } + + // Step 4: record owner + cpu_id_ = arch::cpu::id(); +} + +void Spinlock::release() { + // Step 1: verify caller is the holder + if (!is_held_by_current_cpu()) { + kernel_panic("spinlock: invalid release", name_); + } + + // Step 2: clear owner + cpu_id_ = sync::kInvalidCpuId; + + // Step 3: release the lock with release semantics + __atomic_store_n(&locked_, 0, __ATOMIC_RELEASE); + + // Step 4: restore interrupt state if this was the last lock + sync::pop_intr(); +} + +bool Spinlock::is_held_by_current_cpu() const { + return cpu_id_ == arch::cpu::id(); +} diff --git a/na/kernel/sync/spinlock.h b/na/kernel/sync/spinlock.h new file mode 100644 index 0000000..b3e3393 --- /dev/null +++ b/na/kernel/sync/spinlock.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Spinlock & LockGuard + * + * Provides mutual exclusion for SMP kernels using TTAS + * (Test-and-Test-and-Set) with acquire/release memory ordering. + * Interrupt disable/enable is managed automatically via push_intr/pop_intr. + * + * Reference: docs/specs/spinlock.md + */ + +#include +#include "cpu_state.h" + +class Spinlock { +public: + // Default: BSS zero is safe. Call init() to give the lock a name. + Spinlock() = default; + + // Explicit initialization (for global objects or Placement New) + void init(const char* name); + + // Non-copyable, non-movable (lock identity is its address) + Spinlock(const Spinlock&) = delete; + Spinlock& operator=(const Spinlock&) = delete; + Spinlock(Spinlock&&) = delete; + Spinlock& operator=(Spinlock&&) = delete; + + void acquire(); + void release(); + bool is_held_by_current_cpu() const; + +private: + uint32 locked_ = 0; // 0 = free, 1 = held + const char* name_ = "uninit"; // Lock name (for panic messages) + uint64 cpu_id_ = sync::kInvalidCpuId; // Holder CPU ID +}; + +class LockGuard { +public: + explicit LockGuard(Spinlock& lock) : lock_(lock) { + lock_.acquire(); + } + + ~LockGuard() { + lock_.release(); + } + + LockGuard(const LockGuard&) = delete; + LockGuard& operator=(const LockGuard&) = delete; + LockGuard(LockGuard&&) = delete; + LockGuard& operator=(LockGuard&&) = delete; + +private: + Spinlock& lock_; +}; diff --git a/na/lib/module.mk b/na/lib/module.mk new file mode 100644 index 0000000..46c46d4 --- /dev/null +++ b/na/lib/module.mk @@ -0,0 +1,2 @@ +# lib/module.mk +SRCS_CC += lib/panic.cc diff --git a/na/lib/panic.cc b/na/lib/panic.cc new file mode 100644 index 0000000..4745272 --- /dev/null +++ b/na/lib/panic.cc @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Kernel Panic — minimal fatal error handler. + * + * Before console_init(): silent death (no UART). + * After console_init(): prints message then halts. + * + * Reference: docs/specs/global_init.md §6 + */ + +#include +#include + +// Minimal direct UART output (same as main.cc early boot) +// This can be used by other early-boot code before console_init() + +constexpr uint64 kUart0VA = 0x10000000UL + 0xFFFFFFC000000000ULL; + +extern "C" void early_putc(char c) { + volatile auto* uart = reinterpret_cast(kUart0VA); + *uart = static_cast(c); +} + +extern "C" void early_puts(const char* s) { + while (*s) early_putc(*s++); +} + +extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail) { + arch::cpu::intr_off(); + + early_puts("\n*** KERNEL PANIC: "); + if (msg) { + early_puts(msg); + } + if (detail) { + early_puts(": "); + early_puts(detail); + } + early_puts(" ***\n"); + + while (true) { + arch::cpu::halt_until_interrupt(); + } +} diff --git a/na/linker.ld b/na/linker.ld new file mode 100644 index 0000000..54f619c --- /dev/null +++ b/na/linker.ld @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * LumeOS Kernel Linker Script + * + * Memory Layout (SV39 Higher-Half Kernel): + * Virtual Base: 0xFFFFFFC080200000 + * Physical Base: 0x80200000 + * VA-PA Offset: 0xFFFFFFC000000000 (PAGE_OFFSET) + * + * The AT() directives produce correct LMA (Load Memory Address) so the + * bootloader loads sections at their physical addresses while the kernel + * code references their virtual addresses. + * + * Reference: docs/specs/linker_and_entry.md + */ + +OUTPUT_ARCH(riscv) + +PAGE_OFFSET = 0xFFFFFFC000000000; +BASE_ADDRESS = 0xFFFFFFC080200000; +PHYS_BASE = 0x80200000; + +/* ELF entry point must be the PHYSICAL address because OpenSBI/QEMU + * jumps to it before MMU is enabled. _start is at VMA, so we use + * the computed physical equivalent. */ +ENTRY(_start) + +SECTIONS { + . = BASE_ADDRESS; + + /* ===== .head.text: Image Header (must be at byte 0) ===== */ + .head.text : AT(PHYS_BASE) { + _start = .; + KEEP(*(.head.text)) + } + + /* ===== .text: Executable Code ===== */ + .text : AT(ADDR(.text) - PAGE_OFFSET) { + _stext = .; + *(.text .text.*) + _etext = .; + } = 0 + + . = ALIGN(4096); + + /* ===== .rodata: Read-Only Data ===== */ + .rodata : AT(ADDR(.rodata) - PAGE_OFFSET) { + _srodata = .; + *(.rodata .rodata.*) + + /* C++ global constructor table (.init_array) */ + . = ALIGN(8); + __init_array_start = .; + KEEP(*(.init_array .init_array.*)) + __init_array_end = .; + + _erodata = .; + } + + . = ALIGN(4096); + + /* ===== .data: Initialized Read-Write Data ===== */ + .data : AT(ADDR(.data) - PAGE_OFFSET) { + _sdata = .; + *(.data .data.*) + + /* GP anchor: RISC-V medany model uses gp-relative addressing + * for .sdata/.sbss within +/- 2KB of __global_pointer$ */ + . = ALIGN(16); + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.*) + + _edata = .; + } + + . = ALIGN(4096); + + /* ===== .bss: Uninitialized Data (zeroed by entry.S) ===== */ + .bss : AT(ADDR(.bss) - PAGE_OFFSET) { + _sbss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + + /* Early page table: 4KB aligned, used by entry.S before VMM */ + . = ALIGN(4096); + early_pgdir = .; + . += 4096; + + /* Per-CPU boot stacks: 8KB per CPU, max 8 CPUs */ + . = ALIGN(4096); + boot_stack = .; + . += 8192 * 8; /* NCPU_MAX = 8, BOOT_STACK_SIZE = 8KB */ + boot_stack_top = .; + + _ebss = .; + } + + . = ALIGN(4096); + + /* ===== End of kernel image ===== */ + _kernel_end = .; + + /* Discard unwanted sections */ + /DISCARD/ : { + *(.eh_frame .eh_frame_hdr) + *(.note .note.*) + *(.comment) + } +} \ No newline at end of file -- cgit v1.2.3