diff options
Diffstat (limited to 'arch/riscv/kernel')
| -rw-r--r-- | arch/riscv/kernel/asm-offsets.c | 29 | ||||
| -rw-r--r-- | arch/riscv/kernel/head.S | 317 | ||||
| -rw-r--r-- | arch/riscv/kernel/lumeos.lds.S | 120 | ||||
| -rw-r--r-- | arch/riscv/kernel/signal.cc | 163 |
4 files changed, 629 insertions, 0 deletions
diff --git a/arch/riscv/kernel/asm-offsets.c b/arch/riscv/kernel/asm-offsets.c new file mode 100644 index 0000000..edecab7 --- /dev/null +++ b/arch/riscv/kernel/asm-offsets.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2026 Richard Qin +// +// arch/riscv/kernel/asm-offsets.c — emit struct field offsets and +// scalar constants for consumption by assembly sources (head.S etc.). +// +// 1:1 with Linux arch/riscv/kernel/asm-offsets.c. The Makefile +// compiles this to an .s file (not .o) and runs +// scripts/extract-asm-offsets.sed over the output to produce +// include/generated/asm-offsets.h. +// +// Adding a new offset: include the struct's header, add an OFFSET() +// or DEFINE() line below, rebuild. head.S can then reference the +// generated #define directly. + +#include <lume/kbuild.h> +#include <lume/kernel_map.h> + +void asm_offsets(void) +{ + COMMENT("LumeOS asm-offsets — struct kernel_mapping field offsets"); + OFFSET(KERNEL_MAP_VIRT_ADDR, kernel_mapping, virt_addr); + OFFSET(KERNEL_MAP_VIRT_OFFSET, kernel_mapping, virt_offset); + OFFSET(KERNEL_MAP_PHYS_ADDR, kernel_mapping, phys_addr); + OFFSET(KERNEL_MAP_SIZE, kernel_mapping, size); + OFFSET(KERNEL_MAP_VA_PA_OFFSET, kernel_mapping, va_pa_offset); + OFFSET(KERNEL_MAP_PAGE_OFFSET, kernel_mapping, page_offset); + OFFSET(KERNEL_MAP_VA_KERNEL_PA_OFFSET, kernel_mapping, va_kernel_pa_offset); +} diff --git a/arch/riscv/kernel/head.S b/arch/riscv/kernel/head.S new file mode 100644 index 0000000..e04c44c --- /dev/null +++ b/arch/riscv/kernel/head.S @@ -0,0 +1,317 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * RISC-V kernel entry — Linux-aligned layout. + * + * Flow (mirrors arch/riscv/kernel/head.S in Linux): + * + * _start [.head.text, byte 0 of image] + * · 64-byte Boot Protocol Image Header + * · j real_start + * + * real_start [.text] + * · save a0 (hartid) / a1 (dtb_pa) + * · setup gp / per-hart sp + * · BSP/AP election via amoswap + * · BSP: clear BSS, call setup_vm, then relocate_enable_mmu + * · AP: spin-wait, then relocate_enable_mmu + * + * relocate_enable_mmu [C function, arch/riscv/mm/init.cc] + * · csrw satp <- trampoline_pg_dir + * · jump to higher-half VA + * · csrw satp <- early_pg_dir + * · slide sp/ra/gp to higher-half + * · call relocate_kernel (walks .rela.dyn, patches R_RISCV_RELATIVE) + * + * real_start (continued in higher-half) + * · BSP only: call early_cpp_init (walks .init_array) + * · call kernel_main(hartid, dtb_pa) + * + * Register contract from OpenSBI / U-Boot: + * a0 = hart ID + * a1 = FDT physical base + */ + +#include <lume/config.h> + +#define PAGE_OFFSET 0xFFFFFFC000000000 +#define BOOT_STACK_SIZE 8192 + +/* ======================================================================== + * .head.text — RISC-V Boot Protocol Image Header + * + * Layout per Documentation/arch/riscv/boot-image-header.rst: + * code0/code1 (8 B) : executable code, lands at PC after OpenSBI hand-off + * text_offset (8 B) : load offset from RAM base + * image_size (8 B) : effective image size (0 = unknown) + * flags (8 B) : bit 0: endian + * version (4 B) : major.minor + * res1/res2 (4+8) : reserved + * magic (8 B) : "RISCV\0\0\0" + * magic2 (4 B) : "RSC\x05" + * res3 (4 B) : PE/COFF reserved + * ======================================================================== */ + .section .head.text, "ax" + .globl _start +_start: + j real_start /* code0 */ + .word 0 /* code1 */ + .dword 0x200000 /* text_offset */ + .dword 0 /* image_size (post-link) */ + .dword 0 /* flags */ + .word 0x0002 /* version 0.2 */ + .word 0 /* res1 */ + .dword 0 /* res2 */ + .dword 0x5643534952 /* magic "RISCV\0\0\0" */ + .word 0x05435352 /* magic2 "RSC\x05" */ + .word 0 /* res3 */ + +/* ======================================================================== + * real_start — all harts land here at physical PC + * ======================================================================== */ + .text + .globl real_start +real_start: + /* DEBUG: 'R<hartid>\n' — UART direct at 0x10000000, MMU off. */ + li t0, 0x10000000 + li t1, 'R' + sb t1, 0(t0) + addi t1, a0, '0' + sb t1, 0(t0) + li t1, '\n' + sb t1, 0(t0) + + /* Mask interrupts and supervisor pending bits. */ + csrw sie, zero + csrw sip, zero + + /* gp for medany — relax must be disabled across the `la`. */ + .option push + .option norelax + la gp, __global_pointer$ + .option pop + + /* per-hart boot stack: sp = boot_stack + (hartid + 1) * STACK_SIZE */ + la t0, boot_stack + li t1, BOOT_STACK_SIZE + addi t2, a0, 1 + mul t1, t1, t2 + add sp, t0, t1 + + /* Save hartid / dtb_pa in callee-saved regs preserved across calls. */ + mv tp, a0 /* tp = hartid */ + mv s0, a1 /* s0 = dtb_pa */ + + /* BSP/AP election: first hart to swap bsp_elected 0→1 is BSP. */ + la t0, bsp_elected + li t1, 1 + amoswap.w.aq t2, t1, (t0) + bnez t2, .Lap_wait /* prev != 0 → we're an AP */ + + /* === BSP path === */ + la t0, bsp_hart_id + sw a0, 0(t0) + + li t3, 0x10000000 + li t4, 'B' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Clear BSS — must precede setup_vm, which writes kernel_map. */ + 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: + + li t3, 0x10000000 + li t4, 'V' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* ---- BSP-only: pre-MMU PIC fixup of .rela.dyn ---- + * + * The kernel is built with -fPIE -shared. All global-data accesses + * compile to GOT-indirect loads (auipc + ld), where the linker + * emits each .got slot's initial value as the link-time VA and a + * companion R_RISCV_RELATIVE entry in .rela.dyn. At this point + * we're running at the physical load address — those link-time VAs + * are not yet mapped, so any global access would fault. + * + * Walk .rela.dyn in pure assembly (PC-relative only — no globals) + * and patch every R_RISCV_RELATIVE slot to addend + (PA - LINK_VA). + * After this, setup_vm can dereference kernel_map / early_pg_dir + * / etc. via the GOT and get correct PA-side pointers. + * + * Mirrors Linux arch/riscv/kernel/head.S — same idea, same loop. + * + * reloc: + * t0 = pa_offset = PA(_start) - KERNEL_LINK_ADDR (negative) + * t1 = &__rela_dyn_start + * t2 = &__rela_dyn_end + * for each 24-byte rela in [t1, t2): + * t3 = r_offset; t4 = r_info; t5 = r_addend + * if (t4 & 0xFFFFFFFF) != R_RISCV_RELATIVE: skip + * slot_pa = r_offset + pa_offset + * *slot_pa = r_addend + pa_offset + */ +#define R_RISCV_RELATIVE 3 + + la t0, _start /* PA of _start */ + li t1, 0xFFFFFFC080200000 /* KERNEL_LINK_ADDR (asm/page.h) */ + sub t0, t0, t1 /* t0 = pa_offset (PA - LINK_VA) */ + + la t1, __rela_dyn_start + la t2, __rela_dyn_end +.Lrela_loop: + bgeu t1, t2, .Lrela_done + ld t3, 0(t1) /* r_offset */ + ld t4, 8(t1) /* r_info */ + ld t5, 16(t1) /* r_addend */ + /* r_info low 32 bits = type */ + li t6, 0xFFFFFFFF + and t4, t4, t6 + li t6, R_RISCV_RELATIVE + bne t4, t6, .Lrela_next + /* slot_pa = r_offset + pa_offset */ + add t3, t3, t0 + /* value = r_addend + pa_offset */ + add t5, t5, t0 + sd t5, 0(t3) +.Lrela_next: + addi t1, t1, 24 /* sizeof(Elf64_Rela) */ + j .Lrela_loop +.Lrela_done: + + /* Memory fence so subsequent loads see the patched slots. */ + fence w, r + + li t3, 0x10000000 + li t4, 'P' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Call setup_vm(load_pa = PA of _start, dtb_pa = s0). + * Both args physical at this point — MMU still off. */ + la a0, _start + mv a1, s0 + call setup_vm + + li t3, 0x10000000 + li t4, 'v' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Release APs once page tables are built. */ + la t0, boot_sync_flag + li t1, 1 + amoswap.w.rl zero, t1, (t0) + j .Lenable_mmu + +/* ======================================================================== + * AP path — spin until BSP signals readiness. + * ======================================================================== */ +.Lap_wait: + la t0, boot_sync_flag +.Lap_spin: + lw t1, 0(t0) + beqz t1, .Lap_spin + fence r, rw + + /* fall through */ + +/* ======================================================================== + * .Lenable_mmu — all harts: enable MMU, jump to higher-half, run reloc. + * + * relocate_enable_mmu does its own SATP write + higher-half jump in + * C (with inline asm), then calls relocate_kernel. After it returns, + * we are at higher-half VA with sp / ra / gp slid up, and .rela.dyn + * is applied (so .init_array pointers are valid). + * ======================================================================== */ +.Lenable_mmu: + la a0, early_pg_dir + call relocate_enable_mmu + /* On return: PC, sp, ra, gp are all at higher-half VA. */ + + li t3, 0x10000000 + li t4, 'M' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Mark MMU enabled for console / pmap layers that check this. */ + call console_set_mmu_enabled + + /* BSP only: walk __init_array and call each C++ ctor. */ + la t0, bsp_hart_id + lw t0, 0(t0) + bne tp, t0, .Lskip_cpp_init + call early_cpp_init +.Lskip_cpp_init: + + /* Hand off to kernel_main(hartid, dtb_pa). */ + mv a0, tp + mv a1, s0 + call kernel_main + + /* kernel_main should not return. */ +.Lhalt: + csrc sstatus, 2 + wfi + j .Lhalt + +/* ======================================================================== + * early_cpp_init — walk .init_array and call each constructor. + * + * Called only by BSP, after relocate_kernel has patched the function + * pointers stored in __init_array. Before relocate_kernel runs those + * pointers are zero-filled by the linker. + * ======================================================================== */ + .globl early_cpp_init +early_cpp_init: + 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) + jalr ra, t0 + addi s1, s1, 8 + j .Linit_loop +.Linit_done: + ld ra, 0(sp) + ld s1, 8(sp) + ld s2, 16(sp) + addi sp, sp, 32 + ret + +/* ======================================================================== + * Election state — in .data so explicit zero survives the BSP BSS clear + * (these are read before BSS is cleared on the BSP, and concurrently by + * APs while BSP is in the clear loop). + * ======================================================================== */ + .section .data + .align 2 + .globl boot_sync_flag +boot_sync_flag: + .word 0 + + .globl bsp_elected +bsp_elected: + .word 0 + + .globl bsp_hart_id +bsp_hart_id: + .word 0 diff --git a/arch/riscv/kernel/lumeos.lds.S b/arch/riscv/kernel/lumeos.lds.S new file mode 100644 index 0000000..9bd083b --- /dev/null +++ b/arch/riscv/kernel/lumeos.lds.S @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * RISC-V Kernel Linker Script Template + * + * This is a C-preprocessed linker script (.lds.S). The build system + * runs it through CPP to produce the final vmlinux.lds which is + * passed to the linker. + * + * Architecture-specific definitions: + * - OUTPUT_ARCH, ENTRY + * - PAGE_OFFSET, BASE_ADDRESS, PHYS_BASE + * - .head.text placement + * - GP anchor (__global_pointer$ for RISC-V medany) + * - early_pgdir and boot stacks in .bss + * + * Common section macros come from include/lume/vmlinux.lds.h + * + * Reference: docs/specs/linker_and_entry.md + */ + +#include <lume/lumeos.lds.h> + +/* ===== RISC-V Architecture Constants ===== */ + +OUTPUT_ARCH(riscv) + +PAGE_OFFSET = 0xFFFFFFC000000000; +BASE_ADDRESS = 0xFFFFFFC080200000; +PHYS_BASE = 0x80200000; + +ENTRY(_start) + +/* ===== RISC-V: GP anchor in .data (medany model) ===== */ +#define ARCH_DATA_EXTRA \ + . = ALIGN(16); \ + PROVIDE(__global_pointer$ = . + 0x800); \ + *(.sdata .sdata.*) + +/* ===== RISC-V: page-table storage + per-CPU boot stacks in .bss ===== + * + * Linux RISC-V layout (arch/riscv/kernel/vmlinux.lds.S): + * early_pg_dir — first early page table built by setup_vm, + * used for the rest of boot after MMU enable + * trampoline_pg_dir — single-page table used only during the SATP + * switch from physical to higher-half VA; its + * identity + virt mappings of _start must both + * be valid in the same instruction window + * fixmap_pg_dir — covers the FIXADDR_START range used by the + * early console (UART MMIO before kernel page + * table proper is built) + * + * All three are allocated here so head.S can take their addresses without + * a C definition; their PA == VA mapping covers them during setup_vm. + */ +#define ARCH_BSS_EXTRA \ + . = ALIGN(4096); \ + early_pg_dir = .; \ + . += 4096; \ + trampoline_pg_dir = .; \ + . += 4096; \ + fixmap_pg_dir = .; \ + . += 4096; \ + /* Legacy name retained for any caller still referencing it; */ \ + /* points at the same first 4 KiB as early_pg_dir. */ \ + early_pgdir = early_pg_dir; \ + /* Per-CPU boot stacks: 8KB per CPU, max 8 CPUs */ \ + . = ALIGN(4096); \ + boot_stack = .; \ + . += 8192 * 8; \ + boot_stack_top = .; + +/* ===== Layout ===== */ + +SECTIONS { + . = BASE_ADDRESS; + + /* .head.text: Image Header (must be at byte 0 of the kernel image) */ + .head.text : AT(PHYS_BASE) { + _start = .; + KEEP(*(.head.text)) + } + + TEXT_SECTION + + . = ALIGN(4096); + + /* .rela.dyn lives between .text and .rodata so it ends up in the + * read-only PT_LOAD segment. Mirrors Linux vmlinux.lds.S where + * .rela.dyn sits at the tail of init data, just before RO_DATA. */ + RELA_DYN_SECTION + + . = ALIGN(4096); + + RODATA_SECTION + + . = ALIGN(4096); + + DATA_SECTION + + . = ALIGN(8); + + /* .got lives right after .data in the RW PT_LOAD. All other + * dynamic-linker artefacts (.plt, .dynamic, .dynsym, .dynstr, + * .hash, .gnu.hash, .interp, .dynbss) are discarded by the + * DISCARDS macro — mirrors Linux arch/riscv/kernel/vmlinux.lds.S. */ + GOT_SECTION + + . = ALIGN(4096); + + /* End of file-backed image (BSS follows as NOBITS) */ + __lume_image_end = .; + + BSS_SECTION + + KERNEL_END + + DISCARDS +} diff --git a/arch/riscv/kernel/signal.cc b/arch/riscv/kernel/signal.cc new file mode 100644 index 0000000..39fe34e --- /dev/null +++ b/arch/riscv/kernel/signal.cc @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * arch/riscv/signal.cc — Signal frame construction and sigreturn + * + * Constructs a signal frame on the user stack before returning to + * the user signal handler. On sigreturn, restores the original + * registers from the saved frame. + * + * Frame layout on user stack (grows downward): + * + * +-----------------------+ <- original sp + * | sigset_t saved_mask | 8 bytes + * | TrapFrame saved_regs | 296 bytes + * | kernel_siginfo info | 128 bytes + * +-----------------------+ <- new sp (16-byte aligned) + * + * Reference: Linux arch/riscv/kernel/signal.c + */ + +#include <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/signal_nr.h> +#include <lume/task.h> +#include <lume/uaccess.h> +#include <lume/errno.h> +#include <lume/klog.h> +#include <arch/trapframe.h> + +/* ================================================================== */ +/* Signal frame layout */ +/* ================================================================== */ + +struct rt_sigframe { + kernel_siginfo info; /* siginfo for SA_SIGINFO handlers */ + arch::TrapFrame saved_regs; /* saved register state */ + sigset_t saved_mask; /* blocked mask to restore on sigreturn */ +}; + +/* ================================================================== */ +/* Signal trampoline (written to user stack) */ +/* ================================================================== */ + +/* + * When the user's signal handler returns (via `ret`), ra points here. + * This tiny code sequence invokes sys_rt_sigreturn to restore the + * original execution context. + * + * RISC-V instructions (8 bytes): + * li a7, 139 → addi a7, zero, 139 → 0x08b00893 + * ecall → 0x00000073 + */ +static constexpr uint32 sigreturn_trampoline[2] = { + 0x08b00893, /* li a7, 139 (SYS_rt_sigreturn) */ + 0x00000073, /* ecall */ +}; + +/* ================================================================== */ +/* setup_signal_frame */ +/* ================================================================== */ + +int setup_signal_frame(arch::TrapFrame* tf, int signo, + kernel_sigaction* ka, kernel_siginfo* info) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return -EINVAL; + + /* Calculate frame location on user stack */ + uint64 sp = tf->sp; + + /* Reserve space for the trampoline below the frame */ + sp -= sizeof(sigreturn_trampoline); + sp &= ~15ULL; /* 16-byte align */ + uint64 trampoline_addr = sp; + + /* Reserve space for the signal frame */ + sp -= sizeof(rt_sigframe); + sp &= ~15ULL; /* 16-byte align */ + uint64 frame_addr = sp; + + /* Build the frame in kernel memory, then copy to user stack */ + rt_sigframe frame{}; + if (info) { + frame.info = *info; + } + frame.saved_regs = *tf; + frame.saved_mask = curr->blocked; + + /* Copy frame to user stack */ + int err = copy_to_user(frame_addr, &frame, sizeof(frame)); + if (err < 0) return -EFAULT; + + /* Copy trampoline to user stack */ + err = copy_to_user(trampoline_addr, sigreturn_trampoline, + sizeof(sigreturn_trampoline)); + if (err < 0) return -EFAULT; + + /* Set up the trap frame to execute the signal handler */ + tf->epc = ka->sa_handler; + tf->sp = frame_addr; + + /* Arguments to the handler: handler(int signo, siginfo_t*, ucontext_t*) */ + tf->a[0] = static_cast<uint64>(signo); + + if (ka->sa_flags & SA_SIGINFO) { + /* SA_SIGINFO: pass siginfo and ucontext pointers */ + tf->a[1] = frame_addr; /* &info */ + tf->a[2] = frame_addr + sizeof(kernel_siginfo); /* &saved_regs (as ucontext) */ + } else { + tf->a[1] = 0; + tf->a[2] = 0; + } + + /* Return address: when the handler does `ret`, jump to trampoline */ + if (ka->sa_flags & SA_RESTORER) { + tf->ra = ka->sa_restorer; + } else { + tf->ra = trampoline_addr; + } + + return 0; +} + +/* ================================================================== */ +/* arch_sigreturn — restore state from signal frame */ +/* ================================================================== */ + +int64 arch_sigreturn(arch::TrapFrame* tf) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return -EINVAL; + + /* + * The signal frame is located at the current sp. + * We need to read it back and restore the original state. + */ + uint64 frame_addr = tf->sp; + + rt_sigframe frame{}; + int err = copy_from_user(&frame, frame_addr, sizeof(frame)); + if (err < 0) { + printk(KERN_ERR "sigreturn: failed to read signal frame at %llx\n", + frame_addr); + task_kill_current(SIGSEGV); + return -EFAULT; + } + + /* Restore the saved register state */ + *tf = frame.saved_regs; + + /* Restore the blocked mask */ + curr->blocked = frame.saved_mask; + curr->blocked.del(SIGKILL); + curr->blocked.del(SIGSTOP); + + /* + * Return value: we return the saved a0 from the original context. + * The syscall dispatch will NOT overwrite a0 because sigreturn + * is special — it returns via the restored trap frame directly. + */ + return tf->a[0]; +} |
