diff options
Diffstat (limited to 'na/arch/riscv/boot/entry.S')
| -rw-r--r-- | na/arch/riscv/boot/entry.S | 271 |
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 |
