summaryrefslogtreecommitdiff
path: root/na/arch/riscv/boot/entry.S
diff options
context:
space:
mode:
Diffstat (limited to 'na/arch/riscv/boot/entry.S')
-rw-r--r--na/arch/riscv/boot/entry.S271
1 files changed, 271 insertions, 0 deletions
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