diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:24:56 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:24:56 +0800 |
| commit | 1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (patch) | |
| tree | 0f5e01cd7c7a05abb6b1c7472d7f00f49caf02cc /na/kernel/main.cc | |
| parent | 63dc025705f6533b91fb1a7d26e6ed864efd9900 (diff) | |
| download | lume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.gz lume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.xz | |
feat: establish bootable new architecture checkpoint
Replace the compressed boot path with a relocatable higher-half RISC-V entry, expand memory and SMP support, and add the driver, TTY, VFS, exec, signal, syscall, initramfs, and self-test infrastructure needed for a bootable kernel checkpoint.
Diffstat (limited to 'na/kernel/main.cc')
| -rw-r--r-- | na/kernel/main.cc | 395 |
1 files changed, 322 insertions, 73 deletions
diff --git a/na/kernel/main.cc b/na/kernel/main.cc index f28d078..db5843c 100644 --- a/na/kernel/main.cc +++ b/na/kernel/main.cc @@ -10,26 +10,50 @@ * BSP (cpu_id == 0) executes the full subsystem initialization DAG. * APs spin on ap_boot_sync, then perform per-CPU init only. * + * Boot DAG invariants (docs/specs/linux_abi_mm_plan.md §Boot Sequence): + * - trap_init() MUST precede any allocator (exception safety net) + * - sched_init() MUST precede timer_init() (software before hardware) + * - PID 1 (init) is spawned before entering idle loop + * * Reference: docs/specs/global_init.md */ +#include <arch/config.h> #include <arch/cpu.h> +#include <arch/extable.h> +#include <arch/sbi.h> +#include <lume/addr.h> #include <lume/atomic.h> -#include <lume/boot_params.h> #include <lume/console.h> #include <lume/driver.h> #include <lume/fdt.h> +#include <lume/fdtable.h> +#include <lume/fs_struct.h> +#include <lume/fs_types.h> #include <lume/irq.h> +#include <lume/kernel_map.h> #include <lume/klog.h> #include <lume/kprintf.h> #include <lume/memblock.h> +#include <lume/mount.h> +#include <lume/page_cache.h> #include <lume/pmm.h> #include <lume/random.h> #include <lume/sched.h> +#include <lume/smp.h> +#include <lume/task.h> +#include <lume/pid.h> +#include <lume/pgrp.h> +#include <lume/signal.h> +#include <lume/signal_types.h> #include <lume/trap.h> #include <lume/types.h> -#include <arch/config.h> -#include <arch/extable.h> + +// Declared by entry.S: the hart ID that won BSP election +extern "C" uint32 bsp_hart_id; + +// Linker symbol: kernel entry point (physical start address) +extern "C" char _start[]; // ============================================================ // Forward declarations for subsystem init functions. @@ -42,11 +66,20 @@ 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 task_init(); // 7. PID allocator + global task list -void timer_init(); // 8. Configure timer interrupt (scheduler tick) -void sched_init(); // 9. Create BSP Idle Task, init ready queues -void syscall_init(); // 10. Register syscall jump table +void vmm_init(); // 5. Fine-grained kernel page table; retire early_pgdir +void task_init(); // 6. PID allocator + global task list +void pgrp_init(); // 6.5. Process group hash table +void sched_init(); // 7. Create BSP Idle Task, init ready queues +void timer_init(); // 8. Configure timer interrupt (scheduler tick) +void syscall_init(); // 9. Register syscall jump table +void super_init(); // 10. VFS superblock registry +void ramfs_init(); // 11. RamFS filesystem registration +void vfs_init(); // 12. Mount root filesystem +void cdev_init(); // 13. Character device registry +void console_cdev_init(); // 14. Register console/null/zero cdevs +void devfs_init(); // 15. Create /dev and populate nodes +void tty_init(); // 15.5. TTY subsystem (console TTY + UART RX IRQ) +void populate_rootfs(); // 15.6. Unpack embedded initramfs CPIO into ramfs // Boot self-test runner void selftest_run_all(); @@ -56,6 +89,102 @@ void vmm_init_ap(); // Switch to BSP-built kernel page table void timer_init_ap(); // Start this CPU's timer interrupt void sched_init_ap(); // Init per-CPU ready queue, create Idle Task +// kthread_create from task.cc +extern TaskControlBlock *kthread_create(const char *name, void (*entry)(void *), + void *arg, uint8 priority); + +// ============================================================ +// PID 1 — init thread +// ============================================================ +static void init_thread_func(void *) { + printk(KERN_INFO "[init] PID 1 started\n"); + + /* Run post-SMP tests — APs are online by now */ + extern void selftest_smp(); + kprintf("\n========================================\n"); + kprintf(" Post-SMP Test Suite\n"); + kprintf("========================================\n"); + selftest_smp(); + kprintf("========================================\n"); + kprintf(" Post-SMP tests PASSED\n"); + kprintf("========================================\n\n"); + + /* + * Set up init's own VFS context (like Linux kernel_init). + * kthread_create does NOT inherit files/fs, so init creates its own. + */ + TaskControlBlock *self = current_task(); + + if (!self->files) { + self->files = alloc_files_struct(); + if (!self->files) { + printk(KERN_ERR "[init] failed to allocate files_struct\n"); + goto out; + } + } + if (!self->fs) { + self->fs = alloc_fs_struct(); + if (!self->fs) { + printk(KERN_ERR "[init] failed to allocate fs_struct\n"); + goto out; + } + /* Point to the global root filesystem */ + self->fs->root.de = g_root_dentry; + self->fs->root.mnt = g_root_mnt; + self->fs->pwd = self->fs->root; + } + + /* Open /dev/console → fd 0 (stdin), dup to fd 1, 2 */ + { + extern int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); + extern int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); + + int64 fd0 = + sys_openat(static_cast<uint64>(-100), /* AT_FDCWD */ + reinterpret_cast<uint64>("/dev/console"), O_RDWR, 0, 0, 0); + if (fd0 < 0) { + printk(KERN_ERR "[init] open /dev/console failed: %d\n", + static_cast<int>(fd0)); + } else { + sys_dup(static_cast<uint64>(fd0), 0, 0, 0, 0, 0); /* fd 1 */ + sys_dup(static_cast<uint64>(fd0), 0, 0, 0, 0, 0); /* fd 2 */ + printk(KERN_INFO "[init] fd 0/1/2 -> /dev/console\n"); + } + } + + /* + * Try to exec /sbin/init (like Linux kernel_init). + * If not found, fall through to idle. + */ + { + extern int kernel_execve(const char *, const char *const *, + const char *const *); + + const char *init_paths[] = {"/sbin/init", "/etc/init", "/bin/init", + "/bin/sh", nullptr}; + const char *argv[] = {"/sbin/init", nullptr}; + const char *envp[] = {"HOME=/", "PATH=/sbin:/bin", nullptr}; + + for (int i = 0; init_paths[i]; i++) { + argv[0] = init_paths[i]; + int ret = kernel_execve(init_paths[i], argv, envp); + if (ret == 0) { + /* Success — do_execve modified our trapframe. + * When this kthread returns through the trap return path, + * it will jump to user space. */ + return; + } + printk(KERN_INFO "[init] exec %s failed: %d\n", init_paths[i], ret); + } + + printk(KERN_ERR "[init] No init found! Kernel panic.\n"); + } + +out: + extern void task_exit(int code); + task_exit(0); +} + // ============================================================ // Second-stage synchronization barrier (C++ layer) // AP cores spin here until BSP finishes all global init. @@ -67,61 +196,46 @@ static lume::atomic<uint32> ap_boot_sync; // ============================================================ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { - if (cpu_id == 0) { + if (cpu_id == bsp_hart_id) { // ======================================================== // BSP: Absolute single-core period — no locks needed. // APs are spinning on ap_boot_sync. // ======================================================== - boot_params_init_fdt(fdt_paddr); - const BootParams* bp = boot_params_get(); - uint64 fdt_pa = bp ? bp->fdt_ptr : fdt_paddr; + uint64 fdt_pa = fdt_paddr; + + // ---- Phase 1: Console + Exception Safety Net ---- - // Early console setup: discover UART via FDT before ANY output. + // 1. Early console setup: discover UART via FDT before ANY output. console_early_init(fdt_pa); klog_init(); printk("\n"); printk(KERN_INFO "LumeOS Kernel v0.1\n"); - printk(KERN_INFO "boot: BSP online, cpu_id=0\n"); + printk(KERN_INFO "boot: BSP online, cpu_id=%llu\n", cpu_id); printk(KERN_INFO "boot: higher-half kernel active\n"); - BOOT_STAGE(0, "firmware handoff"); - BOOT_STAGE(1, "bootloader handoff"); - BOOT_STAGE(2, "arch early asm complete"); - if (bp) { - if (bp->kimage_flags & BOOT_IMAGE_DECOMP_FAILED) { - BOOT_STAGE(3, "decompress failed"); - } else if (bp->kimage_flags & BOOT_IMAGE_COMPRESSED) { - BOOT_STAGE(3, "decompress ok"); - } else { - BOOT_STAGE(3, "decompress skipped"); - } - - if (bp->kimage_flags & BOOT_IMAGE_RELOC_FAILED) { - BOOT_STAGE(3, "relocate failed"); - } else if (bp->kimage_flags & BOOT_IMAGE_RELOCATED) { - BOOT_STAGE(3, "relocate ok"); - } else { - BOOT_STAGE(3, "relocate skipped"); - } - - printk(KERN_INFO "boot: kimage phys=0x%llx size=%llu reloc=%llu\n", - bp->kimage_phys_base, - static_cast<unsigned long long>(bp->kimage_size), - static_cast<unsigned long long>(bp->kimage_reloc_size)); - } else { - BOOT_STAGE(3, "decompress/relocate unknown"); - } - BOOT_STAGE(4, "early c init"); + /* kernel_map filled by arch/riscv/mm/init.cc::setup_vm before MMU + * enable; relocate_kernel has already applied .rela.dyn. */ + printk(KERN_INFO "boot: kimage phys=0x%llx virt=0x%llx size=%llu\n", + static_cast<unsigned long long>(kernel_map.phys_addr), + static_cast<unsigned long long>(kernel_map.virt_addr), + static_cast<unsigned long long>(kernel_map.size)); // --- Subsystem init DAG (strict top-down order) --- - // 1. FDT parsing — make device tree available + // 2. Trap handler — install exception vector FIRST. + // Any fault from here on gets a proper panic backtrace + // instead of a silent hang. (Linux: trap_init is in start_kernel + // right after setup_arch.) + trap_init(); + printk(KERN_INFO "init: trap_init done\n"); + + // 3. FDT parsing — make device tree available fdt_init(fdt_pa); printk(KERN_INFO "init: fdt_init done\n"); - // 2. KASLR — seed PRNG and randomise VA layout. + // 4. KASLR — seed PRNG and randomise VA layout. // MUST happen before vmm_init() builds the kernel page table. { uint64 kaslr_seed = 0; @@ -133,52 +247,122 @@ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { // Sort exception table before setting RO permissions in vmm_init() extable_init(); - // 3. Memblock allocator (Early boot PA allocator) + // ---- Phase 2: Memory Subsystem (strict cascade) ---- + + // 5. Memblock allocator (Early boot PA allocator) memblock_init(fdt_pa); printk(KERN_INFO "init: memblock_init done\n"); - // 4. Virtual memory manager — builds page table with KASLR'd bases + // 6. Virtual memory manager — builds page table with KASLR'd bases vmm_init(); printk(KERN_INFO "init: vmm_init done\n"); - // 5. Physical memory manager (Buddy) + // 7. Physical memory manager (Buddy) pmm_init(); printk(KERN_INFO "init: pmm_init done\n"); - // 6. Slab allocator — operator new usable after this + // 8. Slab allocator — operator new usable after this slab_init(); printk(KERN_INFO "init: slab_init done\n"); - // 7. FDT Phase 2 — build DeviceNode object tree (needs Slab) + // 9. Page cache (minimal hash table, stable interface for file-backed mmap) + page_cache_init(); + + // 10. FDT Phase 2 — build DeviceNode object tree (needs Slab) fdt_unflatten(); printk(KERN_INFO "init: fdt_unflatten done\n"); - // 6. Trap handler — set trap vector - trap_init(); - printk(KERN_INFO "init: trap_init done\n"); + // ---- Phase 3: Task & Scheduler (software state before hardware) ---- - // 6.5 Task management (PID allocator + global task list) + // 11. Task management (PID allocator + global task list) task_init(); + printk(KERN_INFO "init: task_init done\n"); - // 7. Hardware Drivers Discovery (Interrupt Controllers, etc) + pgrp_init(); + + sched_init(); + printk(KERN_INFO "init: sched_init done\n"); + + // ---- Phase 3.5: VFS (needs slab + sched) ---- + super_init(); + ramfs_init(); + vfs_init(); + printk(KERN_INFO "init: vfs_init done (ramfs root mounted)\n"); + + // ---- Phase 3.55: Unpack initramfs into root ---- + populate_rootfs(); + + // ---- Phase 3.6: Device nodes ---- + cdev_init(); + console_cdev_init(); + devfs_init(); + printk(KERN_INFO "init: devfs done\n"); + + // ---- Phase 4: Hardware Drivers & Interrupts ---- + + // 13. Hardware Drivers Discovery (Interrupt Controllers, etc) driver_probe_all(); printk(KERN_INFO "init: driver_probe_all done\n"); - // 8. Timer interrupt (scheduler tick source) + // 13.5. TTY subsystem — needs PLIC (driver_probe_all) for UART RX IRQ + tty_init(); + printk(KERN_INFO "init: tty_init done\n"); + + // 14. Timer interrupt (scheduler tick source) + // Safe now: scheduler is fully initialized. timer_init(); printk(KERN_INFO "init: timer_init done\n"); - // 9. Scheduler — create Idle Task 0, init ready queues - sched_init(); - printk(KERN_INFO "init: sched_init done\n"); - - // 10. Syscall jump table + // 15. Syscall jump table syscall_init(); + printk(KERN_INFO "init: syscall_init done\n"); + + // 16. SMP / IPI infrastructure + smp_init(); + arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI + arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART) - // 10. Boot self-tests — verify subsystem correctness + // ---- Phase 5: Self-test & Launch ---- + + // 17. Boot self-tests — verify subsystem correctness. + // Tests that create kthreads must wait() for them (freeing PIDs). selftest_run_all(); - // kernel_panic("Manual Panic Test", "triggered by developer"); // VERIFIED - // OK + + // 18. Spawn PID 1 (init) — the first persistent task. + // Like Linux's rest_init() → user_mode_thread(kernel_init). + // All test PIDs have been freed, so PID 1 is available. + TaskControlBlock *init_task = + kthread_create("init", init_thread_func, nullptr, 0); + if (init_task) { + init_task->flags |= TF_INIT; + + /* Init is session and process-group leader: sid = pgid = pid. + * Allocate signal_struct AND sighand_struct eagerly so every + * descendant (created via task_clone) inherits a real identity + * and a real lock object — signal sending must take siglock and + * cannot tolerate a null sighand. */ + init_task->signal = alloc_signal_struct(init_task->pid, init_task); + if (!init_task->signal) { + printk(KERN_ERR "boot: FAILED to allocate init signal_struct\n"); + } + init_task->sighand = alloc_sighand(); + if (!init_task->sighand) { + printk(KERN_ERR "boot: FAILED to allocate init sighand\n"); + } + + /* Publish g_init_task so reparenting paths can find the foster + * parent when other tasks exit before init takes over user space. */ + extern TaskControlBlock* g_init_task; + g_init_task = init_task; + + printk(KERN_INFO "boot: init thread created (pid=%d sid=%d pgid=%d)\n", + init_task->pid, + init_task->signal ? init_task->signal->sid : -1, + init_task->signal ? init_task->signal->pgid : -1); + } else { + printk(KERN_ERR "boot: FAILED to create init thread!\n"); + } + BOOT_STAGE(5, "core kernel init"); printk(KERN_INFO "boot: BSP init complete, releasing APs\n"); @@ -187,9 +371,37 @@ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { // guarantees all global data is visible to APs. ap_boot_sync.store(1, __ATOMIC_RELEASE); + // Start AP harts via SBI HSM extension. + // OpenSBI v1.0+ parks non-boot harts in STOPPED state. + // They must be explicitly started with sbi_hart_start(). + // + // kernel_map.phys_addr was filled by setup_vm in arch/riscv/mm/init.cc + // before MMU enable — it's the runtime PA at which the kernel was + // loaded. APs start at the same PA as the BSP. + { + uint64 start_pa = kernel_map.phys_addr; + if (start_pa == 0) { + /* Defensive fallback if setup_vm hasn't filled the field. */ + start_pa = + boot_va_to_pa(virt_addr(reinterpret_cast<uint64>(_start))).raw; + } + uint32 num_harts = 4; // TODO: read from FDT /cpus + for (uint32 h = 0; h < num_harts; h++) { + if (h == static_cast<uint32>(cpu_id)) + continue; // skip BSP + auto ret = arch::sbi::sbi_hart_start(h, start_pa, fdt_paddr); + if (ret.error == arch::sbi::SBI_SUCCESS) { + printk(KERN_INFO "smp: started hart %u at PA 0x%llx\n", h, start_pa); + } else { + printk(KERN_ERR "smp: failed to start hart %u (err=%ld)\n", h, + ret.error); + } + } + } + // BSP enables interrupts — first timer tick will preempt - // into the scheduler loop. - // arch::cpu::intr_on(); + // into the scheduler loop, which will pick up PID 1. + arch::cpu::intr_on(); printk(KERN_INFO "boot: BSP entering idle loop\n"); @@ -197,22 +409,59 @@ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { // ======================================================== // AP: spin until BSP finishes global init // ======================================================== + // DEBUG (commented out — uncomment for SMP debugging): + // arch::sbi::sbi_ecall(1, 0, '1'); // AP entered kernel_main + while (ap_boot_sync.load(__ATOMIC_ACQUIRE) == 0) ; + // arch::sbi::sbi_ecall(1, 0, '2'); // AP passed ap_boot_sync + + // --- Per-CPU init (must happen before any printk!) --- + // early_pgdir doesn't map UART MMIO, so we MUST switch to + // BSP's kernel page table before any console output. + vmm_init_ap(); // Switch to BSP-built kernel page table + + // arch::sbi::sbi_ecall(1, 0, '3'); // vmm_init_ap done + + trap_init_ap(); // Install stvec on this CPU + + // arch::sbi::sbi_ecall(1, 0, '4'); // trap_init_ap done + printk(KERN_INFO "boot: AP online, cpu_id=%llu\n", cpu_id); - // --- Per-CPU init only (no global data mutation) --- - // vmm_init_ap(); - // arch_irq_chip_init_ap(); - // timer_init_ap(); - // sched_init_ap(); + sched_init_ap(); // Init per-CPU RunQueue, create Idle Task + + // arch::sbi::sbi_ecall(1, 0, '5'); // sched_init_ap done + + timer_init_ap(); // Start this CPU's timer interrupt + arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI + arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART) - // AP enables interrupts — becomes Idle Task on this CPU - // arch::cpu::intr_on(); + // AP is now a fully functional scheduler CPU. + // Enable interrupts — first timer tick preempts into idle loop. + arch::cpu::intr_on(); + + printk(KERN_INFO "boot: AP cpu_id=%llu fully initialized\n", cpu_id); + } + + // All CPUs reach here. + // The "current task" is the boot stub (bsp_task or ap_task, pid=-1). + // We must hand off to the scheduler's idle task by calling schedule(). + // Without this, the BSP runs as bsp_task (O1 class, timeslice=20 ticks) + // instead of the idle task (which yields immediately when any task is + // runnable). That delays PID 1 by up to 200ms and can stall entirely + // if no timer tick fires before the first WFI. + { + TaskControlBlock *self = current_task(); + if (self) { + self->state = TaskState::Dead; // Boot stub is done; don't re-enqueue + } + schedule(); // Switch to idle task (or init if it's already runnable) } - // All CPUs: idle loop (wfi until interrupt) + // Should not reach here — schedule() switches away permanently. + // But just in case, spin with WFI as a safety net. while (true) { arch::cpu::halt_until_interrupt(); } |
